[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-8044":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":32,"readmeContent":33,"aiSummary":34,"trendingCount":16,"starSnapshotCount":16,"syncStatus":35,"lastSyncTime":36,"discoverSource":37},8044,"counter_culture","magnusvk\u002Fcounter_culture","magnusvk","Turbo-charged counter caches for your Rails app.","",null,"Ruby",2115,223,15,3,0,1,8,29.05,"MIT License",false,"main",true,[25,26,27,28,29,30,31],"activerecord","counter-cache","database","gem","rails","ruby","utility-library","2026-06-12 02:01:48","# counter_culture [![Build Status](https:\u002F\u002Fcircleci.com\u002Fgh\u002Fmagnusvk\u002Fcounter_culture\u002Ftree\u002Fmaster.svg?style=svg)](https:\u002F\u002Fcircleci.com\u002Fgh\u002Fmagnusvk\u002Fcounter_culture\u002Ftree\u002Fmaster)\n\nTurbo-charged counter caches for your Rails app. Huge improvements over the Rails standard counter caches:\n\n* Updates counter cache when values change, not just when creating and destroying\n* Supports counter caches through multiple levels of relations\n* Supports dynamic column names, making it possible to split up the counter cache for different types of objects\n* Can keep a running count, or a running total\n\nTested against Ruby 3.0, 3.1, 3.2, 3.3 and 4.0 and against the latest patch releases of Rails 6.0, 6.1, 7.0, 7.1, 7.2, 8.0 and 8.1.\n\nPlease note that -- unlike Rails' built-in counter-caches -- counter_culture does not currently change the behavior of the `.size` method on ActiveRecord associations. If you want to avoid a database query and read the cached value, please use the attribute name containing the counter cache directly.\n\n```ruby\nproduct.categories.size  # => will lead to a SELECT COUNT(*) query\nproduct.categories_count # => will use counter cache without query\n```\n\n## Installation\n\nAdd counter_culture to your Gemfile:\n\n```ruby\ngem 'counter_culture', '~> 3.2'\n```\n\nThen run `bundle install`\n\n## Database Schema\n\nYou must create the necessary columns for all counter caches. You can use counter_culture's generator to create a skeleton migration:\n\n```\nrails generate counter_culture Category products_count\n```\n\nWhich will generate a migration with code like the following:\n```ruby\nadd_column :categories, :products_count, :integer, null: false, default: 0\n```\nNote that the column must be ```NOT NULL``` and have a default of zero for this gem to work correctly.\n\nIf you are adding counter caches to existing data, you must add code to [manually populate their values](#manually-populating-counter-cache-values) to the generated migration.\n\n## Usage\n\n### Simple counter-cache\n\n#### Has many association\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\nNow, the ```Category``` model will keep an up-to-date counter-cache in the ```products_count``` column of the ```categories``` table.\n\n#### Many to many association\n\n```ruby\nclass User \u003C ActiveRecord::Base\n  has_many :group_memberships\n  has_many :groups, through: :group_memberships\nend\n\nclass Group \u003C ActiveRecord::Base\n  has_many :group_memberships\n  has_many :members, through: :group_memberships, class: \"User\"\nend\n\nclass GroupMembership \u003C ActiveRecord::Base\n  belongs_to :group\n  belongs_to :member, class: \"User\"\n  counter_culture :group, column_name: \"members_count\"\n  # If you'd like to also touch the group when `members_count` is updated\n  # counter_culture :group, column_name: \"members_count\", touch: true\nend\n```\n\nNow, the `Group` model will have an up to date count of its members in the `members_count` column\n\n### Multi-level counter-cache\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :sub_category\n  counter_culture [:sub_category, :category]\nend\n\nclass SubCategory \u003C ActiveRecord::Base\n  has_many :products\n  belongs_to :category\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :sub_categories\nend\n```\n\nNow, the ```Category``` model will keep an up-to-date counter-cache in the ```products_count``` column of the ```categories``` table. This will work with any number of levels.\n\nIf you want to have a counter-cache for each level of your hierarchy, then you must add a separate counter cache for each level.  In the above example, if you wanted a count of products for each category and sub_category you would change the Product class to:\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :sub_category\n  counter_culture [:sub_category, :category]\n  counter_culture [:sub_category]\nend\n```\n\n### Customizing the column name\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, column_name: \"products_counter_cache\"\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\nNow, the ```Category``` model will keep an up-to-date counter-cache in the ```products_counter_cache``` column of the ```categories``` table. This will also work with multi-level counter caches.\n\n### Dynamic column name\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, column_name: proc {|model| \"#{model.product_type}_count\" }\n  # attribute product_type may be one of ['awesome', 'sucky']\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\n### Delta Magnitude\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, column_name: :weight, delta_magnitude: proc {|model| model.product_type == 'awesome' ? 2 : 1 }\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\nNow the `Category` model will keep the `weight` column up to date: `awesome` products will affect it by a magnitude of 2, others by a magnitude of 1.\n\nYou can also use a static multiplier as the `delta_magnitude`:\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, column_name: :weight, delta_magnitude: 3\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\nNow adding a `Product` will increase the `weight` column in its `Category` by 3; deleting it will decrease it by 3.\n\n### Conditional counter cache\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, column_name: proc {|model| model.special? ? 'special_count' : nil }\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\nNow, the ```Category``` model will keep the counter cache in ```special_count``` up-to-date. Only products where ```special?``` returns true will affect the special_count.\n\nIf you would like to use this with `counter_culture_fix_counts`, make sure to also provide [the `column_names` configuration](#handling-dynamic-column-names).\n\n### Temporarily skipping counter cache updates\n\nIf you would like to temporarily pause counter_culture, for example in a backfill script, you can do so as follows:\n\n```ruby\nReview.skip_counter_culture_updates do\n  user.reviews.create!\nend\n\nuser.reviews_count # => unchanged\n```\n\n### Totaling instead of counting\n\nInstead of keeping a running count, you may want to automatically track a running total.\nIn that case, the target counter will change by the value in the totaled field instead of changing by exactly 1 each time.\nUse the ```:delta_column``` option to specify that the counter should change by the value of a specific field in the counted object.\nFor example, suppose the Product model table has a field named ```weight_ounces```, and you want to keep a running\ntotal of the weight for all the products in the Category model's ```product_weight_ounces``` field:\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, column_name: 'product_weight_ounces', delta_column: 'weight_ounces'\nend\n\nclass Category \u003C ActiveRecord::Base\n  has_many :products\nend\n```\n\nNow, the ```Category``` model will keep the counter cache in ```product_weight_ounces``` up-to-date.\nThe value in the counter cache will be the sum of the ```weight_ounces``` values in each of the associated Product records.\n\nThe ```:delta_column``` option supports all numeric column types, not just ```:integer```. Specifically, ```:float``` is supported and tested.\n\n### Dynamically over-writing affected foreign keys\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category, foreign_key_values:\n      proc {|category_id| [category_id, Category.find_by(id: category_id)&.parent_category&.id] }\nend\n\nclass Category \u003C ActiveRecord::Base\n  belongs_to :parent_category, class_name: 'Category', foreign_key: 'parent_id'\n  has_many :children, class_name: 'Category', foreign_key: 'parent_id'\n\n  has_many :products\nend\n```\n\nNow, the ```Category``` model will keep an up-to-date counter-cache in the ```products_count``` column of the ```categories``` table. Each product will affect the counts of both its immediate category and that category's parent. This will work with any number of levels.\n\n### Updating timestamps when counts change\n\nBy default, counter_culture does not update the timestamp of models when it updates their counter caches. If you would like every change in the counter cache column to result in an updated timestamp, simply set the touch option to true:\n```ruby\n  counter_culture :category, touch: true\n```\n\nThis is useful when you require your caches to get invalidated when the counter cache changes.\n\n### Custom timestamp column\n\nYou may also specify a custom timestamp column that gets updated only when a particular counter cache changes:\n```ruby\n  counter_culture :category, touch: 'category_count_changed'\n```\n\nWith this option, any time the `category_counter_cache` changes both the `category_count_changed` and `updated_at` columns will get updated.\n\n### Avoiding deadlocks \u002F executing counter cache updates after commit\n\nSome applications run into issues with deadlocks involving counter cache updates when using this gem. See [#263](https:\u002F\u002Fgithub.com\u002Fmagnusvk\u002Fcounter_culture\u002Fissues\u002F263#issuecomment-772284439) for information and helpful links on how to avoid this issue.\n\nAnother option is to simply defer the update of counter caches to outside of the transaction. This gives up transactional guarantees for your counter cache updates but should resolve any deadlocks you experience. This behavior is disabled by default, enable it on each affected counter cache as follows:\n\n```ruby\n  counter_culture :category, execute_after_commit: true\n```\n[NOTE] You need to manually specify the `after_commit_action` as dependency in the Gemfile to use this feature\n```ruby\n...\ngem \"after_commit_action\"\n...\n```\n\nYou can also pass a `Proc` for dynamic control. This is useful for temporarily moving the counter cache update inside of the transaction:\n\n```ruby\n  counter_culture :category, execute_after_commit: proc { !Thread.current[:update_counter_cache_in_transaction] }\n```\n\n### Aggregating multiple updates into single SQL queries\n\n>  **NOTE**: This does not have an effect on Papertrail callbacks\n\nBy default, every create\u002Fupdate\u002Fdestroy will trigger separate `UPDATE` SQLs for each action and targeted column. For example, if the creation of 1 record needs to increment 3 counter cache columns, a transaction that creates 3 records of this kind will generate 9 `UPDATE` SQL queries adding `+1` to the value of said columns (3 created records x 3 counter cache columns to be incremented by each).\n\nTo avoid this, you can wrap the logic that creates\u002Fupdates\u002Fdestroys multiple records in `CounterCulture.aggregate_counter_updates`. This will sum all updates for counter cache columns and will take the latest value for timestamp updates. As a result, you will get just one `UPDATE` SQL query per target record that updates all of its columns at once.\n\n#### Execute aggregated SQL queries before transaction `COMMIT`\n```ruby\nActiveRecord::Base.transaction do\n  CounterCulture.aggregate_counter_updates do\n    # list of updates\n  end # => executes aggregated SQLs\nend\n```\n\n#### Execute aggregated SQL queries after transaction `COMMIT`\n```ruby\nCounterCulture.aggregate_counter_updates do\n  ActiveRecord::Base.transaction do\n    # list of updates\n  end\nend # => executes aggregated SQLs\n```\n\n#### Examples:\n\u003Cdetails>\u003Csummary>Sums multiple counter updates for single column of a single target record\u003C\u002Fsummary>\n\n```sql\n-- Before\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\n\n-- After\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 2 WHERE `authors`.`id` = 1\n```\n\n```sql\n-- Before\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\n\n-- After\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + -2 WHERE `authors`.`id` = 1\n```\n\n```sql\n-- Before\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\n\n-- After\nNo query\n```\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Combines multiple updates for multiple columns of a single target record\u003C\u002Fsummary>\n\n```sql\n-- Before\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1, updated_at = '2024-06-06 01:00:00' WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1, updated_at = '2024-06-06 02:00:00' WHERE `authors`.`id` = 1\n\n-- After\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 2, updated_at = '2024-06-06 02:00:00' WHERE `authors`.`id` = 1\n```\n\n```sql\n-- Before\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`works_count` = COALESCE(`authors`.`works_count`, 0) + 1 WHERE `authors`.`id` = 1\n\n-- After\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1, `authors`.`works_count` = COALESCE(`authors`.`works_count`, 0) + 1 WHERE `authors`.`id` = 1\n```\n\n\u003C\u002Fdetails>\n\n\u003C\u002Fdetails>\n\n\u003Cdetails>\u003Csummary>Combines multiple updates of multiple target records\u003C\u002Fsummary>\n\n```sql\n-- Before\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) - 1 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 2\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 2\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 1 WHERE `authors`.`id` = 2\n\n-- After\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + -3 WHERE `authors`.`id` = 1\nUPDATE `authors` SET `authors`.`books_count` = COALESCE(`authors`.`books_count`, 0) + 3 WHERE `authors`.`id` = 2\n```\n\n\u003C\u002Fdetails>\n\n### Manually populating counter cache values\n\nYou will sometimes want to populate counter-cache values from primary data. This is required when adding counter-caches to existing data. It is also recommended to run this regularly (at BestVendor, we run it once a week) to catch any incorrect values in the counter caches.\n\n```ruby\nProduct.counter_culture_fix_counts\n# will automatically fix counts for all counter caches defined on Product\n\nProduct.counter_culture_fix_counts exclude: :category\n# will automatically fix counts for all counter caches defined on Product, except for the :category relation\n\nProduct.counter_culture_fix_counts only: :category\n# will automatically fix counts only on the :category relation on Product\n\n# :exclude and :only also accept arrays of one level relations\n# if you want to fix counts on a more than one level relation you need to use convention below:\n\nProduct.counter_culture_fix_counts only: [[:subcategory, :category]]\n# will automatically fix counts only on the two-level [:subcategory, :category] relation on Product\n\nProduct.counter_culture_fix_counts column_name: :reviews_count\n# will automatically fix counts only on the :reviews_count column on Product\n# This allows us to skip the columns that have already been processed. This is useful after running big DB changes that affect only one counter cache column.\n\n# :except and :only also accept arrays\n\nProduct.counter_culture_fix_counts verbose: true\n# prints some logs to STDOUT\n\nProduct.counter_culture_fix_counts only: :category, where: { categories: { id: 1 } }\n# will automatically fix counts only on the :category with id 1 relation on Product\n```\n\nThe ```counter_culture_fix_counts``` counts method uses batch processing of records to keep the memory consumption low. The default batch size is 1000 but is configurable like so\n```ruby\n# In an initializer\nCounterCulture.config.batch_size = 100\n```\nor by passing the :batch_size option to the method call\n\n```ruby\nProduct.counter_culture_fix_counts batch_size: 100\n```\n\n```counter_culture_fix_counts``` returns an array of hashes of all incorrect values for debugging purposes. The hashes have the following format:\n\n```ruby\n{ entity: which model the count was fixed on,\n  id: the id of the model that had the incorrect count,\n  what: which column contained the incorrect count,\n  wrong: the previously saved, incorrect count,\n  right: the newly fixed, correct count }\n```\n\n```counter_culture_fix_counts``` is optimized to minimize the number of queries and runs very quickly.\n\nSimilarly to `counter_culture`, it is possible to update the records' timestamps, when fixing counts. If you would like to update the default timestamp field, pass `touch: true` option:\n\n```ruby\nProduct.counter_culture_fix_counts touch: true\n```\n\nIf you have specified a custom timestamps column, pass its name as the value for the `touch` option:\n\n```ruby\nProduct.counter_culture_fix_counts touch: 'category_count_changed'\n```\n\n\n#### Parallelizing fix counter cache in multiple workers\n\nThe options start and finish are especially useful if you want multiple workers dealing with the same processing queue. You can make worker 1 handle all the records between id 1 and 9999 and worker 2 handle from 10000 and beyond by setting the :start and :finish option on each worker.\n\n>  **! NOTE**: the IDs we pass as `start` and `finish` here are in fact `Category` IDs, not `Product`!\n\n```ruby\nProduct.counter_culture_fix_counts start: 10_000\n# will fix counts for all counter caches defined on Product from record 10000 and onwards.\n\nProduct.counter_culture_fix_counts finish: 10_000\n# let's process until 10000 records.\n\nProduct.counter_culture_fix_counts start: 1000, finish: 2000\n# In worker 1, lets process from 1000 to 2000\n\nProduct.counter_culture_fix_counts start: 2001, finish: 3000\n# In worker 2, lets process from 2001 to 3000\n```\n\n#### Fix counter cache using a replica database\n\nWhen fixing counter caches the number of reads usually vastly exceeds the number of writes. It can make sense to offload the read load to a replica database in this case. Rails 6 introduced [native handling of multiple database connections](https:\u002F\u002Fguides.rubyonrails.org\u002Fv6.0\u002Factive_record_multiple_databases.html). You can use this to send read traffic to a read-only replica using the option `db_connection_builder`:\n\n```ruby\nProduct.counter_culture_fix_counts db_connection_builder: proc{|reading, block|\n  if reading # Count calls will request a reading connection\n    Product.connected_to(role: :reading, &block)\n  else # Update all calls will request a non-reading connection\n    Product.connected_to(role: :writing, &block)\n  end\n}\n```\n\n#### Handling dynamic column names\n\nManually populating counter caches with dynamic column names requires additional configuration:\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  counter_culture :category,\n      column_name: proc {|model| \"#{model.product_type}_count\" },\n      column_names: {\n          [\"products.product_type = ?\", 'awesome'] => 'awesome_count',\n          [\"products.product_type = ?\", 'sucky'] => 'sucky_count'\n      }\n  # attribute product_type may be one of ['awesome', 'sucky']\nend\n```\n\nYou can specify a scope instead of a where condition string for `column_names`. We recommend\nproviding a Proc that returns a hash instead of directly providing a hash: If you were to directly\nprovide a scope this would load your schema cache on startup which will break things like\n`rake db:migrate`.\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  scope :awesomes, ->{ where \"products.product_type = ?\", 'awesome' }\n  scope :suckys, ->{ where \"products.product_type = ?\", 'sucky' }\n\n  counter_culture :category,\n      column_name: proc {|model| \"#{model.product_type}_count\" },\n      column_names: -> { {\n          Product.awesomes => :awesome_count,\n          Product.suckys => :sucky_count\n      } }\nend\n```\n\nIf you would like to avoid this configuration and simply skip counter caches with\ndynamic column names, while still fixing those counters on the model that are not\ndynamic, you can pass `skip_unsupported`:\n\n```ruby\nProduct.counter_culture_fix_counts skip_unsupported: true\n```\n\nYou can also use context within the block that was provided with the `column_names` method:\n\n```ruby\nclass Product \u003C ActiveRecord::Base\n  belongs_to :category\n  scope :awesomes, -> (ids) { where(ids: ids, product_type: 'awesome') }\n\n  counter_culture :category,\n      column_name: 'awesome_count'\n      column_names: -> (context) {\n        { Product.awesomes(context[:ids]) => :awesome_count }\n      }\nend\n\nProduct.counter_culture_fix_counts(context: { ids: [1, 2] })\n```\n\n#### Handling over-written, dynamic foreign keys\n\nManually populating counter caches with dynamically over-written foreign keys (```:foreign_key_values``` option) is not supported. You will have to write code to handle this case yourself.\n\n### Soft-deletes with `paranoia` or `discard`\n\nThis gem will keep counters correctly updated in Rails 4.2 or later when using\n[paranoia](https:\u002F\u002Fgithub.com\u002Frubysherpas\u002Fparanoia) or\n[discard](https:\u002F\u002Fgithub.com\u002Fjhawthorn\u002Fdiscard) for soft-delete support.\nHowever, to ensure that counts are incremented after a restore you have\nto make sure to set up soft deletion (via `acts_as_paranoid` or\n`include Discard::Model`) before the call to `counter_culture` in your model:\n\n#### Paranoia\n\n```ruby\nclass SoftDelete \u003C ActiveRecord::Base\n  acts_as_paranoid\n\n  belongs_to :company\n  counter_culture :company\nend\n```\n\n#### Discard\n\n```ruby\nclass SoftDelete \u003C ActiveRecord::Base\n  include Discard::Model\n\n  belongs_to :company\n  counter_culture :company\nend\n```\n\n#### Counting soft-deleted records\n\nBy default, soft-deleted records are excluded from counter caches. If you want a\ncounter that includes soft-deleted records, use the `include_soft_deleted` option:\n\n```ruby\nclass SoftDelete \u003C ActiveRecord::Base\n  acts_as_paranoid # or: include Discard::Model\n\n  belongs_to :company\n  counter_culture :company, column_name: 'all_records_count', include_soft_deleted: true\nend\n```\n\nWith this option, soft-delete and restore operations will not change the counter.\nOnly hard-destroying a record (`really_destroy!` for Paranoia, `destroy` for\nDiscard) will decrement the counter. `counter_culture_fix_counts` will also\ncorrectly include soft-deleted records when reconciling.\n\n### PaperTrail integration\n\nIf you are using the [`paper_trail` gem](https:\u002F\u002Fgithub.com\u002Fairblade\u002Fpaper_trail)\nand would like new versions to be created when the counter cache columns are\nchanged by counter_culture, you can set the `with_papertrail` option:\n\n```ruby\nclass Review \u003C ActiveRecord::Base\n  counter_culture :product, with_papertrail: true\nend\n\nclass Product \u003C ActiveRecord::Base\n  has_paper_trail\nend\n```\n\n#### Polymorphic associations\n\ncounter_culture now supports polymorphic associations of one level only.\n\nTo discover which models need to be updated via `counter_culture_fix_counts`,\ncounter_culture performs a `DISTINCT` query on the polymorphic relationship.\nThis query can be expensive so we therefore offer the option\n(`polymorphic_classes`) to specify the models' counts that should be corrected:\n\n```ruby\nImage.counter_culture_fix_counts(polymorphic_classes: Product)\n# or\nImage.counter_culture_fix_counts(polymorphic_classes: [Product, Employee])\n```\n\n## Using Read Replicas\n\nWhen using `counter_culture_fix_counts`, you can configure counter_culture to use read replicas for counting operations, which can help reduce load on your primary database. Write operations will still use the primary database.\n\n### Global Configuration\n\nYou can enable read replica support globally:\n\n```ruby\n# config\u002Finitializers\u002Fcounter_culture.rb\nCounterCulture.configure do |config|\n  config.use_read_replica = true\nend\n```\n\n### Per-Call Configuration\n\nYou can also specify read replica usage per call (this takes precedence over global configuration):\n\n```ruby\nModel.counter_culture_fix_counts(\n  db_connection_builder: proc { |reading, block|\n    if reading\n      ApplicationRecord.connected_to(role: :reading, &block)\n    else\n      ApplicationRecord.connected_to(role: :writing, &block)\n    end\n  }\n)\n```\n\nNote: This feature requires Rails 7.1 or higher for full read replica support. On older versions, it will fall back to using the primary database.\n\n## Contributing to counter_culture\n\n* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.\n* Check out the issue tracker to make sure someone already hasn't requested it and\u002For contributed it.\n* Fork the project.\n* Start a feature\u002Fbugfix branch.\n* Commit and push until you are happy with your contribution.\n* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.\n* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.\n\n## Copyright\n\nCopyright (c) 2012-2021 BestVendor, Magnus von Koeller. See LICENSE.txt for further details.\n","counter_culture 是一个为 Rails 应用提供高效计数缓存的 Ruby 宝石。其核心功能包括在值变化时更新计数缓存、支持多层关系中的计数缓存以及动态列名，使得可以为不同类型对象拆分计数缓存，并且能够保持运行计数或总计数。相比 Rails 自带的计数缓存，counter_culture 提供了显著的性能改进。它适用于需要频繁访问关联记录数量的应用场景，如社交网络中粉丝数、文章评论数等统计信息的实时展示，能有效减少数据库查询次数，提升应用性能。",2,"2026-06-11 03:15:47","top_language"]