Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Big refactoring.

Be less hacky/bootleg about AR integration. This is a lot more graceful
and allows for use of `ActiveRecord::Base.reset_counters` and the
maintaining of counter_cache integrity during association changes.
  • Loading branch information...
commit 3f6bbf28dc4459c9be3fefc5ec27f76727aa366d 1 parent 3255123
@agibralter authored
View
2  ar-resque-counter-cache.gemspec
@@ -4,7 +4,7 @@ require "ar-resque-counter-cache/version"
Gem::Specification.new do |s|
s.name = "ar-resque-counter-cache"
- s.version = ArAsyncCounterCache::VERSION
+ s.version = ArResqueCounterCache::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Aaron Gibralter"]
s.email = ["aaron.gibralter@gmail.com"]
View
2  lib/ar-resque-counter-cache.rb
@@ -1,5 +1,3 @@
require 'active_record'
require 'ar-resque-counter-cache/increment_counters_worker'
require 'ar-resque-counter-cache/active_record'
-
-ActiveRecord::Base.send(:include, ArAsyncCounterCache::ActiveRecord)
View
158 lib/ar-resque-counter-cache/active_record.rb
@@ -1,72 +1,130 @@
-module ArAsyncCounterCache
+module ArResqueCounterCache
module ActiveRecord
- def update_async_counters(dir, *association_ids)
- association_ids.each do |association_id|
- reflection = self.class.reflect_on_association(association_id)
- parent_class = reflection.klass
- column = self.class.async_counter_types[association_id]
- if parent_id = send(reflection.foreign_key)
- ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, dir)
+ ORIG_BT = ::ActiveRecord::Associations::BelongsToAssociation
+ ORIG_BTP = ::ActiveRecord::Associations::BelongsToPolymorphicAssociation
+
+ module Associations
+
+ module AsyncUpdateCounters
+ def update_counters_with_async(record)
+ # TODO make async?
+ update_counters_without_async(record)
end
- end
- end
- module ClassMethods
-
- def belongs_to(association_id, options={})
- column = async_counter_cache_column(options.delete(:async_counter_cache))
- raise "Please don't use both async_counter_cache and counter_cache." if column && options[:counter_cache]
- super(association_id, options)
- if column
- # Store the async_counter_cache column for the update_async_counters
- # helper method.
- self.async_counter_types[association_id] = column
- # Fetch the reflection.
- reflection = self.reflect_on_association(association_id)
- parent_class = reflection.klass
- # Let's make the column read-only like the normal belongs_to
- # counter_cache.
- parent_class.send(:attr_readonly, column.to_sym) if defined?(parent_class) && parent_class.respond_to?(:attr_readonly)
- parent_id_column = reflection.foreign_key
- add_callbacks(parent_class.to_s, parent_id_column, column)
+ def self.included(base)
+ base.class_eval do
+ alias_method_chain :update_counters, :async
+ end
end
end
- def async_counter_types
- @async_counter_types ||= {}
+ class AsyncBelongsToAssociation < ORIG_BT
+ include AsyncUpdateCounters
end
- private
+ class AsyncBelongsToPolymorphicAssociation < ORIG_BTP
+ include AsyncUpdateCounters
+ end
+ end
- def add_callbacks(parent_class, parent_id_column, column)
- base_method_name = "async_counter_cache_#{parent_class}_#{column}"
- # Define after_create callback method.
- method_name = "#{base_method_name}_after_create".to_sym
- define_method(method_name) do
- if parent_id = send(parent_id_column)
- ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, :increment)
+ module AssociationReflectionTweaks
+ def association_class_with_async
+ if macro == :belongs_to && options[:async_counter_cache]
+ if options[:polymorphic]
+ Associations::AsyncBelongsToPolymorphicAssociation
+ else
+ Associations::AsyncBelongsToAssociation
end
+ else
+ association_class_without_async
end
- after_commit(method_name, :on => :create)
- # Define after_destroy callback method.
- method_name = "#{base_method_name}_after_destroy".to_sym
- define_method(method_name) do
- if parent_id = send(parent_id_column)
- ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, :decrement)
- end
+ end
+
+ def counter_cache_column_with_async
+ if options[:async_counter_cache] == true
+ "#{active_record.name.demodulize.underscore.pluralize}_count"
+ elsif options[:async_counter_cache]
+ options[:async_counter_cache].to_s
+ else
+ counter_cache_column_without_async
end
- after_commit(method_name, :on => :destroy)
end
- def async_counter_cache_column(opt)
- opt === true ? "#{self.table_name}_count" : opt
+ def self.included(base)
+ base.class_eval do
+ alias_method_chain :association_class, :async
+ alias_method_chain :counter_cache_column, :async
+ end
end
end
- def self.included(base)
- base.extend(ClassMethods)
+ module AsyncBuilderBehavior
+ def build_with_async
+ reflection = build_without_async
+ if options[:async_counter_cache] && options[:counter_cache]
+ raise 'Do not mix `:async_counter_cache` and `:counter_cache`.'
+ end
+ if options[:async_counter_cache]
+ add_async_counter_cache_callbacks(reflection)
+ end
+ reflection
+ end
+
+ def self.included(base)
+ base.class_eval do
+ alias_method_chain :build, :async
+ self.valid_options += [:async_counter_cache]
+ end
+ end
+
+ private
+
+ def add_async_counter_cache_callbacks(reflection)
+ cache_column = reflection.counter_cache_column
+ name = self.name
+
+ method_name = "belongs_to_async_counter_cache_on_create_for_#{name}"
+ model.redefine_method(method_name) do
+ record = send(name)
+ ArResqueCounterCache::IncrementCountersWorker.cache_and_enqueue(
+ record.class.to_s,
+ record.id,
+ cache_column,
+ :increment
+ ) unless record.nil?
+ end
+ model.after_commit(method_name, :on => :create)
+
+ method_name = "belongs_to_async_counter_cache_on_destroy_for_#{name}"
+ model.redefine_method(method_name) do
+ record = send(name)
+ ArResqueCounterCache::IncrementCountersWorker.cache_and_enqueue(
+ record.class.to_s,
+ record.id,
+ cache_column,
+ :decrement
+ ) unless record.nil?
+ end
+ model.after_commit(method_name, :on => :destroy)
+
+ model.send(
+ :module_eval,
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)",
+ __FILE__,
+ __LINE__
+ )
+ end
end
end
end
+
+ActiveRecord::Reflection::AssociationReflection.send(
+ :include,
+ ArResqueCounterCache::ActiveRecord::AssociationReflectionTweaks
+)
+ActiveRecord::Associations::Builder::BelongsTo.send(
+ :include,
+ ArResqueCounterCache::ActiveRecord::AsyncBuilderBehavior
+)
View
6 lib/ar-resque-counter-cache/increment_counters_worker.rb
@@ -1,7 +1,7 @@
require 'resque'
require 'resque-lock-timeout'
-module ArAsyncCounterCache
+module ArResqueCounterCache
# The default Resque queue is :counter_caches.
def self.resque_job_queue=(queue)
@@ -25,7 +25,7 @@ def self.redis=(redis)
end
end
- # ArAsyncCounterCache will very quickly increment a counter cache in Redis,
+ # ArResqueCounterCache will very quickly increment a counter cache in Redis,
# which will then later be updated by a Resque job. Using
# require-lock-timeout, we can ensure that only one job per ___ is running
# at a time.
@@ -43,7 +43,7 @@ def self.cache_and_enqueue(parent_class, id, column, direction)
elsif direction == :decrement
redis.decr(key)
else
- raise ArgumentError, "Must call ArAsyncCounterCache::IncrementCountersWorker with :increment or :decrement"
+ raise ArgumentError, "Must call ArResqueCounterCache::IncrementCountersWorker with :increment or :decrement"
end
::Resque.enqueue(self, parent_class, id, column)
end
View
4 lib/ar-resque-counter-cache/version.rb
@@ -1,3 +1,3 @@
-module ArAsyncCounterCache
- VERSION = "2.1.0"
+module ArResqueCounterCache
+ VERSION = '3.0.0.rc1'
end
View
8 spec/ar_resque_counter_cache/active_record_spec.rb
@@ -1,20 +1,20 @@
require 'spec_helper'
-describe ArAsyncCounterCache::ActiveRecord do
+describe ArResqueCounterCache::ActiveRecord do
context "callbacks" do
subject { User.create(:name => "Susan") }
it "should increment" do
- ArAsyncCounterCache::IncrementCountersWorker.should_receive(:cache_and_enqueue).with("User", subject.id, "posts_count", :increment)
+ ArResqueCounterCache::IncrementCountersWorker.should_receive(:cache_and_enqueue).with("User", subject.id, "posts_count", :increment)
subject.posts.create(:body => "I have a cat!")
end
it "should increment" do
- ArAsyncCounterCache::IncrementCountersWorker.stub(:cache_and_enqueue)
+ ArResqueCounterCache::IncrementCountersWorker.stub(:cache_and_enqueue)
post = subject.posts.create(:body => "I have a cat!")
- ArAsyncCounterCache::IncrementCountersWorker.should_receive(:cache_and_enqueue).with("User", subject.id, "posts_count", :decrement)
+ ArResqueCounterCache::IncrementCountersWorker.should_receive(:cache_and_enqueue).with("User", subject.id, "posts_count", :decrement)
post.destroy
end
end
View
10 spec/integration_spec.rb
@@ -28,7 +28,7 @@
# 3 for comments incrementing posts' comments counts
Resque.size(:testing).should == 8
- # [ArAsyncCounterCache::IncrementCountersWorker, "User", 1, "posts_count"]
+ # [ArResqueCounterCache::IncrementCountersWorker, "User", 1, "posts_count"]
perform_next_job
@user1.reload.posts_count.should == 2
@user1.reload.comments_count.should == 0
@@ -37,7 +37,7 @@
@post1.reload.count_of_comments.should == 0
@post2.reload.count_of_comments.should == 0
- # [ArAsyncCounterCache::IncrementCountersWorker, "Post", 1, "count_of_comments"]
+ # [ArResqueCounterCache::IncrementCountersWorker, "Post", 1, "count_of_comments"]
perform_next_job
@user1.reload.posts_count.should == 2
@user1.reload.comments_count.should == 0
@@ -46,7 +46,7 @@
@post1.reload.count_of_comments.should == 2
@post2.reload.count_of_comments.should == 0
- # [ArAsyncCounterCache::IncrementCountersWorker, "User", 2, "comments_count"]
+ # [ArResqueCounterCache::IncrementCountersWorker, "User", 2, "comments_count"]
perform_next_job
@user1.reload.posts_count.should == 2
@user1.reload.comments_count.should == 0
@@ -55,7 +55,7 @@
@post1.reload.count_of_comments.should == 2
@post2.reload.count_of_comments.should == 0
- # [ArAsyncCounterCache::IncrementCountersWorker, "User", 1, "comments_count"]
+ # [ArResqueCounterCache::IncrementCountersWorker, "User", 1, "comments_count"]
perform_next_job
@user1.reload.posts_count.should == 2
@user1.reload.comments_count.should == 1
@@ -64,7 +64,7 @@
@post1.reload.count_of_comments.should == 2
@post2.reload.count_of_comments.should == 0
- # [ArAsyncCounterCache::IncrementCountersWorker, "Post", 2, "count_of_comments"]
+ # [ArResqueCounterCache::IncrementCountersWorker, "Post", 2, "count_of_comments"]
perform_next_job
@user1.reload.posts_count.should == 2
@user1.reload.comments_count.should == 1
View
2  spec/spec_helper.rb
@@ -32,7 +32,7 @@
RSpec.configure do |config|
config.before(:all) do
- ArAsyncCounterCache.resque_job_queue = :testing
+ ArResqueCounterCache.resque_job_queue = :testing
end
config.before(:each) do
ActiveRecord::Base.silence { CreateModelsForTest.migrate(:up) }
Please sign in to comment.
Something went wrong with that request. Please try again.