Rails HABTM after_add 回调在保存主要对象之前触发
Rails HABTM after_add callback fires before saving primary object
我有两个 ActiveRecord 模型,彼此之间存在 HABTM 关系。
当我通过允许通过选中复选框添加区域的表单添加 AccessUnit 时,我得到一个异常,即 AccessUnitUpdaterJob 无法入队,因为传递的访问单元无法序列化(由于缺少标识符) .在主对象上手动调用保存时,问题已解决,但这当然是一种解决方法,而不是正确的修复方法。
TLDR; after_add 回调似乎是在保存主对象之前触发的。我实际上不确定这是 Rails 中的错误还是预期的行为。我正在使用 Rails 5.
我遇到的确切错误是:
ActiveJob::SerializationError in AccessUnitsController#create
Unable to serialize AccessUnit without an id. (Maybe you forgot to call save?)
下面是一些代码,您可以查看问题的上下文:
class AccessUnit < ApplicationRecord
has_and_belongs_to_many :zones, after_add: :schedule_access_unit_update_after_zone_added_or_removed, after_remove: :schedule_access_unit_update_after_zone_added_or_removed
def schedule_access_unit_update_after_zone_added_or_removed(zone)
# self.save adding this line solves it but isn't a proper solution
puts "Access unit #{name} added or removed to zone #{zone.name}"
# error is thrown on this line
AccessUnitUpdaterJob.perform_later self
end
end
class Zone < ApplicationRecord
has_and_belongs_to_many :access_units
end
在我看来这不是错误。每件事都按预期工作。在保存此图形之前,您可以创建对象的复杂图形。在此创建阶段,您可以将对象添加到关联中。这是您想要触发此回调的时间点,因为它说 after_add
而不是 after_save
.
例如:
@post.tags.build name: "ruby" # <= now you add the objects
@post.tags.build name: "rails" # <= now you add the objects
@post.save! # <= now it is to late, for this callback, you added already multiple objects
也许使用 before_add
回调更有意义:
class Post
has_many :tags, before_add: :check_state
def check_state(_tag)
if self.published?
raise CantAddFurthorTags, "Can't add tags to a published Post"
end
end
end
@post = Post.new
@post.tags.build name: "ruby"
@post.published = true
@post.tags.build name: "rails" # <= you wan't to fire the before_add callback now, to know that you can't add this new object
@post.save! # <= and not here, where you can't determine which object caused the error
你可以在书中读到一些关于这些回调的内容"The Rails 4 Way"
在你的情况下,你必须重新考虑你的逻辑。也许您可以使用 after_save
回调。
我的 2 美分:您考虑从回调切换到服务对象。
回调不是没有代价的。它们并不总是易于调试和测试。
我有两个 ActiveRecord 模型,彼此之间存在 HABTM 关系。 当我通过允许通过选中复选框添加区域的表单添加 AccessUnit 时,我得到一个异常,即 AccessUnitUpdaterJob 无法入队,因为传递的访问单元无法序列化(由于缺少标识符) .在主对象上手动调用保存时,问题已解决,但这当然是一种解决方法,而不是正确的修复方法。
TLDR; after_add 回调似乎是在保存主对象之前触发的。我实际上不确定这是 Rails 中的错误还是预期的行为。我正在使用 Rails 5.
我遇到的确切错误是:
ActiveJob::SerializationError in AccessUnitsController#create
Unable to serialize AccessUnit without an id. (Maybe you forgot to call save?)
下面是一些代码,您可以查看问题的上下文:
class AccessUnit < ApplicationRecord
has_and_belongs_to_many :zones, after_add: :schedule_access_unit_update_after_zone_added_or_removed, after_remove: :schedule_access_unit_update_after_zone_added_or_removed
def schedule_access_unit_update_after_zone_added_or_removed(zone)
# self.save adding this line solves it but isn't a proper solution
puts "Access unit #{name} added or removed to zone #{zone.name}"
# error is thrown on this line
AccessUnitUpdaterJob.perform_later self
end
end
class Zone < ApplicationRecord
has_and_belongs_to_many :access_units
end
在我看来这不是错误。每件事都按预期工作。在保存此图形之前,您可以创建对象的复杂图形。在此创建阶段,您可以将对象添加到关联中。这是您想要触发此回调的时间点,因为它说 after_add
而不是 after_save
.
例如:
@post.tags.build name: "ruby" # <= now you add the objects
@post.tags.build name: "rails" # <= now you add the objects
@post.save! # <= now it is to late, for this callback, you added already multiple objects
也许使用 before_add
回调更有意义:
class Post
has_many :tags, before_add: :check_state
def check_state(_tag)
if self.published?
raise CantAddFurthorTags, "Can't add tags to a published Post"
end
end
end
@post = Post.new
@post.tags.build name: "ruby"
@post.published = true
@post.tags.build name: "rails" # <= you wan't to fire the before_add callback now, to know that you can't add this new object
@post.save! # <= and not here, where you can't determine which object caused the error
你可以在书中读到一些关于这些回调的内容"The Rails 4 Way"
在你的情况下,你必须重新考虑你的逻辑。也许您可以使用 after_save
回调。
我的 2 美分:您考虑从回调切换到服务对象。
回调不是没有代价的。它们并不总是易于调试和测试。