跳至内容 跳至搜索

Active Record 自动保存关联

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

保存父记录、其关联和销毁标记的关联,所有操作都在一个事务中完成。这应该永远不会导致数据库处于不一致状态。

如果任何关联的验证失败,它们的错误消息将应用于父记录。

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

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

验证

除非 :validatefalse,否则子记录会被验证。

回调

具有 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