CarbazochromeT / drug_app

drug_app
0 stars 0 forks source link

RansackとEnumerizeに対応したチェックボックスを作りたい #11

Closed CarbazochromeT closed 1 year ago

CarbazochromeT commented 1 year ago

お世話になっております。 https://l-chika.hatenablog.com/entry/2017/02/08/235827 https://qiita.com/kamelo151515/items/3b91cdf5deccc862cba2 https://dyson.work/2020/09/21/collection-check-box/

上記サイトを参考にし、Ransackに対応した検索機能を実装することを試みております。 今現在、Drugテーブル内のformulationカラムをRansackの検索結果に載せたいのですが、エラーが発生してしまうので、ご助言をいただきたく思います。

Drugテーブル内のデータ Image from Gyazo

schema.rb

  create_table "drugs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
    t.string "name", null: false, collation: "utf8mb3_general_ci", comment: "商品名"
    t.text "effect_text", collation: "utf8mb3_general_ci", comment: " 効能効果"
    t.text "usage", collation: "utf8mb3_general_ci", comment: "用法用量"
    t.string "document_url", collation: "utf8mb3_general_ci", comment: "添付文書URL"
    t.integer "formulation", comment: "剤型 -- tablet(錠剤): 0, powder(粉): 1, capsule(カプセル): 2, liquid(液剤): 3, nose(点鼻): 4"
    t.integer "division", comment: "リスク区分 -- to_guide(要指導医薬品): 0, one_kind(一類医薬品): 1, two_kind(二類医薬品): 2, three_kind(三類医薬品): 3, two_designate(指定二類医薬品): 4"
    t.integer "taxation", default: 0, null: false, comment: "セルフメディケーション税制"
    t.text "formula", collation: "utf8mb3_general_ci", comment: "成分分量"
    t.text "otc_text", collation: "utf8mb3_general_ci", comment: "製品の特徴"
    t.text "caution", collation: "utf8mb3_general_ci", comment: "使用上の注意"
    t.bigint "maker_name_id"
    t.index ["maker_name_id"], name: "index_drugs_on_maker_name_id"
  end

drug.rb

class Drug < ApplicationRecord
  extend Enumerize
  scope :admin_scope, -> { where(published: true) }
  has_many :drug_ingredients,dependent: :destroy
  has_many :ingredients,through: :drug_ingredients
  has_many :drug_symptoms, dependent: :destroy
  has_many :symptoms, through: :drug_symptoms
  belongs_to :maker_name

  accepts_nested_attributes_for(
    :ingredients,
    reject_if: :all_blank,
    allow_destroy: true
  )

  accepts_nested_attributes_for(
    :symptoms,
    reject_if: :all_blank,
    allow_destroy: true
  )

  enumerize :formulation, in: { tablet: 0, powder: 1, capsule: 2, liquid: 3, nose: 4 }, scope: true
  enumerize :division, in: { to_guide: 0, one_kind: 1, two_kind: 2, three_kind: 3, two_designate: 4 }, scope: true

end

チェックボックスを押すとformulationで定義したものと対応した数字が検索結果に反映されるようにしたいです。 Image from Gyazo

しかし、実際の検索結果は、キーではなく「"tablet", "powder", "capsule"」のようなハッシュの方が呼び出されてしまいます。

