こんにちは!kossyです!
諸事情ありブログを書かずにおりました、、、
月8 ~ 10本ペースは守りたいと思いつつも、なかなか時間の確保が厳しいですね、、、(根性が足りないと言われればその通りなのですが)
今回は、RailsのActiveRecordを使ったモデルに定義されたassociation情報を全て取得する reflect_on_all_associations のソースコードを読んでみたので、
備忘録としてブログに残してみたいと思います。
reflect_on_all_associations
まずは適当な箇所にbinding.pryを仕込んで、stepメソッドで処理の内部を見てみます。
From: /usr/local/bundle/gems/activerecord-6.0.4.1/lib/active_record/reflection.rb:104 ActiveRecord::Reflection::ClassMethods#reflect_on_all_associations: 103: def reflect_on_all_associations(macro = nil) => 104: association_reflections = reflections.values 105: association_reflections.select! { |reflection| reflection.macro == macro } if macro 106: association_reflections 107: end [1] pry(User)>
定義元はこちらですね。
いろいろ実行する前に、コメントアウト部分を読んでみます。
Returns an array of AssociationReflection objects for all the associations in the class.
If you only want to reflect on a certain association type, pass in the symbol :has_many, :has_one, :belongs_to as the first parameter.
クラス内のすべての関連付けのAssociationReflectionオブジェクトの配列を返します。
特定の関連付けタイプのみを反映する場合は、最初のパラメーターとしてシンボル:has_many、:has_one、:belongs_toを渡します。
出典: https://github.com/rails/rails/blob/main/activerecord/lib/active_record/reflection.rb
返り値は配列になるとのこと。一旦exitで抜けて、メソッドの中に入らずに返り値を確認してみます。
$ User.reflect_on_all_associations => [#<ActiveRecord::Reflection::HasManyReflection:0x000055b2c512eaa8 @active_record= User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, unique_session_id: string, created_at: datetime, updated_at: datetime), @constructable=true, @foreign_type=nil, @klass=nil, @name=:posts, @options={:dependent=>:destroy}, @plural_name="posts", @scope=nil, @type=nil>] $ User.reflect_on_all_associations.class => Array $ User.reflect_on_all_associations.size => 1 $ User.reflect_on_all_associations.first.class => ActiveRecord::Reflection::HasManyReflection
返り値は配列になっていて、ActiveRecord::Reflection::HasManyReflectionクラスのオブジェクトが配列の中身に格納されていました。
ソースコードを追ってみる
ここからはもう一度stepメソッドを使ってメソッドの中身を追ってみます。
From: /usr/local/bundle/gems/activerecord-6.0.4.1/lib/active_record/reflection.rb:104 ActiveRecord::Reflection::ClassMethods#reflect_on_all_associations: 103: def reflect_on_all_associations(macro = nil) => 104: association_reflections = reflections.values 105: association_reflections.select! { |reflection| reflection.macro == macro } if macro 106: association_reflections 107: end # 引数を渡して実行してないため、nilが返る > macro => nil # hashが返り値になっている > reflections => {"posts"=> #<ActiveRecord::Reflection::HasManyReflection:0x000055b2c512eaa8 @active_record= User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, unique_session_id: string, created_at: datetime, updated_at: datetime), @constructable=true, @foreign_type=nil, @klass=nil, @name=:posts, @options={:dependent=>:destroy}, @plural_name="posts", @scope=nil, @type=nil>} # posts keyの中身が配列で返っている > reflections.values => [#<ActiveRecord::Reflection::HasManyReflection:0x000055b2c512eaa8 @active_record= User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, unique_session_id: string, created_at: datetime, updated_at: datetime), @constructable=true, @foreign_type=nil, @klass=nil, @name=:posts, @options={:dependent=>:destroy}, @plural_name="posts", @scope=nil, @type=nil>]
一通り処理の内容は理解できました。reflectionsオブジェクトの中身をよしなにいじって、モデルに定義されたassociation情報を返している感じですね。
応用的な使い方
以下のように書くと、そのモデルに定義されているassociation名の配列を得ることができます。
$ User.reflect_on_all_associations.map(&:name) => [:posts]
例えば、has_manyなレコードが存在する場合に何かしらの処理を加えたい、という場合に以下のように書くことができます。
has_many_association_names = User.reflect_on_all_associations(:has_many).map(&:name) if has_many_association_names.any? { |name| user.public_send(name).exists? } # 何かしらの処理 else # 何かしらの処理 end
こう書くことで、新たに関連を追加した時に、わざわざ定数にsymbolを追加しなくてもよくなり、コードの変更箇所を減らすことができます。
勉強になりました。