Railsのコードを読む アソシエーションについて

今回はActiveRecordのhas_manyやbelongs_toをモデルで呼び出したときにどのような処理が行われているのかを追ってみました。

まずは自作モデルでアソシエーションを定義

```ruby:models/user.rb
class User < ApplicationRecord
  has_one :profile
end
```

```ruby:models/profile.rb
class Profile < ApplicationRecord
  belongs_to :user
end
```

## アソシエーションの作成

Builder::HasOne.buildでアソシエーションに必要なコールバック、バリデーションの設定を行う。
リフレクションにはアソシエーションの情報が保存されている。

```ruby:activerecord/lib/active_record/association.rb
def has_one(name, scope = nil, options = {})
  reflection = Builder::HasOne.build(self, name, scope, options)
  Reflection.add_reflection self, name, reflection
end

# Build::HasOneの親子関係は以下の通り
self.ancestors
=> [ActiveRecord::Associations::Builder::HasOne,
 ActiveRecord::Associations::Builder::SingularAssociation,
 ActiveRecord::Associations::Builder::Association,
・・・
]
```

## リフレクションクラスにアソシエーションの情報を作成

define_extensionsから順に追っていきます。

```ruby:activerecord/lib/active_record/associations/builder/association.rb
def self.build(model, name, scope, options, &block)
  if model.dangerous_attribute_method?(name)
    raise ArgumentError, "エラーメッセージ"
  end

  extension = define_extensions model, name, &block
  reflection = create_reflection model, name, scope, options, extension
  define_accessors model, reflection
  define_callbacks model, reflection
  define_validations model, reflection
  reflection
end
```
### define_extensions
アソシエーションを定義する時にブロックを渡すと拡張を行える。
ただしhas_many has_many_and_belongs_toのみ
Personモデルに記述してオーバーライドすれば良いんじゃないかって思いましたが、AccountからPeopleを参照した時にだけ有効にしたいときに使えそう。
[参考サイト](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Association+extensions "参考サイト")

```ruby
class Account < ApplicationRecord
  has_many :people do
    def find_or_create_by_name(name)
      first_name, last_name = name.split(" ", 2)
      find_or_create_by(first_name: first_name, last_name: last_name)
    end
  end
end

person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name  # => "Heinemeier Hansson"
```
### create_reflection
create_reflectionではhas_many belongs_toなどに渡すオプションが正しいものかバリデーションチェックとscopeの設定を行い、Reflection.createを呼び出します。

```ruby:activerecord/lib/active_record/reflection.rb
def self.create(macro, name, scope, options, ar)
  klass = \
    case macro
    when :composed_of
      AggregateReflection
    when :has_many
      HasManyReflection
    when :has_one
      HasOneReflection
    when :belongs_to
      BelongsToReflection
    else
      raise "Unsupported Macro: #{macro}"
    end

  reflection = klass.new(name, scope, options, ar)
  options[:through] ? ThroughReflection.new(reflection) : reflection
end

# Reflectionの継承関係はこちら
[3] pry(ActiveRecord::Reflection)> klass.ancestors
=> [ActiveRecord::Reflection::HasOneReflection,
 ActiveRecord::Reflection::AssociationReflection,
 ActiveRecord::Reflection::MacroReflection,
 ActiveRecord::Reflection::AbstractReflection
・・・
]
```

上記の klass.new で下記initializeが呼ばれ各値がセットされる

```
# AggregateReflection < MacroReflection
def initialize(name, scope, options, active_record)
  super
  @automatic_inverse_of = nil
  @type         = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
  @foreign_type = options[:foreign_type] || "#{name}_type"
  @constructable = calculate_constructable(macro, options)
  @association_scope_cache = {}
  @scope_lock = Mutex.new
end

# class MacroReflection < AbstractReflection
def initialize(name, scope, options, active_record)
  @name          = name
  @scope         = scope
  @options       = options
  @active_record = active_record
  @klass         = options[:anonymous_class]
  @plural_name   = active_record.pluralize_table_names ?
                      name.to_s.pluralize : name.to_s
end
```

### define_accessors

UserとProfileが紐付いている時にUserのオブジェクトからprofileメソッドでアクセス、またProfileからuserメソッドでアクセス出来るようにprofile userメソッドを定義しているメソッドです。

```ruby:activerecord/lib/active_record/associations/builder/singular_association.rb
def self.define_accessors(model, reflection)
  super
  define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
end
```

generated_association_methodsでActiveRecord::CoreにincludeされるModuleが返却される。
define_readers define_writersで返却されたModuleに関連先にアクセスするためのメソッドが定義される。

```ruby:activerecord/lib/active_record/associations/builder/association.rb
def self.define_accessors(model, reflection)
  mixin = model.generated_association_methods
  name = reflection.name
  define_readers(mixin, name)
  define_writers(mixin, name)
end
```

singular_association.rb ではdefine_accessotrsといいつつもcreate_userやbuild_userなどの生成系メソッドもdefine_constructorsを呼び出しているため作成される。
reflection.constructable?は下記にて返却される値を参照しているため has_oneの場合にはthroughオプション、polymorphic関連のbelongs_to側では作成されない。
なぜhas_manyのthroughオプションはなぜ生成されるのだろうか。。。
中間テーブルのRefrection情報をどこかに持ってるのかな？

