CarbazochromeT / drug_app

drug_app
0 stars 0 forks source link

PostgreSQL移行後のエラー #16

Open CarbazochromeT opened 1 year ago

CarbazochromeT commented 1 year ago

MySQLからPostgreSQLに移行後、今まで起こり得なかったデータでエラーが発生するようになってしまったので、質問させてください。

herokuにデプロイしたこちらのサイトで、Ransackを用いた検索フォームを使用しております。

https://care-drug-searcher-7c69759f70d9.herokuapp.com/drugs

herokuアプリ上で剤型にチェックマークを押して検索ボタンを押すと、エラーが発生してしまいます。 なお、症状にチェックマークを押した場合は、問題なく結果が出てくるようになっております。

We're sorry, but something went wrong.
If you are the application owner check the logs for more information.

おそらくPostgresで、drugs.formulation = 0のように配列の中身が数値になっていないがためにエラーが発生しているのだと思われます。 MySQLの時は、特に数値になっているかは聞かれなかったため、問題なく検索結果が表示されていました。

MySQL時

SELECT COUNT(DISTINCT `drugs`.`id`) AS `count_id`, `drugs`.`id` AS `drugs_id` 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` LEFT OUTER JOIN `ingredients` `ingredients_drugs` ON `ingredients_drugs`.`id` = `drug_ingredients`.`ingredient_id` WHERE (`drugs`.`formulation` = 0) GROUP BY `drugs`.`id`

PostgreSQL移行後
: I, [2023-10-18T10:45:00.013119 #6]  INFO -- : [717c1a96-2e5a-445c-8976-e99b83147bdc]   Parameters: {"utf8"=>"✓", "q"=>{"formula_cont"=>"", "name_cont"=>"", "symptoms_id_in"=>[""], "formulation_eq_any"=>["", "0"], "ingredients_drive_not_true"=>"{:true=>1, :false=>0}", "ingredients_tobacco_not_true"=>"{:true=>1, :false=>0}", "ingredients_alcohol_not_true"=>"{:true=>1, :false=>0}"}, "commit"=>"検索"}

~~~~~
ActionView::Template::Error (PG::UndefinedFunction: ERROR:  operator does not exist: character varying = integer
2023-10-18T10:45:00.026048+00:00 app[web.1]: LINE 1: ...nts"."ingredient_id" WHERE ("drugs"."formulation" = 0) GROUP...
2023-10-18T10:45:00.026048+00:00 app[web.1]: ^
2023-10-18T10:45:00.026049+00:00 app[web.1]: HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.

関連コード

_search.html.erb

<%= search_form_for @q, url: drugs_path, class: 'drug-search' do |f| %>
~~~~~
  <dt>剤型にこだわりはありますか?</dt>
  <dd>
    <ul class= "search-list">
      <%= f.collection_check_boxes :formulation_eq_any, Drug.formulation.values.map { |v| [v.text, v.value] }.to_h, :last, :first , { 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>
~~~~~

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.string "division", comment: "リスク区分 -- to_guide(要指導医薬品): 0, one_kind(一類医薬品): 1, two_kind(二類医薬品): 2, three_kind(三類医薬品): 3, two_designate(指定二類医薬品): 4"
    t.string "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

  add_foreign_key "drug_symptoms", "drugs"
  add_foreign_key "drug_symptoms", "symptoms"

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
  has_many :symptoms, through: :drug_symptoms
  belongs_to :maker_name
  has_many :likes, dependent: :destroy

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

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

  def admin_label
    name.upcase
  end

  enumerize :formulation, in: { :tablet => 0, :powder => 1, :capsule => 2, :liquid => 3, :nose => 4,
    :troche => 5, :candy => 6, :gargle => 7, :ointment => 8, :suppository => 9, :spray => 10 }, scope: :true
  enumerize :division, in: { to_guide: 0, one_kind: 1, two_kind: 2, three_kind: 3, two_designate: 4 }, scope: true

end

試したこと

postgresにログインし、キャスト変換を行いました。 https://qiita.com/6in/items/f23ead1314b9e6d2f2b7

postgres=# CREATE CAST (int4 AS text) WITH INOUT AS IMPLICIT;
CREATE CAST
postgres=# CREATE CAST (text as numeric) WITH INOUT AS IMPLICIT;
CREATE CAST
postgres=# CREATE OPERATOR || (PROCEDURE = textint4cat,LEFTARG = text, RIGHTARG = int4);
ERROR:  function textint4cat(text, integer) does not exist
postgres=# CREATE FUNCTION textint4cat(text, int4) RETURNS text
   AS 'SELECT $1 || $2::pg_catalog.text' LANGUAGE sql IMMUTABLE STRICT;
CREATE FUNCTION
postgres=# CREATE OPERATOR || (PROCEDURE = textint4cat,LEFTARG = text, RIGHTARG = int4);
CREATE OPERATOR
postgres=# exit

その後、チェックボックスの値で数字に出てくるように変更しました。

      <%= f.collection_check_boxes :formulation_eq_any, Drug.formulation.values.map { |v| [v.text, v.value.to_i] }.to_h, :last, :first , { multiple: true }  do |b| %>

パラメーターの値は文字列のままで、エラーが発生してしまいます。 "formulation_eq_any"=>["", "0"],

 [2023-10-18T22:27:44.199752 #11]  INFO -- : [74791bb0-6ee1-4bec-af99-91fa43ca848d]   Parameters: {"utf8"=>"✓", "q"=>{"formula_cont"=>"", "name_cont"=>"", "symptoms_id_in"=>[""], "formulation_eq_any"=>["", "0"], "ingredients_drive_not_true"=>"{:true=>1, :false=>0}", "ingredients_tobacco_not_true"=>"{:true=>1, :false=>0}", "ingredients_alcohol_not_true"=>"{:true=>1, :false=>0}"}, "commit"=>"検索"}
ActionView::Template::Error (PG::UndefinedFunction: ERROR:  operator does not exist: character varying = integer
2023-10-18T22:27:44.212987+00:00 app[web.1]: LINE 1: ...nts"."ingredient_id" WHERE ("drugs"."formulation" = 0) GROUP...

その後のto_hで配列にするところでまた文字列に変換されているのがいけないのでしょうか。 どなたかアドバイスをお願い致します。

tmtkzk0823 commented 1 year ago

こちらの記事が参考になるかもしれないので共有しますね。 https://qiita.com/k-mashi/items/ecff1a2a693d21db5fb1

CarbazochromeT commented 1 year ago

渡していただいた記事を参考に、コードを組ませていただきましたが、indexメソッドのところでエラーが発生してしまいます。 ArgumentErrorが発生してしまうようです。 検索結果が行き渡っている時にだけ、formulationの配列の中身を数値にしたいのですが、どうすればいいでしょうか?

2023-10-19T22:55:39.873680+00:00 app[web.1]: I, [2023-10-19T22:55:39.873534 #6]  INFO -- : [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3] Started GET "/drugs" for 133.201.8.65 at 2023-10-19 22:55:39 +0000
2023-10-19T22:55:39.918413+00:00 app[web.1]: I, [2023-10-19T22:55:39.918316 #6]  INFO -- : [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3] Processing by DrugsController#index as HTML
2023-10-19T22:55:40.008662+00:00 app[web.1]: I, [2023-10-19T22:55:40.008588 #6]  INFO -- : [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3] Completed 500 Internal Server Error in 90ms (ActiveRecord: 33.5ms | Allocations: 32169)
2023-10-19T22:55:40.009163+00:00 app[web.1]: F, [2023-10-19T22:55:40.009122 #6] FATAL -- : [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3]
2023-10-19T22:55:40.009176+00:00 app[web.1]: [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3] ArgumentError (wrong number of arguments (given 1, expected 0)):
2023-10-19T22:55:40.009177+00:00 app[web.1]: [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3]
2023-10-19T22:55:40.009177+00:00 app[web.1]: [e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3] app/controllers/drugs_controller.rb:14:in `index'
2023-10-19T22:55:40.010285+00:00 heroku[router]: at=info method=GET path="/drugs" host=care-drug-searcher-7c69759f70d9.herokuapp.com request_id=e84c1002-90a3-4f4c-88aa-ae06d8a9f7b3 fwd="133.201.8.65" dyno=web.1 connect=0ms service=149ms status=500 byte

