Rails: если has_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
end

Как написать условный оператор, чтобы увидеть, изменился ли @doc.articles после обновления @doc?

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

Приведенное выше дает мне ошибку. Я не могу найти правильный синтаксис.

Ответ 1

Вы должны проверить каждый. .changed? работает только на одной записи. Вы можете сделать что-то вроде этого, если вам нужно проверить всю ассоциацию хотя бы на одно изменение:

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

Или вы можете использовать Enumerable#any? :

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

Ответ 2

Немного поздно, но для других людей, которые ищут подобное решение, вы можете обнаружить изменения в отношениях (также 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-RELATION и before_remove_for_NAME-OF-RELATION запускаются при добавлении или удалении отношения. Запущенные функции (вы не можете связать функцию по имени, вы должны сделать это с помощью лямбда), добавьте идентификаторы добавленных/удаленных элементов отношения в хеш @articel_changes. Когда модель сохраняется, вы можете обрабатывать объекты своими идентификаторами в функции change_articles. После этого хеш @articel_changes будет очищен.

Ответ 3

Я нашел Enumerable # 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 

Благодарю.