環境
- Rails 7.1.5.1
- Ruby 3.1.5
この記事を書こうと思った理由
ransack gemを2.5.0から4系にアップデートを行った時に、下記のようなメッセージが出力されました
RuntimeError in Home#index
Ransack needs User attributes explicitly allowlisted as
searchable. Define a `ransackable_attributes` class method in your `User`
model, watching out for items you DON'T want searchable (for
example, `encrypted_password`, `password_reset_token`, `owner` or
other sensitive information). You can use the following as a base:
下記の記事を見ながら解決しようと思った時に、どんな仕組みになっているのかなぁ〜とgemのコードを見に行った時の備忘録です
ransackable_attributesのコード
ransackable_attributes(auth_object = nil)
まずこんな感じで定義されています
# Ransackable_attributes, by default, returns all column names
# and any defined ransackers as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransackable_attributes(auth_object = nil)
@ransackable_attributes ||= deprecated_ransackable_list(:ransackable_attributes)
end
||=
自己代入で@ransackable_attributes
がなければdeprecated_ransackable_list(:ransackable_attributes)
を呼びます
deprecated_ransackable_list(method)
ransackable_attributes(auth_object = nil)
で呼ばれているdeprecated_ransackable_list(:ransackable_attributes)
はこんな感じ
def deprecated_ransackable_list(method)
list_type = method.to_s.delete_prefix("ransackable_")
if explicitly_defined?(method)
warn_deprecated <<~ERROR
Ransack's builtin `#{method}` method is deprecated and will result
in an error in the future. If you want to authorize the full list
of searchable #{list_type} for this model, use
`authorizable_#{method}` instead of delegating to `super`.
ERROR
public_send("authorizable_#{method}")
else
raise <<~MESSAGE
Ransack needs #{name} #{list_type} explicitly allowlisted as
searchable. Define a `#{method}` class method in your `#{name}`
model, watching out for items you DON'T want searchable (for
example, `encrypted_password`, `password_reset_token`, `owner` or
other sensitive information). You can use the following as a base:
```ruby
class #{name} < ApplicationRecord
# ...
def self.#{method}(auth_object = nil)
#{public_send("authorizable_#{method}").sort.inspect}
end
# ...
end
```
MESSAGE
end
end
パッと見た感じ、ここでエラーメッセージやら警告が定義してあるなぁというのがわかります
list_type = method.to_s.delete_prefix("ransackable_")
でlist_type
にattributes
を代入します
delete_prefix
は文字列の先頭から prefix を削除した文字列のコピーを返します
次にexplicitly_defined?(method)
でどんな仕組みで分岐されているのかを見に行きます
explicitly_defined?(method)
def explicitly_defined?(method)
definer_ancestor = singleton_class.ancestors.find do |ancestor|
ancestor.instance_methods(false).include?(method)
end
definer_ancestor != Ransack::Adapters::ActiveRecord::Base
end
ここが一番むずかったす
初見、一体これは何をしているんだ…という感じですが、ざっくりいうと
ユーザーがattributes
をカスタムしているかどうか?を判定してくれるのメソッドです
ユーザーがattributes
をカスタムしていればdefiner_ancestor != Ransack::Adapters::ActiveRecord::Base
でtrueが返り、していなければRansack::Adapters::ActiveRecord::Base
で定義されいる元々のattributes
とイコールになってしまうのでfalse
が返ります
※見たことないメソッドについての補足
singleton_class
singleton_class
メソッドとはオブジェクトのRubyの特異クラスを取得してくれるメソッドです
ancestors
モジュールやクラスの継承チェーンを配列で返してくれる
authorizable_ransackable_attributes
public_send("authorizable_#{method}")
ユーザーによってカスタムされていた場合最後はpublic_send
でauthorizable_ransackable_attributes
が呼び出されます
# Bare list of all potentially searchable attributes. Searchable attributes
# need to be explicitly allowlisted through the `ransackable_attributes`
# method in each model, but if you're allowing almost everything to be
# searched, this list can be used as a base for exclusions.
#
def authorizable_ransackable_attributes
if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
column_names + _ransackers.keys + _ransack_aliases.keys +
attribute_aliases.keys
else
column_names + _ransackers.keys + _ransack_aliases.keys
end.uniq
end
if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
でransackがサポートしているエイリアスかどうかを判定します
そしてようやく見覚えのあるスネークケースのリストが生成されます
感想
- 特異メソッドとか初めて聞きました。これもまた記事書くかも
- ちょっとした気持ちで見にいくと複雑で帰れなくなる
参考