Rails:如果has_many关系发生了变化

时间:2013-05-24 03:11:42

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

我有这两个班。

class Article < ActiveRecord::Base
  attr_accessible :body, :issue, :name, :page, :image, :video, :brand_ids
  has_many :publications
  has_many :docs, :through => :publications
end

class Doc < ActiveRecord::Base
  attr_accessible :issue_id, :cover_id, :message, :article_ids, :user_id, :created_at, :updated_at, :issue_code, :title, :template_id
  has_many :publications, dependent: :destroy
  has_many :articles, :through => :publications, :order => 'publications.position'
  has_many :edits, dependent: :destroy
  accepts_nested_attributes_for :articles, allow_destroy: false
end

如何在更新@doc.articles后编写条件语句以查看@doc是否已更改?

if @doc.articles.changed?
  ...
end

上面给我一个错误。我找不到正确的语法。

5 个答案:

答案 0 :(得分:19)

你必须检查每一个。 .changed?仅适用于单个记录。如果您需要检查整个关联以进行至少一次更改,则可以执行此类操作:

if @doc.articles.find_index {|a| a.changed?} then...

或者您可以使用Enumerable#any?

if @doc.articles.any? {|a| a.changed?} then...

答案 1 :(得分:7)

有点晚了,但是对于正在寻找类似解决方案的其他人来说,你可以通过这种方式检测关系中的变化(也是has_and_belongs_to_many):

class Article < ActiveRecord::Base
  attr_accessible :body, :issue, :name, :page, :image, :video, :brand_ids
  has_many :publications
  has_many :docs, :through => :publications
end

class Doc < ActiveRecord::Base
  attr_accessible :issue_id, :cover_id, :message, :article_ids, :user_id, :created_at, :updated_at, :issue_code, :title, :template_id
  has_many :publications, dependent: :destroy
  has_many :articles, :through => :publications, :order => 'publications.position'
  has_many :edits, dependent: :destroy
  accepts_nested_attributes_for :articles, allow_destroy: false

  after_initialize :initialize_article_changes_safe

  after_save :change_articles
  after_save :initialize_article_changes

  before_add_for_articles << ->(method,owner,change) { owner.send(:on_add_article, change) }
  before_remove_for_articles << ->(method,owner,change) { owner.send(:on_remove_article, change) }

  def articles_changed?
    @article_changes[:removed].present? or @article_changes[:added].present?
  end

  private

  def on_add_article(article)
    initialize_article_changes_safe
    @article_changes[:added] << article.id
  end

  def on_remove_article(article)
    initialize_article_changes_safe
    @article_changes[:removed] << article.id
  end

  def initialize_article_changes
    @article_changes = {added: [], removed: []}
  end

  def initialize_article_changes_safe
    @article_changes = {added: [], removed: []} if @article_changes.nil?
  end

  def unchanged_article_ids
    self.article_ids - @article_changes[:added] - @article_changes[:removed]
  end

  def change_articles
    do_stuff if self.articles_changed?
    do_stuff_for_added_articles unless @article_changes[:added].nil?
    do_stuff_for_removed_articles unless @article_changes[:removed].nil?
  end
end

添加或删除关系时会触发两个挂钩before_add_for_NAME-OF-RELATIONbefore_remove_for_NAME-OF-RELATION。触发的函数(您不能通过名称链接函数,您必须通过lambda执行)将添加/删除的关系项的ID添加到@articel_changes哈希。保存模型后,您可以通过change_articles函数中的ID来处理对象。之后,@articel_changes哈希将被清除。

答案 2 :(得分:2)

我发现可枚举#any?有助于以下列方式按ID进行反向排序的中间步骤:

@doc.articles.sort { |a, b| b.id <=> a.id }.any?(&:changed?)

该排序步骤有助于任何?提前返回,而不是循环浏览较旧的文章记录,以查找是否进行了任何更改。

例如在我的情况下,我有一个has_many :event_responders关系,在向集合中添加新的EventResponder后,我验证了以上方式:

不使用中间排序

2.2.1 :019 > ep.event_responders.any? { |a| puts a.changes; a.changed? }
{}
{}
{}
{}
{"created_at"=>[Thu, 03 Sep 2015 08:25:59 UTC +00:00, Thu, 03 Sep 2015 08:25:59 UTC +00:00], "updated_at"=>[Thu, 03 Sep 2015 08:25:59 UTC +00:00, Thu, 03 Sep 2015 08:25:59 UTC +00:00]}
=> true 

使用中间排序

2.2.1 :020 > ep.event_responders.sort { |a, b| b.id <=> a.id }.any? { |a| puts a.changes; a.changed? }
{"created_at"=>[Thu, 03 Sep 2015 08:25:59 UTC +00:00, Thu, 03 Sep 2015 08:25:59 UTC +00:00], "updated_at"=>[Thu, 03 Sep 2015 08:25:59 UTC +00:00, Thu, 03 Sep 2015 08:25:59 UTC +00:00]}
=> true 

感谢。

答案 3 :(得分:2)

使用after_addafter_remove关联回调来检查添加/删除,同时使用saved_changes?来检查现有文章的任何更改。

class Doc < ActiveRecord::Base
  has_many :articles, after_add: :set_article_flag, after_remove: :set_article_flag

  after_save :do_something_with_changed_articles

  private

  def set_article_flag
    @articles_changed = true
  end

  def do_something_with_changed_articles
    if @articles_changed || articles.any?(&:saved_changes?)
      # do something
    end
  end
end

答案 4 :(得分:0)

@jangosteve's comment为基础,可以创建一个魔术方法来响应[association] _changed ?,无论其关联名称是什么:

# app/model/application_record.rb

def method_missing(method_name, *arguments, &block)
  if method_name.to_s =~ /(.*)_changed?/
    association = $1.to_sym
    send(association).any?(&:changed?) || send(association).collect(&:id).sort != send(association).pluck(:id).sort
  else
    super
  end
end

def respond_to_missing?(method_name, include_private = false)
  if method_name.to_s =~ /(.*)_changed?/
    association = $1.to_sym
    self.class.reflect_on_association(association).present?
  else
    super
  end
end