drugs_controller.rb

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

  def index
    @q = Drug.ransack(params[:q])
    @drugs = @q.result(distinct: true).includes(:symptoms, :ingredients, :maker_name)
    .select('drugs.*', 'count(ingredients.id) AS ingredients')
    .left_joins(:ingredients)
    .group('drugs.id')
    .order('ingredients ASC')
    .page(params[:page]).per(10)
    if @q.result.present?
      @drugs.update(params(formulation_params))
    end
    render :index
  end

〜〜〜

  private

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

  def drugs_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

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

  def formulation_params
    params.permit({formulation: []})
  end

  def params_int(formulation_params)
    formulation_params.each do |key, value|
      if integer_string?(value)
        formulation[key] = value.to_i
      end
    end
  end
end

私の方では、こちらのコードを追記いたしました。 urlがgetの状態でupdateを使うのがいけないのでしょうか?

    if @q.result.present?
      @drugs.update(params(formulation_params))
    end

他のコードはこちらです。

apprication_controller.rb

class ApplicationController < ActionController::Base
  add_flash_types :success, :info, :warning, :danger
  before_action :require_login

  private

  def not_authenticated
    redirect_to login_path, danger: "ログインしてください"
  end

  def integer_string?(str)
    Integer(str)
    true
  rescue ArgumentError
    false
  end
