ransackable_scopesを使った検索でdate_fieldを使うとエラーになる
Railsアプリでの検索機能を実装できるRansackというGemがあります。
Ransackでは引数を受け取るscopeを定義してそれを検索で使うことができます。ransackable_scopesという機能を使います。
ransack - Using Scopes/Class Methods
このransackable_scopesとActionView::Helpers::FormHelper#date_fieldを併用するとエラーになる事象があったので、それに関するメモです。
Issueを作ったので、ほぼそれのコピペです。
バージョン
- ransack 2.4.2
- Rails 6.1.4
- Ruby 2.6.3
再現手順
article.rb
class Article < ApplicationRecord
scope :created_since, -> (date) {
where('created_at >= ?', date)
}
def self.ransackable_scopes(auth_object = nil)
%i[created_since]
end
endsearch_controller.rb
class SearchController < ApplicationController
def index
@q = Article.ransack(params[:q])
end
endsearch/index.html.slim
= search_form_for @q, url: search_index_path do |form|
= form.date_field :created_since
= form.submit 'Search'
検索を実行する(検索フォームをサブミット)と下記のエラーとなります。
undefined method `strftime' for "2021-09-25":String
Did you mean? strip
ちなみにransackable_scopesを介さない検索であれば、上記のようなエラーは発生しません。
解決策
シンプルにsearch_fieldを使えば解決します。
= form.search_field :created_since, type: 'date'Ransackの中の処理
ransackable_scopesを介さない検索であればエラーにならないところが謎だったので、Ransackの処理を追ってみました。
ransackable_scopesではない普通の検索であれば、Condition#valueからValue#castが呼ばれて、検索時の value をよしなにキャストしてくれます。
lib/ransack/nodes/condition.rb
def value
if predicate.wants_array
values.map { |v| v.cast(default_type) }
else
values.first.cast(default_type)
end
endlib/ransack/nodes/value.rb
def cast(type)
case type
when :date
cast_to_date(value)
when :datetime, :timestamp, :time
cast_to_time(value)
when :boolean
cast_to_boolean(value)
when :integer
cast_to_integer(value)
when :float
cast_to_float(value)
when :decimal
cast_to_decimal(value)
when :money
cast_to_money(value)
else
cast_to_string(value)
end
endransackable_scopesの場合は上記処理を通過しないので、キャストされずに文字列のままstrftimeを呼び出そうとしてエラーになっているようです。
action_view/helpers/tags/date_field.rb
module ActionView
module Helpers
module Tags # :nodoc:
class DateField < DatetimeField # :nodoc:
private
def format_date(value)
value&.strftime("%Y-%m-%d") # 👈 エラーとなる箇所
end
end
end
end
endsearch_fieldを使うと上記処理は通らないのでエラーになりません。