Parameters: {"q"=>{"ingredients_name_cont"=>"", "name_cont"=>"", "symptoms_name_in"=>["", "1", "2", "3"], "formulation"=>["", "tablet", "powder", "capsule"], "ingredients_drive_eq"=>"false", "ingredients_tobacco_eq"=>"true", "ingredients_alcohol_eq"=>"false"}, "commit"=>"検索"}
06:55:19 web.1  |   Rendering layout layouts/application.html.erb
06:55:19 web.1  |   Rendering drugs/index.html.erb within layouts/application
06:55:19 web.1  |   Symptom Load (0.5ms)  SELECT `symptoms`.* FROM `symptoms`
06:55:19 web.1  |   ↳ app/views/drugs/_search.html.erb:25
06:55:19 web.1  |   Rendered drugs/_search.html.erb (Duration: 17.2ms | Allocations: 5883)
06:55:19 web.1  |   Drug Load (1.0ms)  SELECT DISTINCT `drugs`.* FROM `drugs` LEFT OUTER JOIN `drug_symptoms` ON `drug_symptoms`.`drug_id` = `drugs`.`id` LEFT OUTER JOIN `symptoms` ON `symptoms`.`id` = `drug_symptoms`.`symptom_id` LEFT OUTER JOIN `drug_ingredients` ON `drug_ingredients`.`drug_id` = `drugs`.`id` LEFT OUTER JOIN `ingredients` ON `ingredients`.`id` = `drug_ingredients`.`ingredient_id` WHERE (`symptoms`.`name` IN ('1', '2', '3') AND `ingredients`.`drive` = 0 AND `ingredients`.`tobacco` = 0 AND `ingredients`.`alcohol` = 0) LIMIT 10 OFFSET 0

そこで、チェックボックスの値をDrug.formulation.valuesに変更すると、配列の中身が変わってきてしまいます。

 <%= f.collection_check_boxes :formulation, Drug.formulation.values, :last, :first  do |b| %>
Parameters: {"q"=>{"ingredients_name_cont"=>"", "name_cont"=>"", "symptoms_name_in"=>[""], "formulation"=>["", "t", "r", "e"], "ingredients_drive_eq"=>"false", "ingredients_tobacco_eq"=>"true", "ingredients_alcohol_eq"=>"false"}, "commit"=>"検索"}
07:06:58 web.1  |   Rendering layout layouts/application.html.erb
07:06:58 web.1  |   Rendering drugs/index.html.erb within layouts/application
07:06:58 web.1  |   Symptom Load (0.4ms)  SELECT `symptoms`.* FROM `symptoms`
07:06:58 web.1  |   ↳ app/views/drugs/_search.html.erb:25
07:06:58 web.1  |   Rendered drugs/_search.html.erb (Duration: 15.7ms | Allocations: 5438)
07:06:58 web.1  |   Drug Load (1.0ms)  SELECT DISTINCT `drugs`.* FROM `drugs` LEFT OUTER JOIN `drug_ingredients` ON `drug_ingredients`.`drug_id` = `drugs`.`id` LEFT OUTER JOIN `ingredients` ON `ingredients`.`id` = `drug_ingredients`.`ingredient_id` WHERE (`ingredients`.`drive` = 0 AND `ingredients`.`tobacco` = 0 AND `ingredients`.`alcohol` = 0) LIMIT 10 OFFSET 0

また、value_hashに変更してみると、エラーが発生してしまいます。

 <%= f.collection_check_boxes :formulation, Drug.formulation.value_hash, :last, :first  do |b| %>
ActionView::Template::Error (undefined method `value_hash' for #<Enumerize::Attribute:0x000000010f5e5578 @klass=Drug(id: integer, name: string, effect_text: text, usage: text, document_url: string, formulation: integer, division: integer, taxation: integer, formula: text, otc_text: text, caution: text, maker_name_id: integer), @name=:formulation, @i18n_scope=nil, @i18n_scopes=["enumerize.drug.formulation"], @values=["tablet", "powder", "capsule", "liquid", "nose"], @value_hash={"0"=>"tablet", "1"=>"powder", "2"=>"capsule", "3"=>"liquid", "4"=>"nose", "tablet"=>"tablet", "powder"=>"powder", "capsule"=>"capsule", "liquid"=>"liquid", "nose"=>"nose"}, @skip_validations_value=false>
07:07:52 web.1  | Did you mean?  values):
07:07:52 web.1  |     36:   <dt>剤型にこだわりはありますか?</dt>
07:07:52 web.1  |     37:   <dd>
07:07:52 web.1  |     38:     <ul class= "search-list">
07:07:52 web.1  |     39:       <%= f.collection_check_boxes :formulation, Drug.formulation.value_hash, :last, :first  do |b| %>
07:07:52 web.1  |     40:         <li>
07:07:52 web.1  |     41:           <label class="CheckboxInput">
07:07:52 web.1  |     42:             <%= b.check_box(class: "CheckboxInput-Input") %>

こちらでも試してみましたが、mapが定義されていないと言われてしまい、うまくいきません。

      <%= f.collection_check_boxes :formulation, Drug.formulation.hash.to_i, :last, :first  do |b| %>
ActionView::Template::Error (undefined method `map' for 1975056336601945882:Integer
07:10:07 web.1  | Did you mean?  tap):
07:10:07 web.1  |     36:   <dt>剤型にこだわりはありますか?</dt>
07:10:07 web.1  |     37:   <dd>
07:10:07 web.1  |     38:     <ul class= "search-list">
07:10:07 web.1  |     39:       <%= f.collection_check_boxes :formulation, Drug.formulation.hash.to_i, :last, :first  do |b| %>

Enumerizeのモジュールを参考にしていますが、mapがどのように機能しているのか、理解が追いつかず…。 https://github.com/brainspec/enumerize/blob/master/lib/enumerize/attribute.rb

drug.rbにメソッドを追記してからviewファイルを記載した場合も、エラーが発生してしまいました。

  enumerize :formulation, in: { tablet: 0, powder: 1, capsule: 2, liquid: 3, nose: 4 }, scope: true
  enumerize :division, in: { to_guide: 0, one_kind: 1, two_kind: 2, three_kind: 3, two_designate: 4 }, scope: true

  def search_by_formulation
    Drug.formulation.find_values(*Drug.formulation.values.map(&:to_sym)).map { |formulation| [formulation.text, formulation.value] }
  end
