Rails
ransack
activeadmin

ActiveAdminで自作scopeを使ったfilterに'1'を渡すとエラーになる

現象

ActiveAdminにて、以下のようなコードで自作scopeを利用したfilterを使うと、検索値が'1'のときに ArgumentError (wrong number of arguments (0 for 1)) が発生する。

model/product.rb
class Product < ActiveRecord::Base
  ...

  # custom scope
  scope :original_scope, -> (product_id) {
    product = Product.find(product_id)
    # do something
  end

  def self.ransackable_scopes(_auth_object = nil)
    %i(original_scope)
  end

  ...
end
app/product_admin/product.rb
ActiveAdmin.register Product, namespace: :product_admin do
  ...

  filter :original_scope, as: :select, collection: -> { Product.all }

  ...
end

原因調査

調べてみると、どうやらscopeに渡すときに、0と'0'をfalseに、1と'1'をtrueに変換しているので、値で渡せないようです。

Ransackで自前scope使う時に'1'や'0'を値として渡せない?

関連するIssueを見るとCloseになっているのですが、バージョンの問題なのか私の環境ではエラーのままでした。

Wrong result and errors for join/group/having scope with certain values (0 and 1) #502

対処

案1:可変長引数を使う

可変長引数で受けるとvalue=[]になるので、無理やり書き換える。
今回のケースではidを渡す用途のため、0は無視する。

model/product.rb
class Product < ActiveRecord::Base
  ...

  # custom scope
  scope :original_scope, -> (*product_id) {
    product_id = [1] if product_id == [] # hotfix
    product = Product.find(product_id[0])
    # do something
  end

  ...
end

かなり強引なやりかたで、汎用性も低そうなので、できれば避けたい。

案2:代わりになる引数を使う

今回のケースでは、引数で渡した数値(ID)を使ってProductの検索をするため、数値を使わずに検索できる方法を利用した。

model/product.rb
class Product < ActiveRecord::Base
  ...

  # custom scope
  scope :original_scope, -> (product_name) {
    product = Product.find_by(name: product_name)
    # do something
  end

  ...
end

この方法では当たり前かもしれないが、

  1. ユニークな値であること
  2. インデックスが張られていること

がないとレコードが複数件ヒットしたり、検索が遅かったりする。


もっとスマートな解決方法があれば、ぜひコメントをm(_ _)m

おわり