3
0

ransack scopeの検索キーワードとして 'T' が使えない

Last updated at Posted at 2023-09-25

問題

ransackでは、ActiveRecordのscopeを用いて検索することができます。しかし、以下のように検索キーワードとして 'T' は使えません。エラーメッセージにある通り、scopeに渡されているはずのキーワードが渡されていません。

Sep-24-2023 23-49-35.gif

環境

  • Rails 7.1.7
  • Ruby 3.2.2
  • Ransack 4.0.0

原因

このような問題が起きるのは、'T' などといった真理値っぽいものは、ActiveRecordのscopeに渡される前にサニタイズされるからです。

検索キーワードとして、真っぽいものが入っていたら trueに、偽っぽいものが入っていたらfalseに変換しています(sanitized_scope_argsメソッドの5-8行目)。

def sanitized_scope_args(args)
  if args.is_a?(Array)
    args = args.map(&method(:sanitized_scope_args))
  end

  if Constants::TRUE_VALUES.include? args
    true
  elsif Constants::FALSE_VALUES.include? args
    false
  else
    args
  end
end

TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set

ここで変換されたものがchain_scopeメソッドに渡されます(add_scopeメソッドの12行目)。検索キーワードとして 'T' を渡していれば、sanitized_argsとしてtrueが渡されます。したがって、chain_scopeメソッド内の3行目が実行されます。つまり、ActiveRecord scopeの引数として何も渡さないということです。そのため、上記gifにあるようなエラーメッセージが表示されます。

def add_scope(key, args)
  sanitized_args = if Ransack.options[:sanitize_scope_args] && !@context.ransackable_scope_skip_sanitize_args?(key, @context.object)
    sanitized_scope_args(args)
  else
    args
  end

  if @context.scope_arity(key) == 1
    @scope_args[key] = args.is_a?(Array) ? args[0] : args
  else
    @scope_args[key] = args.is_a?(Array) ? sanitized_args : args
  end
  @context.chain_scope(key, sanitized_args)
end

def chain_scope(scope, args)
  return unless @klass.method(scope) && args != false
  @object = if scope_arity(scope) < 1 && args == true
    @object.public_send(scope)
  else
    @object.public_send(scope, *args)
  end
end

解決策

真理値っぽいものがtruefalseにサニタイズされることが原因です。このサニタイズのスキップは、ransack でサポートされています。ransackable_scopes_skip_sanitize_argsメソッドを以下のようにオーバーライドします。そうすれば、partial_match scopeの引数はサニタイズされません。詳しくはドキュメントを参照ください。

class Stock < ApplicationRecord
  scope :partial_match, ->(keyword) do
    return all if keyword.blank?

    pattern = "%#{sanitize_sql(keyword)}%"
    where('name LIKE :pattern', pattern:)
  end

  private

  class << self
    def ransackable_scopes_skip_sanitize_args
      %i[partial_match]
    end
  end  
end

まとめ

「ransack scopeを用いた検索では、キーワードとして 'T' が使えない」という問題の原因は、検索キーワード中の真理値っぽいものがサニタイズされていることでした。この問題は、そのサニタイズをスキップすることで解決しました。

Sep-24-2023 23-49-35.gif

なぜ真理値っぽいものをサニタイズしているのか?

boolean型のフィールドに対してransack scopeをいい感じに使えるようにするために、真理値っぽいものをサニタイズしているのかもしれません(@aki77 さんに教えてもらいました)。

例えば、チェックボックスのvalue'1''T'などいろんな値が使われています。'1''T'を一意な値に変換して、scope内で使いやすいようにしているのだとしたら、サニタイズするのも少しは納得します。ただ、サニタイズという言い方は微妙ですが。。。

参考URL

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0