覚書:Reducing Leaky Abstractions Introduced by ActiveRecord
Reducing Leaky Abstractions Introduced by ActiveRecord(ActiveRecordがもたらす抽象化の漏れを軽減する)を写経した際の覚書です。
ruby 3.1.0p0
Rails 7.0.2.3
The Setup
rails new reducing_leaky_abstractions
cd reducing_leaky_abstractions
bin/rails g model person name
bin/rails g model post body:text person:belongs_to published:boolean
bin/rails db:migrate
モデルをフィクスチャでロードしたいので、Postモデルのauthorにforeign_keyを追加する。
class Person < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :author, class_name: "Person", foreign_key: "person_id"
end
test/fixtures/people.yml
Personのフィクスチャを区別できるようにnameを変える。
one:
name: person 1
two:
name: person 2
test/fixtures/posts.yml
Postのフィクスチャを区別できるようにbodyを変える。
personをauthorに変更する。
クエリーに一致するようにpublishedをtrueにする。
distinctの有無が影響するようにtwoのauthorをoneにする。
one:
body: post 1
author: one
published: true
two:
body: post 2
author: one
published: true
bin/rails db:fixtures:load
bin/rails c
@newest_posts = Post.where(published: true).order(created_at: :desc).limit(10)
@published_authors = Person.distinct.joins(:posts).where(posts: { published: true })
The Pain
A new feature comes in where teammates want to enqueue posts to be published in the future.
チームメイトが将来公開する投稿をキューに入れたい、という新機能が追加された。
Shotgun Surgery(変更の分散)
何らかの修正を加える際、多くの異なるクラスに対して、多くの小さな変更を加える必要がある。
スキーマを変更する。
bin/rails g migration AddPublishedAtToPosts published_at:datetime
bin/rails g migration RemovePublishedFromPosts published:boolean
bin/rails db:migrate
test/fixtures/people.yml
Personモデルを5つ用意する。
# person_0 .. person_4
<% 5.times do |n| %>
person_<%= n %>:
name: <%= "person #{n}" %>
<% end %>
test/fixtures/posts.yml
publishedをpublished_atに変更する。
person_0とperson_1がpostを持つ。
person_0は2件中1件、published_atに現在時刻を設定する。
person_1は5件すべて、published_atに現在時刻を設定する。
# person_0のpost
post_0_0:
body: post 0 0
author: person_0
published_at: nil
post_0_1:
body: post 0 1
author: person_0
published_at: <%= Time.current %>
post_0_2:
body: post 0 2
author: person_0
published_at: <%= Time.current %>
# person_1のpost
<% 5.times do |n| %>
post_1_<%= n %>:
body: <%= "post 1 #{n}" %>
author: person_1
published_at: <%= Time.current %>
<% end %>
# person_2以降のpostは無し
The Underlying Issue
leaky abstraction
抽象的な表現が漏れる法則
The Suggested Fix
Using Named Scopes Across Models with ActiveRecord#Merge
class Post < ApplicationRecord
# other methods
def self.published
where("published_at < ?", Time.current)
end
end
bin/rails db:fixtures:load
bin/rails c
@newest_posts = Post.published.order(created_at: :desc).limit(10)
@newest_posts.count # => 7
@published_authors = Person.distinct.joins(:posts).merge(Post.published)
@published_authors.count # => 2
Caveats and Considerations
以上です。
この記事が気に入ったらサポートをしてみませんか?