ActionView::Template::Error (undefined method `map' for #<Enumerize::Attribute:0x000000010f86d2f0 @klass=Drug(id: integer, name: string, effect_text: text, usage: text, document_url: string, formulation: integer, division: integer, taxation: integer, formula: text, otc_text: text, caution: text, maker_name_id: integer), @name=:formulation, @i18n_scope=nil, @i18n_scopes=["enumerize.drug.formulation"], @values=["tablet", "powder", "capsule", "liquid", "nose"], @value_hash={"0"=>"tablet", "1"=>"powder", "2"=>"capsule", "3"=>"liquid", "4"=>"nose", "tablet"=>"tablet", "powder"=>"powder", "capsule"=>"capsule", "liquid"=>"liquid", "nose"=>"nose"}, @skip_validations_value=false>
07:18:48 web.1  | Did you mean?  tap):
07:18:48 web.1  |     36:   <dt>剤型にこだわりはありますか?</dt>
07:18:48 web.1  |     37:   <dd>
07:18:48 web.1  |     38:     <ul class= "search-list">
07:18:48 web.1  |     39:       <%= f.collection_check_boxes :search_by_formulation, Drug.formulation, :last, :first  do |b| %>
07:18:48 web.1  |     40:         <li>
07:18:48 web.1  |     41:           <label class="CheckboxInput">
07:18:48 web.1  |     42:             <%= b.check_box(class: "CheckboxInput-Input") %>

ご助言のほどよろしくお願いいたします。

drugs_controller.rb

class DrugsController < ApplicationController
  skip_before_action :require_login
  before_action :set_drug,only: [:show, :edit, :update, :destroy]

  def index
    @q = Drug.ransack(params[:q])
    @drugs = @q.result(distinct: true).includes(:symptoms, :ingredients).page(params[:page]).per(10)
    @results = @q.result
  end

  def show
  end

  def search
    @q = Drug.ransack(params[:q])
    @drugs = @q.result(distinct: true).includes(:symptoms, :ingredients).page(params[:page]).per(10)
    @results = @q.result
  end

  def show
    @drug = Drug.find(params[:id])
  end

  private

  def set_drug
    @drug = Drug.find(params[:id])
  end

  def drug_params
    params.require(:drugs).permit(:id, :drug, :name, :effect_text, :usage, :document_url, {formulation: []}, :division, :taxation,  { symptom_ids: [] },  { ingredient_ids: [] }, :drive,:tobacco, :alcohol, :maker_names)
  end
end

drugs/_search.html.erb

<dl>
<%= search_form_for @q, url: drugs_path, class: 'drug-search' do |f| %>
  <dt>成分名で検索</dt>
  <dd>
    <ul class= "search-list">
      <li>
      <div class = "search-textarea">
      <%= f.text_field :ingredients_name_cont, placeholder: '成分名で検索' %>
      </div>
      </li>
    </ul>
  </dd>
  <dt>商品名で検索</dt>
  <dd>
    <ul class= "search-list">
      <li>
        <div class = "search-textarea">
        <%= f.text_field :name_cont, placeholder: '商品名で検索' %>
      </li>
      </ul>
  </dd>
  <dt>どんな症状がありますか?</dt>
  <dd>
    <ul class= "search-list">
      <%= f.collection_check_boxes :symptoms_name_in, Symptom.all , :id, :name, { multiple: true }  do |b| %>
        <li>
          <label class="CheckboxInput">
            <%= b.check_box(class: "CheckboxInput-Input") %>
            <span class="CheckboxInput-DummyInput"></span>
            <%= b.label(class: "CheckboxInput-LabelText") %>
          </label>
        </li>
      <% end %>
    </ul>
  </dd>
  <dt>剤型にこだわりはありますか?</dt>
  <dd>
    <ul class= "search-list">
      <%= f.collection_check_boxes :search_by_formulation, Drug.formulation, :last, :first  do |b| %>
        <li>
          <label class="CheckboxInput">
            <%= b.check_box(class: "CheckboxInput-Input") %>
            <span class="CheckboxInput-DummyInput"></span>
            <%= b.label(class: "CheckboxInput-LabelText") %>
          </label>
        </li>
        <% end %>
    </ul>
  </dd>
  <dt>車の運転はしますか?</dt>
  <dd>
    <ul class= "search-list">
      <li>
      <div class="yesno">
      <%= f.radio_button :ingredients_drive_eq, true, id:'drive_ok', checked: true  %>
      <%= f.label :drive_true ,"はい", for: 'drive_ok', class:"switch-on" %>
      <%= f.radio_button :ingredients_drive_eq, false, id:'drive_ng', checked: true %>
      <%= f.label :drive_false ,"いいえ",for: 'drive_ng', class:"switch-off" %>
      </div>
      </li>
    </ul>
  </dd>
  <p style="clear:both"></p>
  <dt>タバコは吸いますか?</dt>
  <dd>
    <ul class= "search-list">
      <li>
      <div class="yesno">
      <%= f.radio_button :ingredients_tobacco_eq, false, id:'tobacco_ok'  %>
      <%= f.label :tobacco_true ,"はい", for: 'tobacco_ok', class:"switch-on" %>
      <%= f.radio_button :ingredients_tobacco_eq, true, id:'tobacco_ng', checked: true %>
      <%= f.label :tobacco_false ,"いいえ",for: 'tobacco_ng', class:"switch-off" %>
      </div>
      </li>
    </ul>
  </dd>
  <p style="clear:both"></p>
  <dt>お酒は飲みますか?</dt>
  <dd>
    <ul class= "search-list">
      <li>
      <div class="yesno">
      <%= f.radio_button :ingredients_alcohol_eq, true, id:'alcohol_ok'  %>
      <%= f.label :alcohol_true ,"はい", for: 'alcohol_ok', class:"switch-on" %>
      <%= f.radio_button :ingredients_alcohol_eq, false, id:'alcohol_ng', checked: true %>
      <%= f.label :alcohol_false ,"いいえ",for: 'alcohol_ng', class:"switch-off" %>
      </div>
      </li>
    </ul>
  </dd>
    <%= f.submit '検索', class: 'btn btnmint' do %>
    検索ページに進む
    <% end %>
</dl>
<% end %>
kenchasonakai commented 1 year ago

Ransackでenum的なものを使う時は検索フォームからparamsで送る値に気をつけないといけなかったような気がするのでそもそもどういう形式でRansackに値を渡してあげれば検索できるようになるのかを検証して、それに沿うような形で実装すると良いかと思います

下記の記事とかが古いですが関係あるような気がします https://www.tom08.net/2016-12-05-121746/

CarbazochromeT commented 1 year ago

コメントありがとうございます。

https://spirits.appirits.com/doruby/8917/?cn-reloaded=1 https://blog.to-ko-s.com/enum-select-box/

こちらの記事を見ながらハッシュ形式で送っております。 また、今回はenum_helpではなくEnumerizeを使用しているため、もしかしたら若干扱い方が変わるかもしれません。

drug.rb

  enumerize :formulation, in: { :tablet => 0, :powder => 1, :capsule => 2, :liquid => 3, :nose => 4 }, scope: :true
irb(main):007:0> Drug.formulation.values
=> 
["tablet",
 "powder",
 "capsule",
 "liquid",
 "nose"]
irb(main):008:0> Drug.formulation.options
=> 
[["錠剤", "tablet"],
 ["散剤", "powder"],
 ["カプセル", "capsule"],
 ["液剤", "liquid"],
 ["点鼻薬", "nose"]]

valuesを使用すると今文字列が来てしまっているのですが、今回はテーブルの中身は数字で入っているので、valuesを使用した際に数字が呼び出せるようにしたいです。

kenchasonakai commented 1 year ago

https://github.com/brainspec/enumerize/issues/158#issuecomment-45342892

上記のissueを参考に下記の形とかで行けないですかね?

Drug.formulation.values.map { |v| [v.value] }
CarbazochromeT commented 1 year ago
      <%= f.collection_check_boxes :formulation, Drug.formulation.values.map { |v| [v.value] }, :last, :first  do |b| %>
        <li>
          <label class="CheckboxInput">
            <%= b.check_box(class: "CheckboxInput-Input") %>
            <span class="CheckboxInput-DummyInput"></span>
            <%= b.label(class: "CheckboxInput-LabelText") %>
          </label>
        </li>
        <% end %>

Image from Gyazo

表示が「錠剤」、「散剤」から番号に変わってしまいました。

Parameters: {"q"=>{"ingredients_name_cont"=>"", "name_cont"=>"", "symptoms_name_in"=>[""], "formulation"=>["", "0", "1", "2"], "ingredients_drive_eq"=>"false", "ingredients_tobacco_eq"=>"true", "ingredients_alcohol_eq"=>"false"}, "commit"=>"検索"}
20:17:48 web.1  |   Rendering layout layouts/application.html.erb
20:17:48 web.1  |   Rendering drugs/index.html.erb within layouts/application
20:17:48 web.1  |   Symptom Load (0.4ms)  SELECT `symptoms`.* FROM `symptoms`
20:17:48 web.1  |   ↳ app/views/drugs/_search.html.erb:25
20:17:48 web.1  |   Rendered drugs/_search.html.erb (Duration: 16.1ms | Allocations: 5463)
20:17:48 web.1  |   Drug Load (1.3ms)  SELECT DISTINCT `drugs`.* FROM `drugs` LEFT OUTER JOIN `drug_ingredients` ON `drug_ingredients`.`drug_id` = `drugs`.`id` LEFT OUTER JOIN `ingredients` ON `ingredients`.`id` = `drug_ingredients`.`ingredient_id` WHERE (`ingredients`.`drive` = 'false' AND `ingredients`.`tobacco` = 'true' AND `ingredients`.`alcohol` = 'false') LIMIT 10 OFFSET 0
kenchasonakai commented 1 year ago

valuesを使用した際に数字が呼び出せるようにしたいです。

数字が呼び出せるようにしたかったのかなと思っていたのですが違かったですかね...?

f.collection_check_boxesにどんな値を渡したいのか実際に渡したい値(配列?)を記述していただいて良いでしょうか?

CarbazochromeT commented 1 year ago

伝わりにくくてすみません。 チェックボックスの表示は「錠剤」、「散剤」などの日本語表示で、渡したい値は「0」、「1」などの数字です。 チェックボックスを押すと入力が反映されるようにしたいです。

kenchasonakai commented 1 year ago

[0, 1, 2]とか[["錠剤", 0], ["散剤", 1]]みたいな感じで日本語ではなく実際にf.collection_check_boxesに渡したい値を書いてもらえるとイメージしやすいです

CarbazochromeT commented 1 year ago

https://qiita.com/Shokichi_jp/items/b97d6c48f295a3d54a81 こちらのサイトを参考に、例えばですが、

irb(main):001:0> Drug.formulation
=> {"tablet"=>0, "powder"=>1, ...}
irb(main):002:0> Drug.formulation.keys
=> ["tablet", "powder", ...]

のようになるのが理想系ですね

kenchasonakai commented 1 year ago

{"tablet"=>0, "powder"=>1, ...}のようにしたいのであれば

Drug.formulation.values.map { |v| [v.text, v.value] }.to_hとかで行けないですかね?

["tablet", "powder", ...]は書いてくれている実行ログをみる感じDrug.formulation.valuesでとれてそうな気がします

CarbazochromeT commented 1 year ago

こちらで書き直してみました。

    <ul class= "search-list">
      <%= f.collection_check_boxes :formulation, Drug.formulation.values.map { |v| [v.text, v.value] }.to_h, :last, :first  do |b| %>
        <li>
          <label class="CheckboxInput">
            <%= b.check_box(class: "CheckboxInput-Input") %>
            <span class="CheckboxInput-DummyInput"></span>
            <%= b.label(class: "CheckboxInput-LabelText") %>
          </label>
        </li>
        <% end %>
    </ul>

錠剤ラベルが反映され、数字が行き渡るようになりました!ありがとうございます!

Parameters: {"q"=>{"ingredients_name_cont"=>"", "name_cont"=>"", "symptoms_name_in"=>["", "1", "2", "3", "4"], "formulation"=>["", "2", "3"], "ingredients_drive_eq"=>"true", "ingredients_tobacco_eq"=>"true", "ingredients_alcohol_eq"=>"false"}, "commit"=>"検索"}
09:19:22 web.1  |   Rendering layout layouts/application.html.erb
09:19:22 web.1  |   Rendering drugs/index.html.erb within layouts/application
09:19:22 web.1  |   Symptom Load (0.4ms)  SELECT `symptoms`.* FROM `symptoms`
09:19:22 web.1  |   ↳ app/views/drugs/_search.html.erb:25
09:19:22 web.1  |   Rendered drugs/_search.html.erb (Duration: 15.4ms | Allocations: 6044)
09:19:22 web.1  |   Drug Load (0.7ms)  SELECT DISTINCT `drugs`.* FROM `drugs` LEFT OUTER JOIN `drug_symptoms` ON `drug_symptoms`.`drug_id` = `drugs`.`id` LEFT OUTER JOIN `symptoms` ON `symptoms`.`id` = `drug_symptoms`.`symptom_id` LEFT OUTER JOIN `drug_ingredients` ON `drug_ingredients`.`drug_id` = `drugs`.`id` LEFT OUTER JOIN `ingredients` ON `ingredients`.`id` = `drug_ingredients`.`ingredient_id` WHERE (`symptoms`.`name` IN ('1', '2', '3', '4') AND `ingredients`.`drive` = 'true' AND `ingredients`.`tobacco` = 'true' AND `ingredients`.`alcohol` = 'false') LIMIT 10 OFFSET 0