```ruby:activerecord/lib/active_record/reflection.rb
class HasOneReflection < AssociationReflection # :nodoc:
  # 省略
  private
    def calculate_constructable(macro, options)
      !options[:through]
    end
end

class BelongsToReflection < AssociationReflection # :nodoc:
  # 省略
  private
    def calculate_constructable(macro, options)
      !polymorphic?
    end
end
```

### define_callbacks

dependentオプションを設定したときには子レコードを削除するためのコールバックとextension.buildではautosaveのためのコールバックを設定している。
has_manyではbefore_add after_add before_remove after_removeを設定しているがhas_oneではなぜないのだろう。。。
まあ必要無いという判断もわからなくもないような気はする。

```ruby:activerecord/lib/active_record/associations/builder/association.rb
module ActiveRecord::Associations::Builder # :nodoc:
  class Association #:nodoc:
    # 省略
    def self.define_callbacks(model, reflection)
      if dependent = reflection.options[:dependent]
        check_dependent_options(dependent)
        add_destroy_callbacks(model, reflection)
      end

      Association.extensions.each do |extension|
        extension.build model, reflection
      end
    end

  end
end
```

has_many has_many_and_belongs_toのdefine_callbacks

```ruby:activerecord/lib/active_record/associations/builder/collection_association.rb
module ActiveRecord::Associations::Builder # :nodoc:
  class CollectionAssociation < Association #:nodoc:
	# 省略
    def self.define_callbacks(model, reflection)
      super
      name    = reflection.name
      options = reflection.options
      CALLBACKS.each { |callback_name|
        define_callback(model, callback_name, name, options)
      }
    end

  end
end
```

### define_validations
ここでは何もしていない

```
def self.define_validations(model, reflection)
  # noop
end
```

## ActiveRecordのオブジェクトにリフレクションを追加

```ruby:activerecord/lib/active_record/association.rb
def has_one(name, scope = nil, options = {})
  reflection = Builder::HasOne.build(self, name, scope, options)
  Reflection.add_reflection self, name, reflection
end
```

```ruby:activerecord/lib/active_record/reflection.rb
def self.add_reflection(ar, name, reflection)
  ar.clear_reflections_cache
  ar._reflections = ar._reflections.merge(name.to_s => reflection)
end
```

## まとめ

アソシエーションのクラスマクロを呼び出すと以下の事が行われる。

1. アソシエーション指定時に渡されたextensionの設定
2. リフレクションの作成と追加
3. アソシエーションのみに関連するコールバックの設定 (after_addなど after_saveは違うところ)
4. アソシエーションメソッドを定義 user.profile のprofileメソッドやuser.create_profile など

### 登場クラス

#### Association

```
# アソシエーションに関連するModuleのautoloadやアソシエーションのクラスマクロを定義
ActiveRecord::Associations
# アソシエーション定義するときにリフレクションの設定やコールバックの設定を行う
ActiveRecord::Associations::Builder::Association
	# belongs_toやhas_oneなど1対のアソシエーションを担当
	ActiveRecord::Associations::Builder::SingularAssociation < Association
		ActiveRecord::Associations::Builder::HasOne < SingularAssociation
	# has_manyなど多対のアソシエーションを担当
	ActiveRecord::Associations::Builder::CollectionAssociation < Association
		ActiveRecord::Associations::Builder::HasMany < CollectionAssociation
```

#### Reflection

```
# アソシエーション名 スコープ(lambda) オプション(class_name, throughなど) 外部キーの情報などを保持
ActiveRecord::Reflection::AbstractReflection
	ActiveRecord::Reflection::MacroReflection < AbstractReflection
		ActiveRecord::Reflection::AssociationReflection < MacroReflection
			ActiveRecord::Reflection::HasOneReflection < AssociationReflection
			ActiveRecord::Reflection::HasManyReflection < AssociationReflection
```

### 個人的に気に入ったコード

アソシエーションメソッドを定義するところでCoreモジュールからincludeした空のModuleを返却して、受け取ったAssociation側ではdefine_readers define_writerの中でclass_evalを使ってメソッドを追加させているところが個人的にお気に入り。
非常に拡張しやすい。

```
module ActiveRecord
  module Core
    def generated_association_methods
      @generated_association_methods ||= begin
        mod = const_set(:GeneratedAssociationMethods, Module.new)
        include mod
        mod
      end
    end

  end
end

module ActiveRecord::Associations::Builder # :nodoc:
  class Association #:nodoc:
    def self.define_accessors(model, reflection)
      mixin = model.generated_association_methods
      name = reflection.name
      define_readers(mixin, name)
      define_writers(mixin, name)
    end
  end
end
```

## 参考にしたサイト

http://woshidan.hatenadiary.jp/entry/2015/04/18/194612
http://callahan.io/blog/2014/10/08/behind-the-scenes-of-the-has-many-active-record-association/