end
tmtkzk0823 commented 1 year ago

indexメソッドのところでエラーが発生してしまいます。 こちらindexアクションがよばれた時にエラーが起こっているのか、render indexでレンダリングをしている時(そこまでの処理は通常に行われている)エラーが発生しているのかどちらか検討つきそうでしょうか?

CarbazochromeT commented 1 year ago

こちらでupdateの内容を書き足した後にエラーが発生したため、indexアクション内でのエラーで間違いないと思います。 html.erbの内容を書き足す前は正常に動いておりました。


def index
    @q = Drug.ransack(params[:q])
    @drugs = @q.result(distinct: true).includes(:symptoms, :ingredients, :maker_name)
    .select('drugs.*', 'count(ingredients.id) AS ingredients')
    .left_joins(:ingredients)
    .group('drugs.id')
    .order('ingredients ASC')
    .page(params[:page]).per(10)
    render :index
  end

こちらがエラーが起こる前のdrugs_controller.erbの中身です。

tmtkzk0823 commented 1 year ago

ありがとうございます。

def params_int(formulation_params)

こちらのメソッドを呼び出している箇所が見当たらないのですが、どこかで呼び出していますでしょうか?(見逃していたらすみません。)

CarbazochromeT commented 1 year ago

すみません、呼び出していませんでした。

そもそも数値に変換することができるはずなのに変換できないということは、Ransackの方で勝手に文字列変換されてしまっていることが原因な気がしてきました。

irb(main):032:1* def integer_string?(str)
irb(main):033:1*   Integer(str)
irb(main):034:1*   true
irb(main):035:1* rescue ArgumentError
irb(main):036:1*   false
irb(main):037:0> end
=> :integer_string?
irb(main):031:0> integer_string?(drug.formulation.value)
=> true
irb(main):030:0> Drug.formulation.values.map { |v| [v.text, v.value] }.to_h
=> 
{"錠剤"=>0,                                                
 "散剤"=>1,                                                
 "カプセル"=>2,                                            
 "液剤"=>3,                                                
 "点鼻"=>4,                                                
 "トローチ"=>5,                                            
 "飴"=>6,                                                  
 "うがい液"=>7,                                            
 "軟膏"=>8,                                                
 "坐薬"=>9,                                                
 "のどスプレー"=>10}                                       
irb(main):013:0> Drug.ransack(formulation_eq_any: 1).result.to_sql
=> "SELECT \"drugs\".* FROM \"drugs\" WHERE (\"drugs\".\"formulation\" = 1)"                     

formulation_in_anyにしたら無事に出てくるようになりました!

  <dt>剤型にこだわりはありますか?</dt>
  <dd>
    <ul class= "search-list">
      <%= f.collection_check_boxes :formulation_in_any, Drug.formulation.values.map { |v| [v.text, v.value] }.to_h, :last, :first , { 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>