【railsソースコード】has_manyを深掘る
Railsのversion: 7.1.3
はじめにまとめ
-
has_many
メソッドの定義場所:activerecord/lib/active_record/associations.rb
-
Association
の孫クラスを作成して、それをreflection
として記録している
きっかけ
「Railsって、〇〇って書けば✕✕って動くんでしょ」 はなんとなくわかってきたけど、
「どういう仕組みでそう動くのか」 についても知ってみたくなったため
(has_manyを選んだのは、最近たまたま業務でさわっていたから)
見ていく
見ていくこと
-
has_many
メソッドはどこに置かれているのか? -
has_many
メソッドのコードがしていること
※もし間違っているところなどありましたら、ご指摘・ご意見いただけますとありがたいです
has_many
メソッドはどこに置かれているのか?
1.has_many
メソッドが定義されているファイル: activerecord/lib/active_record/associations.rb
そのファイル内でのhas_many
メソッドの位置
module ActiveRecord
(略)
module Associations # :nodoc:
extend ActiveSupport::Autoload
extend ActiveSupport::Concern
(略)
private
(略)
module ClassMethods
def has_many(name, scope = nil, **options, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
(略)
わかること
-
has_many
メソッドはActiveRecord
モジュールをはじめ複数のモジュールの中で定義されている
理由がわからなかったこと
-
has_many
メソッドが定義されているmodule ClassMethods
が、なぜprivate
キーワードの下にあるのか
has_many
メソッドのコードがしていること
2. 改めて、has_many
メソッドのコードはこちら
def has_many(name, scope = nil, **options, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
1行目Builder::HasMany
クラスのインスタンスを作成し、それを変数reflection
に代入している
Builder::HasMany
クラスとは?
ここで定義されているクラスらしい
# frozen_string_literal: true
module ActiveRecord::Associations::Builder # :nodoc:
class HasMany < CollectionAssociation # :nodoc:
(略)
end
end
class HasMany < CollectionAssociation
とあり、CollectionAssociation
を継承している。CollectionAssociation
を見に行ってみる
module ActiveRecord
module Associations
# = Active Record Association Collection
#
# CollectionAssociation is an abstract class that provides common stuff to
# ease the implementation of association proxies that represent
# collections. See the class hierarchy in Association.
#
# CollectionAssociation:
# HasManyAssociation => has_many
# HasManyThroughAssociation + ThroughAssociation => has_many :through
#
# The CollectionAssociation class provides common methods to the collections
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
# the <tt>:through association</tt> option.
(略)
class CollectionAssociation < Association # :nodoc:
(略)
コメントを読むと、このクラスはhas_many
とhas_many :through
の関連付けが行われたときに利用されるらしい
上記コードブロックの末尾にclass CollectionAssociation < Association
とあるので、次はAssociation
を見に行ってみる
module ActiveRecord
module Associations
# = Active Record Associations
#
# This is the root class of all associations ('+ Foo' signifies an included module Foo):
#
# Association
# SingularAssociation
# HasOneAssociation + ForeignAssociation
# HasOneThroughAssociation + ThroughAssociation
# BelongsToAssociation
# BelongsToPolymorphicAssociation
# CollectionAssociation
# HasManyAssociation + ForeignAssociation
# HasManyThroughAssociation + ThroughAssociation
#
# Associations in Active Record are middlemen between the object that
# holds the association, known as the <tt>owner</tt>, and the associated
# result set, known as the <tt>target</tt>. Association metadata is available in
# <tt>reflection</tt>, which is an instance of +ActiveRecord::Reflection::AssociationReflection+.
#
(略)
class Association # :nodoc:
attr_reader :owner, :target, :reflection, :disable_joins
delegate :options, to: :reflection
def initialize(owner, reflection)
reflection.check_validity!
@owner, @reflection = owner, reflection
@disable_joins = @reflection.options[:disable_joins] || false
reset
reset_scope
@skip_strict_loading = nil
end
(略)
...has_many
メソッドの1行目から深いところまで来てしまったのでこれ以上は深追いしません
1点だけ言及したいのは
-
has_many
メソッドの1行目reflection = Builder::HasMany.build(self, name, scope, options, &extension)
では、Association
クラスの孫クラスであるBuilder::HasMany
クラスが使われている
ということ
出発点のBuilder::hasMany
クラスではinitialize
メソッドがなく、どこでやってるんだろと気になっていたため見つかってスッキリした
Reflection.add_reflection self, name, reflection
2行目def has_many(name, scope = nil, **options, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
2行目のコードを見ていきます
add_reflection
というメソッドが実行されている。引数には先ほど作成した変数reflection
も含まれている
そもそも、この行に3度も出てきているreflection
とは何なのか?
情報工学においてリフレクション (reflection) とは、プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことを指す
https://ja.wikipedia.org/wiki/リフレクション_(情報工学)
copilotでは次の回答が来た
"reflection"は、プログラムが自身の構造や特性について情報を取得するための機能を指します。具体的には、クラスやオブジェクトのメソッドや属性、関連するクラスやモジュールなどに関する情報を取得することができます
話をコードに戻すと、Reflection.add_reflection self, name, reflection
では、has_manyが書かれたモデル(例: author)と引数に書かれたモデル(例: books)のAssociation
が記録されているということになる
長くなってきたので、最後にReflection
とadd_reflection
メソッドが記載されているところをあげて終わりにします
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
extend ActiveSupport::Concern
(略)
class << self
def add_reflection(ar, name, reflection)
ar.clear_reflections_cache
name = -name.to_s
ar._reflections = ar._reflections.except(name).merge!(name => reflection)
end
おさらい
-
has_many
メソッドの定義場所:activerecord/lib/active_record/associations.rb
-
has_many
メソッドはAssociation
クラスを作成して、それをreflection
として記録している - reflectionというプログラミングの基本概念みたいな言葉がある。意味は、プログラムが自身の構造や特性について情報を取得するための機能
よだん
コードリーディング記事を書くのはおすすめかもしれない。記事を書く、イコール人に読まれること前提なので、コードリーディングもひとりでのほほんをやるより集中できたから
参考資料
公式ドキュメント has_manyの部分: https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
Discussion