magnusvk / counter_culture Goto Github PK
View Code? Open in Web Editor NEWTurbo-charged counter caches for your Rails app.
License: MIT License
Turbo-charged counter caches for your Rails app.
License: MIT License
I'm stilling having problems where the counter_culture_fix_counts isn't accurately updating float counters. In my app the floats are rounded to whole numbers, and are often reset to 0 when the sum of counts is not 0.
I have added a spec (and am working on making it better). I pushed out what I have so far, and you can see what I am trying to test. Look for the spec titled "should fix a sum counter cache correctly" line 961.
https://github.com/atstockland/counter_culture
Thanks, Magnus
rails d counter_culture Xxx xxx
will print
remove db/migrate/xxxxx.rb but no this file
I have an HABTM association between 2 models and would love to use some sort of cache counter.
Does this gem support that?
Thanks.
I've just come upon this issue, will flesh out here more when I have extra details / specs. But incase anyone has seen it:
If I have config.use_transactional_fixtures = true
(the default), then I found that the callbacks to increment/decrement the counters weren't being called in my RSpec specs.
I outlined the problem in this gist, demonstrating how it only seemed to occur when running specs, and not from the console. @JonRowe was kind enough to point me to use_transactional_fixtures
, and sure enough, setting that to false
resolved the issue.*
I'm using the postgresql
adapter (I see the specs use sqlite3
), not sure if that's relevant.
* I used database_cleaner instead.
Hi! First of all, sorry if this space is strictly about bugs.. I don't think I've got into one. But I really really could use some help!
I don't understand why this is failing so horribly. I have this related models:
class Match < ActiveRecord::Base
has_many :predictions
after_save :score_predictions
# I'm feeling this method might be a problem but I couldn't keep somewhat
# sane counts without _fix_counts here
def score_predictions
predictions.find_each(&:score!)
Prediction.counter_culture_fix_counts
end
end
class Prediction < ActiveRecord::Base
belongs_to :partial_score
belongs_to :match
counter_culture :partial_score, column_name: 'total',
delta_column: 'points'
# I tried doing it from PartialScore with a counter_culture call but
# couldn't make it work
counter_culture [ :partial_score, :score ], column_name: 'prediction_total',
delta_column: 'points'
counter_culture [ :partial_score, :score ],
column_name: Proc.new { |p| p.exact? ? 'exacts' : nil },
column_names: { [ 'predictions.exact = ?', true ] => 'exacts' }
def score!
# method that calculates +points+ and sets column based on object and
# match state and finally saves
save
end
end
class PartialScore < ActiveRecord::Base
has_many :predictions
belongs_to :score
end
class Score < ActiveRecord::Base
has_many :partial_scores
end
I'm getting really wrong counts, every time. I'm sure it's something I can't
see here. Sorry again for using this issue kinda as rubber duck debugging.
We are having a problem with our implementation of counter cache. When we have an after_create method it ends up doubling up the count (returning 2 instead of 1).
Here is a simple setup
class Issue < ActiveRecord::Base
belongs_to :order
counter_culture :order
after_create :set_ticket_number
def set_ticket_number
update_attribute(:ticket_number, 200000+id)
end
end
class Order < ActiveRecord::Base
has_many :issues
end
Then in the console
>> o = Order.first
=> #<Order id: 1, issues_count: 0>
>> o.issues_count
=> 0
>> o.issues
=> []
>> o.issues.create
(0.1ms) BEGIN
SQL (0.5ms) INSERT INTO `issues` (`order_id`, `ticket_number`) VALUES (1, NULL)
Order Load (0.7ms) SELECT `orders`.* FROM `orders` WHERE `orders`.`id` = 1 LIMIT 1
(0.3ms) UPDATE `issues` SET `order_id` = 1, `ticket_number` = 1217251 WHERE `issues`.`id` = 1017251
(0.7ms) COMMIT
SQL (0.6ms) UPDATE `orders` SET `issues_count` = COALESCE(`issues_count`, 0) + 1 WHERE `orders`.`id` = 1
SQL (0.5ms) UPDATE `orders` SET `issues_count` = COALESCE(`issues_count`, 0) + 1 WHERE `orders`.`id` = 1
=> #<Issue id: 1017251, order_id: 1, ticket_number: 1217251>
>> o.issues.size
=> 1
>> o.reload
Order Load (0.4ms) SELECT `orders`.* FROM `orders` WHERE `orders`.`id` = 1 LIMIT 1
=> #<Order id: 1, issues_count: 2>
>> o.issues_count
=> 2
Switching back to the Rails counter_cache: true fixes it, also commenting out the after_create fixes it.
Do you have any ideas why it might be doing that? I took a look at the source shortly and thought I might have been interfering with your after_create hook.
Thanks for the awesome gem, it's stopped a lot of our dead lock issues.
hello, I'm using counter_culture to create survey applications
the problem is each time I add citizen the count columns is not automatically update
I have to go to console and run Citizen.counter_culture_fix_counts
below is my model and controller for reference
I'm using rails 4 and nested_attributes
thank you for help
model
class Familycard < ActiveRecord::Base
has_many :citizens , :dependent => :destroy
accepts_nested_attributes_for :citizens, :allow_destroy => :true
end
class Citizen < ActiveRecord::Base
belongs_to :familycard
counter_culture :familycard,
:column_name => Proc.new { |model| "#{model.sex}_count"},
:column_names => {
["citizens.sex = ? ", 'male'] => 'males_count',
["citizens.sex = ? ", 'female'] => 'females_count'
}
counter_culture :familycard
counter_culture :familycard,
:column_name => Proc.new { |model| "#{model.job}_count"},
:column_names => {
["citizens.job = ? ", 'Entrepreneur'] => 'Entrepreneurs_count',
["citizens.job = ? ", 'House wife'] => 'housewifes_count',
["citizens.job = ? ", 'Student'] => 'students_count',
["citizens.job = ? ", 'Veteran'] => 'veterans_count'
}
end
controller
class FamilycardController < ApplicationController
def new
@familycard = Familycard.new(:citizens => [Citizen.new])
end
def create
@familycard = Familycard.new(familycard_params)
if @familycard.save
flash[:success] = "Data Saved"
redirect_to familycards_path
else
render 'familycards/familycard_form'
end
end
I have a counter setup on a class that is the join class between two models. It should change the counter only when the associated parent object has the flag is_deleted set to false. I can't seem to get manual populating working for it though as the column_names hash seems to generate an error when a manual fix is run.
I couldn't figure out how to get this join to work (I'm using Squeel for it) for this counter and get this error:
NoMethodError: undefined method `%' for #ActiveRecord::Relation:0x000000057bf020
Here's the class:
class EventCreation < ActiveRecord::Base
attr_accessible :event_id, :user_id
belongs_to :event
belongs_to :user
counter_culture :user,
:column_name => Proc.new {
|model| !model.event.is_deleted ? 'event_creations_count' : nil
},
:column_names => {
[joins{event}.where{event.is_deleted == false}] => 'event_creations_count'
}
end
@aaronchi reports:
There is a still a problem here for conditional columns... the conditions get added to the main query after WHERE instead of appending to the LEFT JOIN.
It looks like version 0.1.7 hasn't been pushed to rubygems yet. http://rubygems.org/gems/counter_culture. I'll just pull from your git repository, but I wanted to give you a heads up.
Thanks,
Aaron
I'm using Rails 3.2.12 and counter_culture 0.1.11...
class Cat < ActiveRecord::Base
has_many :lives
end
class Live < ActiveRecord::Base
belongs_to :cat
counter_culture :cat
end
In the migration:
add_column :cat, :lives_count, :integer, :null => false, :default => 0
Cat.counter_culture_fix_counts
undefined method each' for nil:NilClass ..../gems/counter_culture-0.1.11/lib/counter_culture.rb:58:in
counter_culture_fix_counts'
I have a classes User
and Item
, each of which has columns haves_count
and wants_count
, which I am trying to apply counter_culture to. They are joined by has_many through
relationships with a class CollectionRecord
.
# User.rb
has_many :collection_records
has_many :items, :through => :collection_records
# Item.rb
has_many :collection_records
has_many :users, :through => :collection_records
CollectionRecord
has an enum status: [:have, :want]
, which I'm trying to use to define the counter column names. I have my counters set up in CollectionRecord
like so:
# CollectionRecord.rb
belongs_to :item
counter_culture :item, :column_name => Proc.new {|cr| "#{cr.status}s_count" },
:column_names => { ["collection_records.status = ?", CollectionRecord.statuses[:have] ] => 'haves_count', [ "collection_records.status = ?", CollectionRecord.statuses[:want] ] => 'wants_count' }
belongs_to :user
counter_culture :user, :column_name => Proc.new {|cr| "#{cr.status}s_count" },
:column_names => {[ "collection_records.status = ?", CollectionRecord.statuses[:have] ] => 'haves_count', [ "collection_records.status = ?", CollectionRecord.statuses[:want] ] => 'wants_count'}
Which is copied from https://github.com/magnusvk/counter_culture#handling-dynamic-column-names and adapted for an enum based on http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html. When I run CollectionRecord.counter_culture_fix_counts
I get:
NoMethodError: undefined method `statuses' for #<Class:0x000001039dfc70>
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/dynamic_matchers.rb:26:in `method_missing'
from /Users/jaime/Desktop/CompleteSet-Rails/app/models/collection_record.rb:14:in `<class:CollectionRecord>'
from /Users/jaime/Desktop/CompleteSet-Rails/app/models/collection_record.rb:4:in `<top (required)>'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:443:in `load'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:443:in `block in load_file'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:633:in `new_constants_in'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:442:in `load_file'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:342:in `require_or_load'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:480:in `load_missing_constant'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/dependencies.rb:180:in `const_missing'
from (irb):101
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/console.rb:90:in `start'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/console.rb:9:in `start'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:69:in `console'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands.rb:17:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
Furthermore, I get this same error anytime I try to do anything else with the class, not just when I try to fix the counts. I've tried taking the class name off of the front and just using something like :column_names => { ["collection_records.status = ?", statuses[:have] ] => 'haves_count', ...
but that gives the same error. It does work if I replace CollectionRecord.statuses[:have/:want]
with the actual values (0/1), but that seems smelly and error prone.
Any idea why using the mapping breaks the entire class, or what I can do to fix it?
I declared counter_culture as following:
class Product < ActiveRecord::Base
...
counter_culture :product_type, column_name: Proc.new {|model| model.active? ? 'products_count' : nil }
end
And I'd like to fix counts for all count declare in Product model, so I run Product.counter_culture_fix_counts
but it raised exception
RuntimeError: Must provide :column_names option for relation [:product_type] when :column_name is a Proc;
you may skip this relation with :skip_unsupported => true
Currently when the count column is updated any update callbacks in the counter_cultured model are not running. Imagine there is a after_update hook that does something (e.g. set the status column to be "FULL") when the count exceeds certain number. It would be nice if those callbacks are not skipped when the count is updated by counter_culture.
We have a multi-level relation with an STI model at the base for our email system. We're tracking email clicks and views. So, it looks like this:
EmailEvent::Viewed/Clicked
-> Email
-> Mailing
We've got some counters on Emails
and Mailings
that work just fine when a new EmailEvent
is created. They're hooked up like so:
counter_culture [:email, :mailing], column_name: "viewed_count"
counter_culture :email, column_name: "viewed_count"
But when we run counter_culture_fix_counts
on either type of EmailEvent
, this happens:
2.1.2 :017 > EmailEvent::Viewed.counter_culture_fix_counts
Mailing Load (1.2ms) SELECT mailings.id, COUNT(email_events.id) AS count, mailings.viewed_count FROM "mailings" LEFT JOIN emails ON mailings.id = emails.mailing_id AND emails.type IN ('EmailEvent::Viewed') LEFT JOIN email_events ON emails.id = email_events.email_id AND email_events.type IN ('EmailEvent::Viewed') GROUP BY "mailings"."id" ORDER BY "mailings"."id" ASC LIMIT 1000 OFFSET 0
PG::UndefinedColumn: ERROR: column emails.type does not exist
LINE 1: ...OIN emails ON mailings.id = emails.mailing_id AND emails.typ...
I'll keep chugging on a solution and issue a PR if I come up with anything. But I figured others should know.
Thanks for taking a look at this!
Current version doesn't seem to allow totaling of floats...only integers.
Example:
Failure/Error: fixed = Company.counter_culture_fix_counts
ActiveRecord::StatementInvalid:
SQLite3::SQLException: ambiguous column name: companies.id: SELECT companies.id, COUNT(companies.id) AS count, companies.children_count FROM "companies" LEFT JOIN companies ON companies.id = companies.parent_id GROUP BY "companies"."id" ORDER BY "companies"."id" ASC LIMIT 1000 OFFSET 0
# ./lib/counter_culture.rb:118:in `block (2 levels) in counter_culture_fix_counts'
# ./lib/counter_culture.rb:102:in `each'
# ./lib/counter_culture.rb:102:in `block in counter_culture_fix_counts'
# ./lib/counter_culture.rb:62:in `each'
# ./lib/counter_culture.rb:62:in `counter_culture_fix_counts'
# ./spec/counter_culture_spec.rb:1333:in `block (3 levels) in <top (required)>'
It would be nice if updating the counters also set the model's updated_at field to the current time, in order to facilitate cache expiration.
Imagine a Category index page listing category names and product counts. These days, it is trivial to perform http caching and/or cache the view using a key that includes the most recent change to the categories.
# controller
fresh_when last_modified: @categories.maximum(:updated_at)
# haml view
- cache ['categories', @categories.maximum(:updated_at)] do
= the view template
Now, if I add/remove a product or change a product's category, I also need to touch the affected categories, otherwise the category index will be stale. Normally, you would enforce this like so:
belongs_to :category, touch: true
HOWEVER, this is too general, as it will cause the cache to needlessly expire when, for example, I am only editing a product's name or other attribute.
Thus, ideally I would like to be able to say something like:
counter_culture :category, touch: true
Does this sound reasonable? I looked at the source, and I understand that you eventually call rails' update_counters
AR method, so the fix would be a little more involved than I initially hoped...
Thanks!
Giuseppe
I tried to run generator command, got this error:
$ bundle exec rails g counter_culture Shop menus_count
/Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/generators/actions/create_migration.rb:13:in `migration_file_name': protected method `migration_file_name' called for #<CounterCultureGenerator:0x007fcce2d67b88> (NoMethodError)
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/generators/actions/create_migration.rb:34:in `existing_migration'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/actions/empty_directory.rb:112:in `invoke_with_conflict_check'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/actions/create_file.rb:60:in `invoke!'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/actions.rb:94:in `action'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/generators/migration.rb:34:in `create_migration'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/generators/migration.rb:63:in `migration_template'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/counter_culture-0.1.19/lib/generators/counter_culture_generator.rb:14:in `generate_migration'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `block in invoke_all'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `each'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `map'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `invoke_all'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/group.rb:232:in `dispatch'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/generators.rb:157:in `invoke'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/commands/generate.rb:11:in `<top (required)>'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/activesupport-4.1.0.rc1/lib/active_support/dependencies.rb:247:in `require'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/activesupport-4.1.0.rc1/lib/active_support/dependencies.rb:247:in `block in require'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/activesupport-4.1.0.rc1/lib/active_support/dependencies.rb:232:in `load_dependency'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/activesupport-4.1.0.rc1/lib/active_support/dependencies.rb:247:in `require'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:135:in `generate_or_destroy'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:51:in `generate'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
from /Users/ykiwng/.rvm/gems/ruby-2.1.0/gems/railties-4.1.0.rc1/lib/rails/commands.rb:17:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
counter_culture :category, :column_name => Proc.new {|model| model.special? ? 'special_count' : nil }
If the model above is not special, nil is returned...and then fix_counts yells "must provide column_name". If nil is returned, shouldn't count_fix move on, rather than get stuck on no column_name?
Is there a work around for this?
I have two models:
class Resume < ActiveRecord::Base
has_many :abstracts, :dependent => :destroy
end
and
class Abstract < ActiveRecord::Base
belongs_to :resume, touch: true
counter_culture :resume
end
In the resume controller I have:
before_action :set_resume, only: [:show, :edit, :update, :destroy]
def update
respond_to do |format|
if @resume.update(resume_params)
# Stuff
end
end
end
def resume_params
params.require(:resume).permit(
:abstracts,
abstracts_attributes: [:id, :title, :text, :_destroy])
end
When I add a new Abstract the counter goes up correctly but when deleting one through the ResumeController::update method the count decreases by two.
First of all, thanks for running this awesome project!
However, I think I've found a bug in counter_culture on Rails 4.1. It seems that the conditional column_name hack isn't working. In the code sample below, I have put in a totally invalid :column_name value "xxx" to be returned if the model.published? does not return nil.
counter_culture :story,
:column_name => Proc.new {|model|
model.published? ? "xxx" : nil
},
:column_names => {['parts.status = ?', 'published'] => "parts_count"}
When this code runs, I see this in my logs:
UPDATE `stories` SET `read_count` = COALESCE(`read_count`, 0) + 0 WHERE `stories`.`id` = 27505
So, apparently, returning nil from that block is actually just going with the default name instead of not-counting. Calling fix_counts actually provides correct counts.
By adding the https://github.com/grosser/test_after_commit gem, testing counter_culture is a piece of cake. Works like a charm!
Perhaps this could be mentioned in the README ?
Not sure if this is a bug or user-error. I'll try to illustrate this...think in terms of a flight school with students and instructors.
Account class
belongs_to :user
Rating class
belongs_to :account # the students account
Flight class
belongs_to :account # the instructors account
belongs_to :rating
counter_culture [:account, :user], # the instructor
delta_column: 'total_time',
column_name: "total_time_sum",
column_names: { ["flights.total_time > ?", 0] => "total_time_sum" }
counter_culture [:rating, :account, :user], # the student
delta_column: 'total_time',
column_name: "total_time_sum",
column_names: { ["flights.total_time > ?", 0.0] => "total_time_sum" }
These counters live in the Flight model. There are two users involved in a flight (student and instructor). The students account is associated to the flight through their rating, whereas the instructors account is directly associated with the flight. If they both go out and fly, they both need to update the total_time_sum column in the users table.
[:account, :user] should only affect instructors
[:rating, :account, :user] should only affect students.
However, when I run Flight.counter_culture_fix_counts all users run through both of these methods. In some cases the first method updates the cache to a real value, and the second sets it to 0.0...or visa versa. For example, near the top of the hash is {:entity=>"User", :id=>224, :what=>"total_time_sum", :wrong=>0.0, :right=>59.3} and further down the hash is {:entity=>"User", :id=>224, :what=>"total_time_sum", :wrong=>59.3, :right=>0.0}
I've inspected my models and don't see where anything else might cause this. I'll try to write a spec for this. But, I wanted to reach out and see if you have encountered this, or perhaps my setup is wrong.
Thanks
After some time trying to debug a model whose counter wasn't incrementing in my app, I found a counter_culture bug.
What happens
Counters are not incremented nor decremented when parent elements triggers children #save.
How to reproduce
Why does it happen?
I don't know. I've tried to โ but couldn't - fully understand how counter_culture works. But maybe it is incrementing when model is being created and then it decrements back to 0 when model is changed. I'm not sure.
Example:
I've created a Rails app to demonstrate the bug.
Run rake test
and see tests failing.
Take a look at test/unit/child_test.rb to understand what's going on.
https://github.com/leods92/counter_culture_after_commit_bug
I'd be glad to further discuss about this issue and help someone solve it.
Also, if you guys can give me a clue of what's causing this problem I can provide a pull request.
And thank you, @magnusvk, for this awesome gem. :)
counter_culture_fix_counts
first loads all the correct counts into memory, then iterates over all instances and compares counts. Now, if in between loading the correct counts and comparing them in the loop, new children were created or destroyed, this count will be off and reset to an incorrect, outdated value.
Maybe we can use an update_all with a JOIN to let the database handle this atomically? Might this also be faster?
We could also put the fetching of each correct count and the corresponding update into a transaction, but then that would slow the whole process way down.
I would like if this is currently possible, and if not make a feature request.
If the feature is desirable I may try to implement it.
This will be better explained with an example.
A bug tracker issue has comments.
It is possible to upvote issues by starting a comment with +1
, and downvote them by starting it with -1
.
I want to keep a running count of the score = upvotes - downvotes
of an issue on a table column.
What I would like to write:
class Comment < ActiveRecord::Base
belongs_to :issue
counter_culture :issue,
:column_name => 'issue_score_count',
:delta => Proc.new {|comment|
if comment.start_with?('+1')
1
elsif comment.start_with?('-1')
-1
else
0
end
}
end
class Issue < ActiveRecord::Base
has_many :products
end
Where :delta
is the "feature" I would like.
If this were possible, it would be strictly more general than delta_column
, which could be implemented simply as:
:delta => Proc.new {|comment| comment.column_name}
I have this in my user.rb
:
belongs_to :user
counter_culture :user,
:column_names => {
["goals.completed = 1"] => 'goals_completed_count',
["goals.id"] => 'goals_count'
}
This seems to work fine on create and destroy. On update, I'm not sure how to trigger it. The end result I'm after is knowing how many total goals each user has, and how many of those are completed. In my code above, I'm using "goals.id" just so the counter includes all goals since that's my total goals field. The other one is matching off of the completed field which is a boolean.
BTW, completed is a checkbox and can be set either at creation or when doing an update. I thought I would just manually do an after_update
but I can't tell if a record was previously marked completed or not.
This is an awesome gem and does exactly what I want... if I could just figure out how to get it properly setup. I ran the manual update in the console and it properly updated both fields so that part seems setup correctly.
I understand the need for the column_names config in order to facilitate fast manual updates, but the format made my brain hurt. This seems confusing to me:
:column_names => {
["products.product_type = ?", 'awesome'] => 'awesome_count',
["products.product_type = ?", 'sucky'] => 'sucky_count'
}
Wouldn't something like this make more sense?
:column_names => [
:awesome_count => ["products.product_type = ?", 'awesome'] ,
:sucky_count => ["products.product_type = ?", 'sucky']
]
This is far more readable and clear to me.
If you changed this, you could continue to support both formats by checking if the value passed is an array.
Thanks for the great gem, it's designed to do exactly what I was looking for: keep a cache column of a sum, rather than just a count.
I have a setup like so:
class Game < ActiveRecord::Base
has_many :players
def modify_player_scores
players.each do |p|
p.update(score: p.raw_score * 2)
end
end
end
class Player < ActiveRecord::Base
belongs_to :game
counter_culture :game, :column_name => 'total_score', :delta_column => 'score'
after_create do
game.modify_player_scores
end
end
When a player is created, this triggers a callback to it's parent game that modifies the player. I would expect the cache column to update when the player is changed in this manner, but it does not seem to do that.
Instead, it either ignores the updated value or uses the updated value incorrectly at a later time when another update is called manually.
I forked and made a test illustrating this, and will try to attach a pull request. Should I now make a pull request, or do you want to grab the test from my fork another way? (Forgive me, this is the first time I've tried to share code with an existing repository.)
Also, it's entirely possible that this test is trying to do something that is not the intended behavior. If so, please let me know.
Thank you again for your time.
I'm trying to keep a counter of the # of targets a given theme has that are NOT completed.
Target.tier != "Completed"
I created the field Theme.active_targets_count successfully.
The relevant section in my target.rb:
belongs_to :theme
counter_culture :theme, :column_name => Proc.new {|model| model.tier != "Completed" ? 'active_targets_count' : nil }
If a Target.tier == "1" and then I run the following:
def complete
@target.update_columns(completion_date: Time.current, tier: "Completed")
redirect_to targets_path, :notice => "Target marked complete."
end
The active_targets_count does not decrement.
Am I doing something wrong or is this an issue? Thanks! Love your gem and it's been very handy on other projects.
I have a User class with an enum status and a default scope that checks this status:
class User < ActiveRecord::Base
enum status: [:active, :suspended, :inactive]
...
default_scope { where status: :active }
...
I added a couple counter columns to my users table, and when I try to manually set the values, I get a MySQL error because of the default scope applying the enum in a way that the update doesn't like.
UserLogin.counter_culture_fix_counts
User Load (0.9ms) SELECT users.id, COUNT(user_logins.id) AS count, users.logins_count FROM `users` LEFT JOIN user_logins ON users.id = user_logins.user_id WHERE `users`.`status` = 'active' GROUP BY `users`.`id` ORDER BY `users`.`id` ASC LIMIT 1000 OFFSET 0
SQL (0.6ms) UPDATE `users` SET `users`.`logins_count` = 4 WHERE `users`.`status` = 'active' AND `users`.`id` = 1
Mysql::Error: Truncated incorrect DOUBLE value: 'active': UPDATE `users` SET `users`.`logins_count` = 4 WHERE `users`.`status` = 'active' AND `users`.`id` = 1
ActiveRecord::StatementInvalid: Mysql::Error: Truncated incorrect DOUBLE value: 'active': UPDATE `users` SET `users`.`logins_count` = 4 WHERE `users`.`status` = 'active' AND `users`.`id` = 1
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/mysql_adapter.rb:423:in `query'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/mysql_adapter.rb:423:in `block in exec_without_stmt'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract_adapter.rb:373:in `block in log'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activesupport-4.1.1/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract_adapter.rb:367:in `log'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/mysql_adapter.rb:422:in `exec_without_stmt'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/mysql_adapter.rb:283:in `exec_query'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/mysql_adapter.rb:466:in `exec_delete'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:111:in `update'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `update'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/relation.rb:327:in `update_all'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:133:in `block (3 levels) in counter_culture_fix_counts'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:120:in `each'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:120:in `block (2 levels) in counter_culture_fix_counts'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:102:in `each'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:102:in `block in counter_culture_fix_counts'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:62:in `each'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/counter_culture-0.1.24/lib/counter_culture.rb:62:in `counter_culture_fix_counts'
from (irb):16
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/console.rb:90:in `start'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/console.rb:9:in `start'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:69:in `console'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
from /Users/jaime/.rvm/gems/ruby-2.1.1/gems/railties-4.1.1/lib/rails/commands.rb:17:in `<top (required)>'
from bin/rails:4:in `require'
It seems to work when I do something like:
User.unscoped do
UserLogin.counter_culture_fix_counts
end
which I got from http://stackoverflow.com/a/4759121/1461038
I figured I'd document my solution here in case anyone else has the same problem, or if there is a better solution.
This is not really an issue of the gem itself (which it has been really useful), but I would like to ask what would be the suggested way of dealing with the following problem I have.
In one of my models (Order) I have several counters that keep track of the number of Ads (Order has_many Ads), depending on their status. So, I have the following in my Ad model:
counter_culture :order, :column_name => Proc.new {|model| model.pending_review? ? 'pending_review_ads_count' : nil }
counter_culture :order, :column_name => Proc.new {|model| model.reviewed? ? 'review_ads_count' : nil }
counter_culture :order, :column_name => Proc.new {|model| model.failed? ? 'fail_ads_count' : nil }
counter_culture :order, :column_name => Proc.new {|model| model.pending? ? 'pending_ads_count' : nil }
I am trying to dinamically change the status of an Order, when some conditions happen with its counters. For example
What would be the best approach of doing this kind of comparison on the counters in order to trigger a specific method?
This hash is returned when running counter_culture_fix_counts.
{:entity=>"User", :id=>263, :what=>"total_time_sum", :wrong=>10.6, :right=>10}.
The DB schema on this column is:
t.float "total_time_sum", default: 0.0, null: false
The total is, in fact, 10.6...so counter_culture seems to be confused about the float value.
i tried following in my model:
counter_culture :playground, :column_name => Proc.new {|model| model.recognized? ? 'playground_snapshots_recognized_count' : nil }
counter_culture :playground, :column_name => 'playground_snapshots_taken_count'
its working and updating both columns if model.recognized, otherwise updating just taken column. Perfect.
I would like to test it since there is some logic behind. I have been using database cleaner with truncation mode, trying to call model.reload in test but counters are not updated at all.
The method looks for count[model.id], and it seems if there are gaps in the primary key column, it isn't finding count[model.id] and so setting the count to 0
When I run counter_culture_fix_counts, it returns that a column is wrong and should be a sum of 0.0. When, in fact the columns sum is not zero. Also, counter_culture_fix_counts is returning records that it thinks are incorrect, but it is not updating the database...just returning the hash.
I updated to from 0.1.14 ~> 0.1.16 and receive now the following deprecation warning with Rails 4.
DEPRECATION WARNING: #connection is deprecated in favour of accessing it via the class.
I tried to run the counter_culter tests on my local machine with rake
and rspec
, but it looks like there is a loop somewhere. (ruby 2.0)
Excerpt from the console output:
D, [2013-10-07T16:44:40.118517 #38558] DEBUG -- : (1.2ms) commit transaction
D, [2013-10-07T16:44:40.119292 #38558] DEBUG -- : (0.1ms) begin transaction
D, [2013-10-07T16:44:40.121151 #38558] DEBUG -- : SQL (0.5ms) INSERT INTO "simple_dependents" ("created_at", "simple_main_id", "updated_at") VALUES (?, ?, ?) [["created_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00], ["simple_main_id", 3610], ["updated_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00]]
D, [2013-10-07T16:44:40.122453 #38558] DEBUG -- : SimpleMain Load (0.1ms) SELECT "simple_mains".* FROM "simple_mains" WHERE "simple_mains"."id" = ? ORDER BY "simple_mains"."id" ASC LIMIT 1 [["id", 3610]]
D, [2013-10-07T16:44:40.124705 #38558] DEBUG -- : (1.6ms) commit transaction
D, [2013-10-07T16:44:40.128017 #38558] DEBUG -- : SQL (2.0ms) UPDATE "simple_mains" SET "simple_dependents_count" = COALESCE("simple_dependents_count", 0) + 1 WHERE "simple_mains"."id" = 3610
D, [2013-10-07T16:44:40.128639 #38558] DEBUG -- : (0.2ms) begin transaction
D, [2013-10-07T16:44:40.131126 #38558] DEBUG -- : SQL (0.6ms) INSERT INTO "simple_dependents" ("created_at", "simple_main_id", "updated_at") VALUES (?, ?, ?) [["created_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00], ["simple_main_id", 3610], ["updated_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00]]
D, [2013-10-07T16:44:40.132465 #38558] DEBUG -- : SimpleMain Load (0.1ms) SELECT "simple_mains".* FROM "simple_mains" WHERE "simple_mains"."id" = ? ORDER BY "simple_mains"."id" ASC LIMIT 1 [["id", 3610]]
D, [2013-10-07T16:44:40.134285 #38558] DEBUG -- : (1.3ms) commit transaction
D, [2013-10-07T16:44:40.136665 #38558] DEBUG -- : SQL (1.4ms) UPDATE "simple_mains" SET "simple_dependents_count" = COALESCE("simple_dependents_count", 0) + 1 WHERE "simple_mains"."id" = 3610
D, [2013-10-07T16:44:40.136985 #38558] DEBUG -- : (0.1ms) begin transaction
D, [2013-10-07T16:44:40.139417 #38558] DEBUG -- : SQL (0.9ms) INSERT INTO "simple_dependents" ("created_at", "simple_main_id", "updated_at") VALUES (?, ?, ?) [["created_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00], ["simple_main_id", 3610], ["updated_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00]]
D, [2013-10-07T16:44:40.140599 #38558] DEBUG -- : SimpleMain Load (0.1ms) SELECT "simple_mains".* FROM "simple_mains" WHERE "simple_mains"."id" = ? ORDER BY "simple_mains"."id" ASC LIMIT 1 [["id", 3610]]
D, [2013-10-07T16:44:40.142350 #38558] DEBUG -- : (1.2ms) commit transaction
D, [2013-10-07T16:44:40.144588 #38558] DEBUG -- : SQL (1.5ms) UPDATE "simple_mains" SET "simple_dependents_count" = COALESCE("simple_dependents_count", 0) + 1 WHERE "simple_mains"."id" = 3610
D, [2013-10-07T16:44:40.145144 #38558] DEBUG -- : (0.1ms) begin transaction
D, [2013-10-07T16:44:40.147456 #38558] DEBUG -- : SQL (0.9ms) INSERT INTO "simple_mains" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00], ["updated_at", Mon, 07 Oct 2013 23:44:40 UTC +00:00]]
D, [2013-10-07T16:44:40.154149 #38558] DEBUG -- : (6.1ms) commit transaction
For any reasonably sized table, counter_culture_fix_counts
will taken a non-trivial amount of time to run.
In keeping with the mantra of disposability required when using services such as Heroku (as I do), I was wondering: If a counter_culture_fix_counts
is killed halfway through, do we lose all the fixed counts?
I.e. if it is killed before completion and I restart it immediately, will be back to square one, or do you expect it to complete quicker the second time (since some set of the incorrect counts will already have been fixed)?
It incorrectly keeps using id instead of the declared primary_key field
Hi - I'd love to use this gem, but I'm having trouble using it for a many-to-many association.
Here's the setup:
Attendee <--> Access <--> Meeting
Attendee model has_many :accesses and has_many :meetings, :through => :accesses
Access model belongs_to :attendee and belongs_to :meeting
Meeting model has_many :accesses and has_many :attendees, :through => :accesses
I'd like to keep a counter of Attendees for each Meeting
The Attendee model has:
counter_culture [:accesses, :meetings] #[:access, :meeting]
I set up the integer column attendees_count on Meeting just like the example.
When I expect the counter to be updated, I get this error:
undefined method `attendee_id_changed?' for #Attendee:0x007fd03246ac30
Can you help? Thanks, Justin
How to reset counters for separate object or for objects matching some query? For example where(deleted: false, hidded: false)
OK, so I have a situation where an Article has_many Tags, and a Tag has_many Articles, through a join table that belongs_to both of them. That's where this code is:
class ArticlesTag < ActiveRecord::Base
belongs_to :article
belongs_to :tag
counter_culture :tag, column_name: "articles_count"
counter_culture :tag, column_name: Proc.new{ |model| model.article.recent? ? 'recent_articles_count' : nil }
As you can see, I'm trying to store both the number of articles associated with a tag, and the number of "recent" articles. The problem here comes up when I try to populate the count columns in the Tag table using counter_culture_fix_counts:
Must provide :column_names option for relation [:tag] when :column_name is a Proc; you may skip this relation with :skip_unsupported => true
But I can't figure out how to properly configure that option. The condition that makes an article recent is on the article table itself, not the join table where all the counter_cache code is going. If I add an option like:
column_names: {
["articles.created_at > ?", 30.days.ago] => 'recent_articles_count'
}
I just get an error that there is no such column 'articles.created_at', since the articles table isn't being joined in when it tries to run the counter_culture_fix_counts command.
How to make counter on polymorphic relationship?
The counters doesn't seem to update well in test environment.
Let's go with three models User, Product, and Purchase, where a user has_many products through purchases. User is counting purchases.
When I run @user.products.push(@Product), it seems to update the counter just fine.
However, @user.purchases.create(product: @Product) does not update the counter. I have to run Purchase.counter_culture_fix_counts in order to get the test passing.
I have to use 'create' instead of 'push' for various reasons. Using create works fine in real use cases, but it doesn't work in test environment.
Running counter_culture_fix_counts in test is not a big deal, but it would be good to get it working without running the fix method.
I forked the gem and will see if I can write a failing test (just learning specs). But, it seems that a float value is being rounded down to nearest whole number when running counter_culture_fix_counts.
For example (see code below)...when deleting a rating that is associated with the account, the conditional for counter_culture complains with "undefined method `ratings' for #<Rating:..."
rating.rb
belongs_to :account
counter_culture :account, column_name: Proc.new {|account| account.ratings.active.present? ? 'active_ratings_count' : nil }
counter_culture :account, column_name: Proc.new {|account| account.ratings.present? ? 'ratings_count' : nil }
account.rb
has_many :ratings, dependent: :destroy
If you create model like this
class UploadTest < ActiveRecord::Base
belongs_to :category
attr_accessible :category_id, :something
mount_uploader :something, PhotoUploader
counter_culture :category
end
with uploader connected to s3 (fog and carrierwave-aws backends have same behavior) after updating file only file name in database is updated, but new file on s3 is not uploaded (old file stays on s3). This issue only on updating file, creating new object works ok.
I've created same issue in carrierwave project.
I'm trying to setup counter_culture on a rails 3.0.11 project which I've got a relationship between two models like so:
class Parish < ActiveRecord::Base
belongs_to :incumbent, :class_name => "Person"
counter_culture :incumbent, :column_name => "parishes_count"
...
class Person < ActiveRecord::Base
has_many :parishes, :foreign_key => :incumbent_id
...
When I update the parish with a new incumbent I get this error:
undefined method `foreign_key' for #<ActiveRecord::Reflection::AssociationReflection:0x0000000721b310>
Could there be an option to have the counter cache updated every time a model is saved? With the example below, when the name field is updated, thereby changing the special status, special_count is not updated.
class Product < ActiveRecord::Base
belongs_to :category
counter_culture :category, :column_name => Proc.new {|model| model.special? ? 'special_count' : nil }
def special?
name.include("Cool") || name.include("Groovy")
end
end
The problem looks like it's on line 193 of lib/counter_culture.rb.
By the way, I found your gem on the comments section of the railscasts counter cache episode. Thanks for putting this together.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.