How to update counter_cache when updating a model?

I have a simple connection:

class Item
  belongs_to :container, :counter_cache => true
end

class Container
  has_many :items
end

Say I have two containers. I create an element and bind it to the first container. The counter is incremented.

Then I decided to associate it with another container. How to update items_count column of both containers?

I found a possible solution at http://railsforum.com/viewtopic.php?id=39285 .. however I am a newbie and I do not understand this. Is this the only way to do this?

+5
source share
8 answers

It should work automatically. When you update items.container_id, it will decrease the counter of the old container and increase it. But if this does not work, then it is strange. You can try this callback:

class Item
  belongs_to :container, :counter_cache => true
  before_save :update_counters

  private
  def update_counters
    new_container = Container.find self.container_id
    old_container = Container.find self.container_id_was
    new_container.increament(:items_count)
    old_container.decreament(:items_count)
  end
end

UPD

:

container1 = Container.create :title => "container 1"
#=> #<Container title: "container 1", :items_count: nil>
container2 = Container.create :title => "container 2"
#=> #<Container title: "container 2", :items_count: nil>
item = container1.items.create(:title => "item 1")
Container.first
#=> #<Container title: "container 1", :items_count: 1>
Container.last
#=> #<Container title: "container 1", :items_count: nil>
item.container = Container.last
item.save
Container.first
#=> #<Container title: "container 1", :items_count: 0>
Container.last
#=> #<Container title: "container 1", :items_count: 1>

, - . .

+3

, - ( after_update :fix_updated_counter counter_cache)

module FixUpdateCounters

  def fix_updated_counters
    self.changes.each { |key, (old_value, new_value)|
      # key should match /master_files_id/ or /bibls_id/
      # value should be an array ['old value', 'new value']
      if key =~ /_id/
        changed_class = key.sub /_id$/, ''
        association   = self.association changed_class.to_sym

        case option = association.options[ :counter_cache ]
        when TrueClass
          counter_name = "#{self.class.name.tableize}_count"
        when Symbol
          counter_name = option.to_s
        end

        next unless counter_name

        association.klass.decrement_counter(counter_name, old_value) if old_value
        association.klass.increment_counter(counter_name, new_value) if new_value
      end
    }   end end

ActiveRecord::Base.send(:include, FixUpdateCounters)
+3

3.1. 3.1 . .

  private
    def update_counters
      new_container = Container.find self.container_id
      Container.increment_counter(:items_count, new_container)
      if self.container_id_was.present?
        old_container = Container.find self.container_id_was
        Container.decrement_counter(:items_count, old_container)
      end
    end
+2

,

class Item < ActiveRecord::Base

    after_update :update_items_counts, if: Proc.new { |item| item.collection_id_changed? }

private

    # update the counter_cache column on the changed collections
    def update_items_counts

        self.collection_id_change.each do |id|
            Collection.reset_counters id, :items
        end

    end

end

http://api.rubyonrails.org/classes/ActiveModel/Dirty.html http://railscasts.com/episodes/109-tracking-attribute-changes reset_counters http://apidock.com/rails/v3.2.8/ActiveRecord/CounterCache/reset_counters

+2

@fl00r

class Container
  has_many :items_count
end

class Item
  belongs_to :container, :counter_cache => true
  after_update :update_counters

  private

 def update_counters
   if container_id_changed?
     Container.increment_counter(:items_count, container_id)
     Container.decrement_counter(:items_count, container_id_was)
   end

   # other counters if any
   ...
   ...

 end

end
+1

(Rails 3.2.3). , , . ActiveRecord:: Base after_update, counter_caches.

ActiveRecord:: Base

lib/fix_counters_update.rb :

module FixUpdateCounters

  def fix_updated_counters
    self.changes.each {|key, value|
      # key should match /master_files_id/ or /bibls_id/
      # value should be an array ['old value', 'new value']
      if key =~ /_id/
        changed_class = key.sub(/_id/, '')
        changed_class.camelcase.constantize.decrement_counter(:"#{self.class.name.underscore.pluralize}_count", value[0]) unless value[0] == nil
        changed_class.camelcase.constantize.increment_counter(:"#{self.class.name.underscore.pluralize}_count", value[1]) unless value[1] == nil
      end
    }
  end 
end

ActiveRecord::Base.send(:include, FixUpdateCounters)

ActiveModel:: Dirty changes, , , . , , (.. /_id/), , decrement_counter / increment_counter. nil , .

config/initializers/active_record_extensions.rb :

require 'fix_update_counters'

, , :

class Comment < ActiveRecord::Base
  after_update :fix_updated_counters
  ....
end
+1

@Curley .

module FixUpdateCounters

  def fix_updated_counters
    self.changes.each {|key, value|
      # key should match /master_files_id/ or /bibls_id/
      # value should be an array ['old value', 'new value']
      if key =~ /_id/
        changed_class = key.sub(/_id/, '')

        # Get real class of changed attribute, so work both with namespaced/normal models
        klass = self.association(changed_class.to_sym).klass

        # Namespaced model return a slash, split it.
        unless (counter_name = "#{self.class.name.underscore.pluralize.split("/")[1]}_count".to_sym)
          counter_name = "#{self.class.name.underscore.pluralize}_count".to_sym
        end

        klass.decrement_counter(counter_name, value[0]) unless value[0] == nil
        klass.increment_counter(counter_name, value[1]) unless value[1] == nil
      end
    }
  end 
end

ActiveRecord::Base.send(:include, FixUpdateCounters)
+1

, , .
fl00r, , return "false", , . , "after_update: update_counters".

Curley , , , "_id". , .

( ):

def update_counters
   if container_id_changed?
     Container.increment_counter(:items_count, container_id) unless container_id.nil?
     Container.decrement_counter(:items_count, container_id_was) unless container_id_was.nil?
   end
end
+1

All Articles