Rails API: ActiveRecord::AutosaveAssociation(翻訳)
AutosaveAssociation
は、親がsave
されるときに、関連付けられているレコードも自動的にsave
されるようにするモジュールです。save
に加えて、mark_for_destruction
済みの関連付けレコードのdestroy
も行います(mark_for_destruction
およびmarked_for_destruction?
を参照)。
親とその関連付けのsave
、およびmark_for_destruction
済みの関連付けレコードのdestroy
は、すべて1個のトランザクション内で行われます。これにより、データベースの状態が不整合にならないようにしています。
関連付けでバリデーションが1個以上失敗すると、そのエラーメッセージは親に適用されます。
これは、「mark_for_destruction
済みの関連付けが直接destroy
されることはない」という意味でもある点にご注意ください。ただし、バリデーションが失敗したときの関連付けは引き続きmark_for_destruction
済みのままになります。
また、「autosave: false
を宣言する」ことと「:autosave
を宣言しない」ことは同じでない点にもご注意ください。:autosave
オプションが存在しない後者の場合、新規の関連付けはsave
されますが、更新された関連付けはsave
されません。
🔗 バリデーション
子レコードは、validate: false
を指定しない限りバリデーションされます。
🔗 コールバック
自動保存オプションを指定した関連付けでは、さまざまなコールバック(around_save
、before_save
、after_create
、after_update
)をモデルに定義します。コールバックは「モデルでの定義順」で実行されることにご注意ください。また、自動保存コールバックが実行される前に関連付けの内容を変更することは避けてください。通常であれば、関連付けより後にコールバックを配置することをおすすめします。
🔗 1対1の例
class Post < ActiveRecord::Base
has_one :author, autosave: true
end
上のように書くことで、親の変更とそれに関連付けられたモデルの変更が、自動的かつアトミックにsave
されるようになります。
post = Post.find(1)
post.title # => "The current global position of migrating ducks"
post.author.name # => "alloy"
post.title = "On the migration of ducks"
post.author.name = "Eloy Duran"
post.save
post.reload
post.title # => "On the migration of ducks"
post.author.name # => "Eloy Duran"
関連付けられたモデルを親のsave
操作の一環としてdestroy
するときは、以下のようにmark_for_destruction
でマーキングするだけで簡単に行なえます。
post.author.mark_for_destruction
post.author.marked_for_destruction? # => true
ただし、上の時点ではデータベースからまだ削除されていません。
id = post.author.id
Author.find_by(id: id).nil? # => false
post.save
post.reload.author # => nil
save
すると実際にデータベースから削除されます。
Author.find_by(id: id).nil? # => true
🔗 1対多の例
:autosave
オプションが宣言されていない場合、親がsave
されたときに子が新規レコードの場合にのみsave
されます。
class Post < ActiveRecord::Base
has_many :comments # :autosaveオプションを宣言していない
end
post = Post.new(title: 'ruby rocks')
post.comments.build(body: 'hello world')
post.save # => postもcommentもsaveされる
post = Post.create(title: 'ruby rocks')
post.comments.build(body: 'hello world')
post.save # => postもcommentもsaveされる
post = Post.create(title: 'ruby rocks')
comment = post.comments.create(body: 'hello world')
comment.body = 'hi everyone'
post.save # => postはsaveされるがcommentはsaveされない
:autosave: true
を指定すると、子は新規レコードかどうかにかかわらず、すべてsave
されます。
class Post < ActiveRecord::Base
has_many :comments, autosave: true
end
post = Post.create(title: 'ruby rocks')
comment = post.comments.create(body: 'hello world')
comment.body = 'hi everyone'
post.comments.build(body: "good morning.")
post.save # => postも2つのcommentもsaveされる
関連付けられたモデルの1つを親のsave
操作の一環としてdestroy
するときは、以下のようにmark_for_destruction
でマーキングするだけで簡単に行なえます。
post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
post.comments[1].mark_for_destruction
post.comments[1].marked_for_destruction? # => true
post.comments.length # => 2
ただし、上の時点ではデータベースからまだ削除されていません。
id = post.comments.last.id
Comment.find_by(id: id).nil? # => false
post.save
post.reload.comments.length # => 1
save
すると実際にデータベースから削除されます。
Comment.find_by(id: id).nil? # => true
🔗 注意
レコード自体が変更されたときに自動保存がトリガーされるのは、関連付けレコードが既に永続化済みの場合だけであることにご注意ください。その理由は、関連付けのバリデーションが循環することで引き起こされるSystemStackError
から保護するためです。これには1つ例外があり、カスタムのバリデーションコンテキストが利用されている場合は、関連付けされたレコードに対して常にバリデーションが実行されます。
🔗 メソッド
changed_for_autosave?
destroyed_by_association
destroyed_by_association=
marked_for_destruction?
reload
🔗 インスタンスpublicメソッド
🔗 changed_for_autosave?()
このレコードが何らかの方法で変更されたかどうかを返します(ネストした自動保存関連付けが同様に変更されたかどうかについても返します)。
🔗 destroyed_by_association()
destroyされる親の関連付けを返します1。
カウンタキャッシュが不必要に更新されるのを避けたいときに利用します。
🔗 destroyed_by_association=(reflection)
このレコードのdestroyをトリガーするのに使う特定の関連付けを指定します。指定した関連付けは、そのエンティティがdestroy
されたときに、このレコードをmark_for_destruction
でマーキングするのに使われます。
🔗 mark_for_destruction()
このレコードが、親のsave
トランザクションの一環としてdestroyされるようマーキングします。実際には、これによってレコードが即座にdestroyされるのではなく、親.save
が呼び出されたときに子レコードがdestroyされます。
このメソッドは、この関連付けられているモデルで親の:autosave
オプションが有効になっている場合にのみ有用です。
🔗 marked_for_destruction?()
このレコードが親のsave
トランザクションの一環としてdestroyされるかどうかを返します。
このメソッドは、この関連付けられているモデルで親の:autosave
オプションが有効になっている場合にのみ有用です。
🔗 reload(options = nil)
オブジェクトの属性を通常どおり再読み込みし、marked_for_destruction
フラグをクリアします。
関連記事
-
訳注: 具体的には、destroyされる親が存在する場合は、その親との関連付けを返します。destroyされる親がない場合は
nil
を返します。 ↩
概要
MITライセンスに基づいて翻訳・公開いたします。
ActiveRecord::AutosaveAssociation
訳文には適宜強調を加えています。