跳至内容 跳至搜索

Active Record 自动保存关联

AutosaveAssociation 是一个模块,负责在父记录保存时自动保存关联记录。除了保存之外,它还会销毁所有被标记为销毁的关联记录。(请参阅 mark_for_destructionmarked_for_destruction?)。

父记录、其关联的保存以及被标记关联的销毁,都发生在事务内部。这应该永远不会导致数据库处于不一致的状态。

如果任何关联的验证失败,其错误消息将被应用到父记录上。

请注意,这也意味着被标记为销毁的关联不会被立即销毁。但是,它们仍然会被标记为销毁。

请注意,autosave: false 与不声明 :autosave 不同。当 :autosave 选项不存在时,新关联记录会被保存,但更新的关联记录不会被保存。

验证

子记录会被验证,除非 :validate 被设置为 false

回调

带有 autosave 选项的关联会在你的模型上定义几个回调(around_save、before_save、after_create、after_update)。请注意,回调的执行顺序与它们在模型中定义的顺序一致。你应该避免在 autosave 回调执行之前修改关联内容。将你的回调放在关联之后通常是最佳实践。

一对一示例

class Post < ActiveRecord::Base
  has_one :author, autosave: true
end

现在可以自动原子地执行父记录及其关联模型的更改保存了

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"

作为父记录保存操作的一部分销毁关联模型,就像标记它销毁一样简单

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

现在它已经从数据库中移除了

Author.find_by(id: id).nil? # => true

一对多示例

:autosave 未声明时,新子记录会在其父记录保存时被保存

class Post < ActiveRecord::Base
  has_many :comments # :autosave option is not declared
end

post = Post.new(title: 'ruby rocks')
post.comments.build(body: 'hello world')
post.save # => saves both post and comment

post = Post.create(title: 'ruby rocks')
post.comments.build(body: 'hello world')
post.save # => saves both post and comment

post = Post.create(title: 'ruby rocks')
comment = post.comments.create(body: 'hello world')
comment.body = 'hi everyone'
post.save # => saves post, but not comment

:autosave 为 true 时,所有子记录都会被保存,无论它们是否是新记录

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 # => saves post and both comments.

作为父记录保存操作的一部分销毁关联模型之一,就像标记它销毁一样简单

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

现在它已经从数据库中移除了

Comment.find_by(id: id).nil? # => true

注意事项

请注意,autosave 仅在已持久化的关联记录本身已更改时才会触发。这是为了防止由循环关联验证引起的 SystemStackError。唯一例外是当使用自定义验证上下文时,在这种情况下,验证将始终在关联记录上触发。

方法
A
C
D
M
R
V

实例公共方法

autosaving_belongs_to_for?(association)

# File activerecord/lib/active_record/autosave_association.rb, line 284
def autosaving_belongs_to_for?(association)
  @autosaving_belongs_to_for ||= {}
  @autosaving_belongs_to_for[association]
end

changed_for_autosave?()

返回此记录是否已以任何方式更改(包括其任何嵌套的 autosave 关联是否同样已更改)。

# File activerecord/lib/active_record/autosave_association.rb, line 275
def changed_for_autosave?
  new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave?
end

destroyed_by_association()

返回正在销毁的父记录的关联。

用于避免不必要地更新计数器缓存。

# File activerecord/lib/active_record/autosave_association.rb, line 269
def destroyed_by_association
  @destroyed_by_association
end

destroyed_by_association=(reflection)

记录正在被销毁的关联,并且在此过程中销毁此记录。

# File activerecord/lib/active_record/autosave_association.rb, line 262
def destroyed_by_association=(reflection)
  @destroyed_by_association = reflection
end

mark_for_destruction()

标记此记录将在父记录的保存事务中被销毁。这不会立即实际销毁记录,而是子记录将在调用 parent.save 时被销毁。

仅当父记录上对此关联模型启用了 :autosave 选项时才有用。

# File activerecord/lib/active_record/autosave_association.rb, line 249
def mark_for_destruction
  @marked_for_destruction = true
end

marked_for_destruction?()

返回此记录是否将在父记录的保存事务中被销毁。

仅当父记录上对此关联模型启用了 :autosave 选项时才有用。

# File activerecord/lib/active_record/autosave_association.rb, line 256
def marked_for_destruction?
  @marked_for_destruction
end

reload(options = nil)

像往常一样重新加载对象的属性,并清除 marked_for_destruction 标志。

# File activerecord/lib/active_record/autosave_association.rb, line 238
def reload(options = nil)
  @marked_for_destruction = false
  @destroyed_by_association = nil
  super
end

validating_belongs_to_for?(association)

# File activerecord/lib/active_record/autosave_association.rb, line 279
def validating_belongs_to_for?(association)
  @validating_belongs_to_for ||= {}
  @validating_belongs_to_for[association]
end