Skip to content

design intent of counter_cache? #8997

@jasl

Description

@jasl
[1] pry(main)> Node.last.posts
  Node Load (0.2ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
  Post Load (0.4ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`node_id` = 5
=> []
[2] pry(main)> Post.first.node = Node.last
  Node Load (0.1ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
  SQL (0.3ms)  UPDATE `nodes` SET `posts_count` = COALESCE(`posts_count`, 0) + 1 WHERE `nodes`.`id` = 5
  SQL (0.3ms)  UPDATE `nodes` SET `posts_count` = COALESCE(`posts_count`, 0) - 1 WHERE `nodes`.`id` = 1
=> #<Node id: 5, name: "ttt", cover: nil, description: nil, created_at: "2013-01-18 21:34:14", updated_at: "2013-01-18 22:15:41", posts_count: 0, state: "publish">
[3] pry(main)> Post.first.node
=> #<Node id: 1, name: "System", cover: nil, description: "", created_at: "2012-12-07 18:29:00", updated_at: "2012-12-07 18:40:03", posts_count: 4, state: "system">
[4] pry(main)> Node.last.posts_count
  Node Load (0.4ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
=> 1
[5] pry(main)> Node.last.posts
  Node Load (0.4ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
  Post Load (0.3ms)  SELECT `posts`.* FROM `posts` WHERE `posts`.`node_id` = 5
=> []
[6] pry(main)> Node.last.posts_count
  Node Load (0.4ms)  SELECT `nodes`.* FROM `nodes` ORDER BY `nodes`.`id` DESC LIMIT 1
=> 1

updating counter occur in assigning, but the resource maybe not save, that will made counter incorrectly, why don't update counter after resource update?

update
lib/active_record/associations/builder/belongs_to.rb

method_name = "belongs_to_counter_cache_after_create_for_#{name}"
mixin.redefine_method(method_name) do
  record = send(name)
  record.class.increment_counter(cache_column, record.id) unless record.nil?
end
model.after_create(method_name)

method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
mixin.redefine_method(method_name) do
  record = send(name)
  record.class.decrement_counter(cache_column, record.id) unless record.nil?
end
model.before_destroy(method_name)

we can learn that AR will update counter at resource create and destroy, there is a question: why before destroy not after destroy? although there has transaction to ensure consistency but 'after' is more reasonable.

def replace(record)
  raise_on_type_mismatch(record) if record

  update_counters(record)
  replace_keys(record)
  set_inverse_instance(record)

  @updated = true if record

  self.target = record
end

this will affect on assigning, it will update counter immediately, but has potential problem as I said above.

I think AR should ensure final consistency, so update counter after persistence.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions