Module: ConcernsOnRails::Models::CounterCacheable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/models/counter_cacheable.rb
Overview
Conditional, denormalized association counters (“counter_culture-lite”). Rails’ native ‘belongs_to …, counter_cache: true` maintains exactly one column counting every child — it cannot keep an `approved_comments_count` next to a `comments_count`, and it has no way to repair drift after a backfill or a counter_cache-less write. This concern, declared on the CHILD, keeps one or many parent columns in sync, each with an optional `if:` condition, and ships a `recount_counter_caches!` repair method.
class Comment < ApplicationRecord
include ConcernsOnRails::CounterCacheable
belongs_to :post # declare the belongs_to FIRST
belongs_to :author, class_name: "User"
counter_cacheable_by :post # posts.comments_count
counter_cacheable_by :post, count: :approved_comments_count,
if: -> { approved? } # conditional counter
counter_cacheable_by :author, count: :posts_count, touch: true
end
Post.find(1).comments_count # maintained on create/destroy/update
Comment.recount_counter_caches! # repair/backfill every counter
Behaviour:
* create/destroy adjust the counter by ±1 when the foreign key is present
and the `if:` condition holds for the record's current state.
* update handles BOTH a foreign-key reparent (the row moved to another
parent) AND a condition flip (the `if:` result changed): the old parent
is decremented if it used to count the row, the new parent incremented
if it counts it now. A no-op save writes nothing.
* Adjustments use `update_counters` — a single SQL `COALESCE(col,0) ± 1`,
atomic under concurrency — and run inside the record's own save
transaction, so a rolled-back save rolls back the counter too.
Notes:
* The `belongs_to` must be declared BEFORE the macro (the reflection is
validated at declaration time). Polymorphic associations are not
supported in this version.
* Do NOT also set native `counter_cache: true` on the same column — both
would fire and double-count.
* Counters track the PERSISTED record. Writes that skip callbacks
(`update_column(s)`, `update_all`, `delete`, raw SQL) are not tracked —
run `recount_counter_caches!` to reconcile.
* `if:` conditions should read the record's OWN columns; the previous
state is reconstructed from the changed attributes, not the
associations.
* `recount_counter_caches!` rewrites every parent's counter and, for a
conditional counter, scans the children in Ruby (portable across
adapters, but O(n)) — a maintenance operation, run it offline.
* Reach for the `counter_culture` gem when you need multi-level rollups,
delta columns, or after-commit execution.
Defined Under Namespace
Modules: ClassMethods
Constant Summary collapse
- LABEL =
"ConcernsOnRails::Models::CounterCacheable".freeze