has_many
*1にblockを渡すとassociationにメソッドを追加することができる。
class User < ActiveRecord::Base
has_many :posts do
def stats
group(:status).count
end
end
end
user.posts.stats
内部的にはblockがModule.new(&block)
されてextend
optionに渡される。なので始めからextend
optionとして渡してやってもよい。
module StatsCounter
def stats
group(:status).count
end
end
class User < ActiveRecord::Base
has_many :posts, extend: StatsCounter
end
user.posts.stats
ちなみにblockを渡した際にnew
されたModule
はActiveRecord::Associations::Builder.define_extensions
によってUser
classにUser::PostsAssociationExtension
のような名前でconst_set
される*2。
使いどころを考える
子テーブルの絞り込みを行うfinderを書くなら子テーブルのほうのモデルのscope
でよいし、何を書いても子テーブル側の詳細や知識が親テーブル側のモデルに漏れている感じがする。
親テーブルから辿ったときにしか使わせたくない、つまり対象となるassociationの一部としてのみ扱いたいロジックを書きたいときが使いどころといえそう。
公式ドキュメントではファクトリーメソッドのような例が挙げられている。
class Account < ActiveRecord::Base
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
person.last_name
うーん、上記の例でもPeople
classに書いたほうが良さそうな気もする
This article corresponds to the 7th day of ohbarye Advent Calendar 2020.