Rails: if has_many relationship changed

后端 未结 5 1502
借酒劲吻你
借酒劲吻你 2021-01-01 15:21

I\'ve got these two classes.

class Article < ActiveRecord::Base
  attr_accessible :body, :issue, :name, :page, :image, :video, :brand_ids
  has_many :pub         


        
相关标签:
5条回答
  • 2021-01-01 15:35

    You have to check each one. .changed? only works on a single record. You could do something like this if you need to check the whole association for at least one change:

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

    Or you can use Enumerable#any?:

    if @doc.articles.any? {|a| a.changed?} then...
    
    0 讨论(0)
  • 2021-01-01 15:35

    Building up on @jangosteve's comment, you can create a magic method that responds to [association]_changed?, whatever the association name:

    # 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
    
    0 讨论(0)
  • 2021-01-01 15:39

    Use the after_add and after_remove association callbacks to check for additions/removals along with using saved_changes? to check for any changes on existing articles.

    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
    
    0 讨论(0)
  • 2021-01-01 15:41

    A bit late, but for other people who are looking for a similar solution, you can detect changes in the Relationship (also has_and_belongs_to_many) on this way:

    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
    

    The two hooks before_add_for_NAME-OF-RELATION and before_remove_for_NAME-OF-RELATION are triggered when adding or removing a relation. The triggered functions (you can not link a function by name, you have to do it by lambda) add the ids of the added / removed relation items to the @articel_changes hash. When the model is saved, you can handle the objects by their ids in the change_articles function. After it, the @articel_changes hash will be cleared.

    0 讨论(0)
  • 2021-01-01 15:47

    I found Enumerable#any? helpful with an intermediate step of reverse sorting by id in following manner:

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

    That sort step helps any? to return early, instead of looping through older article records to find if any change was made.

    For e.g. in my case I had a has_many :event_responders relation and after adding a new EventResponder to the collection, I validated the above to be working in following manner:

    Without using an intermediate sort

    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 
    

    Using an intermediate sort

    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 
    

    Thanks.

    0 讨论(0)
提交回复
热议问题