在记录更新时访问关联的先前值

时间:2012-02-25 16:26:10

标签: ruby-on-rails ruby-on-rails-3 ruby-on-rails-3.1

我有一个“事件”模型,有很多“邀请函”。通过活动表单上的复选框设置邀请。更新事件时,我想将更新前的邀请与更新后的邀请进行比较。我想将此作为事件验证的一部分。

我的问题是我似乎无法在任何模型回调或验证中访问旧邀请。事务已经开始了,由于邀请不是事件模型的属性,我不能使用_was来获取旧值。

我想过尝试使用“after_initialize”回调来自己存储它。这些回调似乎不尊重“:on”选项,但我不能这样做:on:update。我不希望每次初始化对象时都运行它。

有没有更好的方法解决这个问题?

以下是我的更新控制器中的代码:

  def update
  params[:event][:invited_user_ids] ||= []
  if @event.update_attributes(params[:event])
    redirect_to @event
  else
    render action: "edit"
  end
  end

我的主要目标是制作它,以便您可以将用户添加到活动中,但不能删除用户。我想验证发布的invite_user_ids是否包含当前被邀请的所有用户。

- 更新
作为临时解决方案,我在:has_many关联上使用了:before_remove选项。我将其设置为抛出ActiveRecord :: RollBack异常,以防止用户不被邀请。不完全是我想要的,因为我无法显示验证错误但它确实阻止了它。

谢谢你, Corsen

1 个答案:

答案 0 :(得分:3)

你能使用ActiveModel::Dirty吗?像这样:

def Event < ActiveRecord::Base
  validates :no_invitees_removed

  def no_invitees_removed
    if invitees.changed? && (invitees - invitees_was).present?
      # ... add an error or re-add the missing invitees
    end
  end
end

编辑:我没有注意到OP已经打折ActiveModel::Dirty,因为它不适用于关联。我的坏。

另一种可能性是覆盖invited_user_ids=方法将现有用户ID附加到给定数组:

class Event < ActiveRecord::Base
  # ...

  def invited_user_ids_with_guard=(ids)
    self.invited_user_ids_without_guard = self.invited_user_ids.concat(ids).uniq
  end
  alias_method_chain :invited_user_ids=, :guard
end

这仍然适用于您,因为update_attributes最终会调用单个attribute=方法。


修改:@corsen在评论中询问为什么我在此示例中使用了alias_method_chain而不是super

调用super仅在覆盖继承链中进一步定义的方法时才有效。在模块中混合或从另一个类继承提供了执行此操作的方法。该模块或类不直接“添加”方法到派生类。相反,它将自己插入该类的继承链中。然后你可以在不破坏方法的原始定义的情况下重新定义派生类中的方法(因为它们仍然在超类/模块中)。

在这种情况下,invited_user_ids的任何祖先都没有定义Event。它是通过元编程直接在Event类上定义的,作为ActiveRecord的一部分。在super内调用invited_user_ids将导致NoMethodError,因为它没有超类定义,并且重新定义方法会丢失其原始定义。所以alias_method_chain实际上是实现super的最简单方法 - 就像在这种情况下的行为一样。

有时alias_method_chain过度杀戮并污染您的命名空间,并且很难跟踪堆栈跟踪。但有时它是改变方法行为而不会丢失原始行为的最佳方法。你只需了解差异就可以知道哪个是合适的。