【问题标题】:Rails: if has_many relationship changedRails:如果 has_many 关系发生变化
【发布时间】:2020-03-09 21:35:07
【问题描述】:

我有这两个课程。

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@doc.articles 是否发生了变化?

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

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

【问题讨论】:

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


    【解决方案1】:

    您必须检查每一项。 .changed? 仅适用于单个记录。如果您需要检查整个关联是否有至少一项更改,您可以这样做:

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

    或者你可以使用Enumerable#any?:

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

    【讨论】:

    • 将has_many添加到列表中的情况如何?这是一个变化,但会以这种方式被检测到吗?
    • 有没有办法检测@person.task_ids_changed?。我想检测 _ids 数组是否发生变化。
    • 也可以写成@doc.articles.any?(&amp;:changed?)
    • 这似乎不适用于添加或删除关联对象的情况。
    • 如果您还想说明添加或删除关联对象的时间,您可以使用@doc.articles.any?(&amp;:changed?) || @doc.articles.collect(&amp;:id).sort != @doc.articles.pluck(&amp;:id).sort。这是因为collect 将从内存关联中提取 id(包括数组中的 nil 项以用于尚未保存的关联,并从数组中排除 id 用于待删除的关联),而@987654329 @ 从数据库中提取 id(相反,不包括尚未保存的关联 id,包括待删除的关联 id)。
    【解决方案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
    

    【讨论】:

    【解决方案3】:

    有点晚了,但是对于正在寻找类似解决方案的其他人,您可以通过这种方式检测关系(也有 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 哈希将被清除。

    【讨论】:

    • 考虑到在 Rails 中你有 before_add, after_add, before_remove, after_remove 可以在关联上定义的回调,这似乎过于复杂。
    • @dft 这在正常关系上是正确的,但在 has_and_belongs_to_many 关系上是正确的。 has_and_belongs_to_many 关系表没有主键,只包含两个 id。因此,即使您从钩子中获得触发器,在更改过程中从目标对象中获取 id 也是非常棘手的。另一种解决方案是创建一个中间表和一个中间模型。但比你有一个经典的 1:n 关系。
    • 这是救命稻草
    【解决方案4】:

    我发现 Enumerable#any? 有助于通过以下方式按 id 进行反向排序的中间步骤:

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

    该排序步骤有助于 any? 提前返回,而不是遍历旧的 article 记录来查找是否进行了任何更改。

    例如就我而言,我有一个 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 
    

    谢谢。

    【讨论】:

      【解决方案5】:

      @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
      

      【讨论】:

        猜你喜欢
        • 2012-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-20
        相关资源
        最近更新 更多