ar-async-counter-cache 0.0.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ ar-async-counter-cache
2
+ ----------------------
3
+
4
+ This gem allows you to update ActiveRecord counter cache columns
5
+ asynchronously using Resque (http://github.com/defunkt/resque). You may want
6
+ to do this in situations where you want really speedy inserts and have models
7
+ that "belong_to" many different parents and also allows for fast inserts of
8
+ many children for the same parent.
9
+
10
+ For example, let's say a single Post gets 1000 comments very quickly. This
11
+ will set a key in Redis indicating that there is a delta of +1000 for that
12
+ Post's comments_count column. It will also queue 1000 Resque jobs. This is
13
+ where resque-lock-timeout comes in. Only one of those jobs will be allowed to
14
+ run at a time. Once a job acquires the lock it removes all other instances of
15
+ that job from the queue (see IncrementCountersWorker.around_perform_lock1).
16
+
17
+ You use it like such:
18
+
19
+ class User < ActiveRecord::Base
20
+ has_many :comments
21
+ has_many :posts
22
+ end
23
+
24
+ class Post < ActiveRecord::Base
25
+ belongs_to :user, :async_counter_cache => true
26
+ has_many :comments
27
+ end
28
+
29
+ class Comment < ActiveRecord::Base
30
+ belongs_to :user, :async_counter_cache => true
31
+ belongs_to :post, :async_counter_cache => "count_of_comments"
32
+ end
33
+
34
+ Notice, you may specify the name of the counter cache column just as you can
35
+ with the normal belongs_to `:counter_cache` option. You also may not use both
36
+ the `:async_counter_cache` and `:counter_cache` options in the same belongs_to
37
+ call.
38
+
39
+ All you should need to do is require this gem in your project that uses
40
+ ActiveRecord and you should be good to go;
41
+
42
+ e.g. In your Gemfile:
43
+
44
+ gem 'ar-async-counter-cache', '0.1.0'
45
+
46
+ and then in RAILS_ROOT/config/environment.rb somewhere:
47
+
48
+ require 'ar-async-counter-cache'
49
+
50
+ By default, the Resque job is placed on the `:counter_caches` queue:
51
+
52
+ @queue = :counter_caches
53
+
54
+ However, you can change this:
55
+
56
+ in RAILS_ROOT/config/environment.rb somewhere:
57
+
58
+ ArAsyncCounterCache.resque_job_queue = :low_priority
59
+
60
+ `ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue` can also be
61
+ used to increment/decrement arbitrary counter cache columns (outside of
62
+ belongs_to associations):
63
+
64
+ ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(klass, id, column, direction)
65
+
66
+ Where:
67
+
68
+ * `klass` is the Class of the ActiveRecord object
69
+ * `id` is the id of the object
70
+ * `column` is the counter cache column
71
+ * `direction` is either `:increment` or `:decrement`
data/Rakefile CHANGED
@@ -2,12 +2,15 @@ begin
2
2
  require 'jeweler'
3
3
  Jeweler::Tasks.new do |gemspec|
4
4
  gemspec.name = "ar-async-counter-cache"
5
- gemspec.summary = "Increment ActiveRecord's counter cache column asynchronously (using Resque)."
6
- gemspec.description = "Increment ActiveRecord's counter cache column asynchronously (using Resque)."
5
+ gemspec.summary = "Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout)."
6
+ gemspec.description = "Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout)."
7
7
  gemspec.email = "aaron.gibralter@gmail.com"
8
8
  gemspec.homepage = "http://github.com/agibralter/ar-async-counter-cache"
9
9
  gemspec.authors = ["Aaron Gibralter"]
10
- gemspec.add_dependency("activerecord", ">= 2.3.5")
10
+ gemspec.add_dependency("activerecord", "~> 2.3.5")
11
+ gemspec.add_dependency("resque", "~> 1.10.0")
12
+ gemspec.add_dependency("resque-lock-timeout", "~> 0.2.1")
13
+ gemspec.files.exclude("pkg")
11
14
  end
12
15
  Jeweler::GemcutterTasks.new
13
16
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.1.1
@@ -5,26 +5,25 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ar-async-counter-cache}
8
- s.version = "0.0.3"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Aaron Gibralter"]
12
- s.date = %q{2010-06-17}
13
- s.description = %q{Increment ActiveRecord's counter cache column asynchronously (using Resque).}
12
+ s.date = %q{2010-10-06}
13
+ s.description = %q{Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).}
14
14
  s.email = %q{aaron.gibralter@gmail.com}
15
15
  s.extra_rdoc_files = [
16
- "README"
16
+ "README.md"
17
17
  ]
18
18
  s.files = [
19
19
  ".gitignore",
20
- "README",
20
+ "README.md",
21
21
  "Rakefile",
22
22
  "VERSION",
23
23
  "ar-async-counter-cache.gemspec",
24
24
  "lib/ar-async-counter-cache.rb",
25
25
  "lib/ar_async_counter_cache/active_record.rb",
26
- "lib/ar_async_counter_cache/increment_counters_job.rb",
27
- "pkg/ar-async-counter-cache-0.0.1.gem",
26
+ "lib/ar_async_counter_cache/increment_counters_worker.rb",
28
27
  "spec/ar_async_counter_cache/active_record_spec.rb",
29
28
  "spec/integration_spec.rb",
30
29
  "spec/models.rb",
@@ -34,8 +33,8 @@ Gem::Specification.new do |s|
34
33
  s.homepage = %q{http://github.com/agibralter/ar-async-counter-cache}
35
34
  s.rdoc_options = ["--charset=UTF-8"]
36
35
  s.require_paths = ["lib"]
37
- s.rubygems_version = %q{1.3.6}
38
- s.summary = %q{Increment ActiveRecord's counter cache column asynchronously (using Resque).}
36
+ s.rubygems_version = %q{1.3.7}
37
+ s.summary = %q{Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).}
39
38
  s.test_files = [
40
39
  "spec/ar_async_counter_cache/active_record_spec.rb",
41
40
  "spec/integration_spec.rb",
@@ -47,13 +46,19 @@ Gem::Specification.new do |s|
47
46
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
47
  s.specification_version = 3
49
48
 
50
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
- s.add_runtime_dependency(%q<activerecord>, [">= 2.3.5"])
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<activerecord>, ["~> 2.3.5"])
51
+ s.add_runtime_dependency(%q<resque>, ["~> 1.10.0"])
52
+ s.add_runtime_dependency(%q<resque-lock-timeout>, ["~> 0.2.1"])
52
53
  else
53
- s.add_dependency(%q<activerecord>, [">= 2.3.5"])
54
+ s.add_dependency(%q<activerecord>, ["~> 2.3.5"])
55
+ s.add_dependency(%q<resque>, ["~> 1.10.0"])
56
+ s.add_dependency(%q<resque-lock-timeout>, ["~> 0.2.1"])
54
57
  end
55
58
  else
56
- s.add_dependency(%q<activerecord>, [">= 2.3.5"])
59
+ s.add_dependency(%q<activerecord>, ["~> 2.3.5"])
60
+ s.add_dependency(%q<resque>, ["~> 1.10.0"])
61
+ s.add_dependency(%q<resque-lock-timeout>, ["~> 0.2.1"])
57
62
  end
58
63
  end
59
64
 
@@ -1,11 +1,5 @@
1
1
  require 'active_record'
2
-
3
- begin
4
- require 'resque'
5
- require 'ar_async_counter_cache/increment_counters_job'
6
- rescue LoadError
7
- end
8
-
2
+ require 'ar_async_counter_cache/increment_counters_worker'
9
3
  require 'ar_async_counter_cache/active_record'
10
4
 
11
5
  ActiveRecord::Base.send(:include, ArAsyncCounterCache::ActiveRecord)
@@ -2,17 +2,13 @@ module ArAsyncCounterCache
2
2
 
3
3
  module ActiveRecord
4
4
 
5
- def async_increment_counters
6
- raise %{
7
- Since you don't have resque installed, please define #{self.class.to_s}#async_increment_counters.
8
- Basically it should queue a job that calls #{self.class.to_s}#update_async_counters(:increment).
9
- }.gsub(/\s+/, ' ').strip
10
- end
11
-
12
- def update_async_counters(dir, *parent_types)
13
- (parent_types.empty? ? self.class.async_counter_types.keys : parent_types).each do |parent_type|
14
- if (col = self.class.async_counter_types[parent_type]) && (parent = send(parent_type))
15
- parent.class.send("#{dir}_counter", col, parent.id)
5
+ def update_async_counters(dir, *association_ids)
6
+ association_ids.each do |association_id|
7
+ reflection = self.class.reflect_on_association(association_id)
8
+ parent_class = reflection.klass
9
+ column = self.class.async_counter_types[association_id]
10
+ if parent_id = send(reflection.primary_key_name)
11
+ ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, dir)
16
12
  end
17
13
  end
18
14
  end
@@ -20,21 +16,22 @@ module ArAsyncCounterCache
20
16
  module ClassMethods
21
17
 
22
18
  def belongs_to(association_id, options={})
23
- if col = async_counter_cache_column(options.delete(:async_counter_cache))
24
- # Add callbacks only once: update_async_counters updates all the
25
- # model's belongs_to counter caches; we do this because there's no
26
- # need to add excessive messages on the async queue.
27
- add_callbacks if self.async_counter_types.empty?
28
- # Store async counter so that update_async_counters knows which
29
- # counters to update.
30
- self.async_counter_types[association_id] = col
19
+ column = async_counter_cache_column(options.delete(:async_counter_cache))
20
+ raise "Please don't use both async_counter_cache and counter_cache." if column && options[:counter_cache]
21
+ super(association_id, options)
22
+ if column
23
+ # Store the async_counter_cache column for the update_async_counters
24
+ # helper method.
25
+ self.async_counter_types[association_id] = column
26
+ # Fetch the reflection.
27
+ reflection = self.reflect_on_association(association_id)
28
+ parent_class = reflection.klass
31
29
  # Let's make the column read-only like the normal belongs_to
32
30
  # counter_cache.
33
- parrent_class = association_id.to_s.classify.constantize
34
- parrent_class.send(:attr_readonly, col.to_sym) if defined?(parrent_class) && parrent_class.respond_to?(:attr_readonly)
35
- raise "Please don't use both async_counter_cache and counter_cache." if options[:counter_cache]
31
+ parent_class.send(:attr_readonly, column.to_sym) if defined?(parent_class) && parent_class.respond_to?(:attr_readonly)
32
+ parent_id_column = reflection.primary_key_name
33
+ add_callbacks(parent_class.to_s, parent_id_column, column)
36
34
  end
37
- super(association_id, options)
38
35
  end
39
36
 
40
37
  def async_counter_types
@@ -43,23 +40,22 @@ module ArAsyncCounterCache
43
40
 
44
41
  private
45
42
 
46
- def add_callbacks
43
+ def add_callbacks(parent_class, parent_id_column, column)
44
+ base_method_name = "async_counter_cache_#{parent_class}_#{column}"
47
45
  # Define after_create callback method.
48
- method_name = "belongs_to_async_counter_cache_after_create".to_sym
49
- if defined?(ArAsyncCounterCache::IncrementCountersJob)
50
- define_method(method_name) do
51
- Resque.enqueue(ArAsyncCounterCache::IncrementCountersJob, self.class.to_s, self.id)
52
- end
53
- else
54
- define_method(method_name) do
55
- self.async_increment_counters
46
+ method_name = "#{base_method_name}_after_create".to_sym
47
+ define_method(method_name) do
48
+ if parent_id = send(parent_id_column)
49
+ ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, :increment)
56
50
  end
57
51
  end
58
52
  after_create(method_name)
59
53
  # Define before_destroy callback method.
60
- method_name = "belongs_to_async_counter_cache_before_destroy".to_sym
54
+ method_name = "#{base_method_name}_before_destroy".to_sym
61
55
  define_method(method_name) do
62
- update_async_counters(:decrement)
56
+ if parent_id = send(parent_id_column)
57
+ ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, :decrement)
58
+ end
63
59
  end
64
60
  before_destroy(method_name)
65
61
  end
@@ -0,0 +1,108 @@
1
+ require 'resque'
2
+ require 'resque-lock-timeout'
3
+
4
+ module ArAsyncCounterCache
5
+
6
+ # The default Resque queue is :counter_caches.
7
+ def self.resque_job_queue=(queue)
8
+ IncrementCountersWorker.class_eval do
9
+ @queue = queue
10
+ end
11
+ end
12
+
13
+ # The default lock_timeout is 60 seconds.
14
+ def self.resque_lock_timeout=(lock_timeout)
15
+ IncrementCountersWorker.class_eval do
16
+ @lock_timeout = lock_timeout
17
+ end
18
+ end
19
+
20
+ # If you don't want to use Resque's Redis connection to store the temporary
21
+ # counter caches, you can set a different connection here.
22
+ def self.redis=(redis)
23
+ IncrementCountersWorker.class_eval do
24
+ @redis = redis
25
+ end
26
+ end
27
+
28
+ # ArAsyncCounterCache will very quickly increment a counter cache in Redis,
29
+ # which will then later be updated by a Resque job. Using
30
+ # require-lock-timeout, we can ensure that only one job per ___ is running
31
+ # at a time.
32
+ class IncrementCountersWorker
33
+
34
+ extend ::Resque::Plugins::LockTimeout
35
+ @queue = :counter_caches
36
+ @lock_timeout = 60
37
+
38
+ def self.cache_and_enqueue(parent_class, id, column, direction)
39
+ parent_class = parent_class.to_s
40
+ key = cache_key(parent_class, id, column)
41
+ if direction == :increment
42
+ redis.incr(key)
43
+ elsif direction == :decrement
44
+ redis.decr(key)
45
+ else
46
+ raise ArgumentError, "Must call ArAsyncCounterCache::IncrementCountersWorker with :increment or :decrement"
47
+ end
48
+ ::Resque.enqueue(self, parent_class, id, column)
49
+ end
50
+
51
+ def self.redis
52
+ @redis || ::Resque.redis
53
+ end
54
+
55
+ # args: (parent_class, id, column)
56
+ def self.identifier(*args)
57
+ args.join('-')
58
+ end
59
+
60
+ # Try again later if lock is in use.
61
+ def self.lock_failed(*args)
62
+ ::Resque.enqueue(self, *args)
63
+ end
64
+
65
+ # args: (parent_class, id, column)
66
+ def self.cache_key(*args)
67
+ "ar-async-counter-cache:#{identifier(*args)}"
68
+ end
69
+
70
+ # The name of this method ensures that it runs within around_perform_lock.
71
+ #
72
+ # We've leveraged resque-lock-timeout to ensure that only one job is
73
+ # running at a time. Now, this around filter essentially ensures that only
74
+ # one job per parent-column can sit on the queue at once. Since the
75
+ # cache_key entry in redis stores the most up-to-date delta for the
76
+ # parent's counter cache, we don't have to actually perform the
77
+ # Klass.update_counters for every increment/decrement. We can batch
78
+ # process!
79
+ def self.around_perform_lock1(*args)
80
+ # Remove all other instances of this job (with the same args) from the
81
+ # queue. Uses LREM (http://code.google.com/p/redis/wiki/LremCommand) which
82
+ # takes the form: "LREM key count value" and if count == 0 removes all
83
+ # instances of value from the list.
84
+ redis_job_value = ::Resque.encode(:class => self.to_s, :args => args)
85
+ ::Resque.redis.lrem("queue:#{@queue}", 0, redis_job_value)
86
+ yield
87
+ end
88
+
89
+ def self.perform(parent_class, id, column)
90
+ key = cache_key(parent_class, id, column)
91
+ if (delta = redis.getset(key, 0).to_i) != 0
92
+ begin
93
+ parent_class = ::Resque.constantize(parent_class)
94
+ parent_class.find(id)
95
+ parent_class.update_counters(id, column => delta)
96
+ rescue Exception => e
97
+ # If anything happens, set back the counter cache.
98
+ if delta > 0
99
+ redis.incrby(key, delta)
100
+ elsif delta < 0
101
+ redis.decrby(key, -delta)
102
+ end
103
+ raise e
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -2,22 +2,20 @@ require 'spec_helper'
2
2
 
3
3
  describe ArAsyncCounterCache::ActiveRecord do
4
4
 
5
- describe ArAsyncCounterCache::ActiveRecord::ClassMethods do
6
- it "should set async_counter_types" do
7
- Post.async_counter_types.should == {:user => "posts_count"}
8
- Comment.async_counter_types.should == {:user => "comments_count", :post => "count_of_comments"}
9
- end
10
- end
11
-
12
5
  context "callbacks" do
13
6
 
14
- before(:each) do
15
- @user = User.create(:name => "Susan")
7
+ subject { User.create(:name => "Susan") }
8
+
9
+ it "should increment" do
10
+ ArAsyncCounterCache::IncrementCountersWorker.should_receive(:cache_and_enqueue).with("User", subject.id, "posts_count", :increment)
11
+ subject.posts.create(:body => "I have a cat!")
16
12
  end
17
-
18
- it "should queue job" do
19
- Resque.should_receive(:enqueue).with(ArAsyncCounterCache::IncrementCountersJob, "Post", an_instance_of(Fixnum))
20
- @user.posts.create(:body => "I have a cat!")
13
+
14
+ it "should increment" do
15
+ ArAsyncCounterCache::IncrementCountersWorker.stub(:cache_and_enqueue)
16
+ post = subject.posts.create(:body => "I have a cat!")
17
+ ArAsyncCounterCache::IncrementCountersWorker.should_receive(:cache_and_enqueue).with("User", subject.id, "posts_count", :decrement)
18
+ post.destroy
21
19
  end
22
20
  end
23
21
  end
@@ -5,7 +5,6 @@ describe "integration" do
5
5
  before(:each) do
6
6
  @worker = Resque::Worker.new(:testing)
7
7
  @worker.register_worker
8
- # Models...
9
8
  @user1 = User.create(:name => "Susan")
10
9
  @user2 = User.create(:name => "Bob")
11
10
  @post1 = @user1.posts.create(:body => "I have a cat!")
@@ -15,40 +14,82 @@ describe "integration" do
15
14
  @comment3 = @post2.comments.create(:body => "Your mouse also sucks!", :user => @user2)
16
15
  end
17
16
 
18
- it "should set countercache asynchronously" do
17
+ it "should increment/decrement counter caches asynchronously in batches" do
18
+ # Should be asynchronous...
19
19
  @user1.posts_count.should == 0
20
20
  @user1.comments_count.should == 0
21
21
  @user2.posts_count.should == 0
22
22
  @user2.comments_count.should == 0
23
23
  @post1.count_of_comments.should == 0
24
24
  @post2.count_of_comments.should == 0
25
- end
26
25
 
27
- it "should perform one at a time" do
26
+ # 2 for posts incrementing users' posts counts
27
+ # 3 for comments incrementing users' comments counts
28
+ # 3 for comments incrementing posts' comments counts
29
+ Resque.size(:testing).should == 8
30
+
31
+ # [ArAsyncCounterCache::IncrementCountersWorker, "User", 1, "posts_count"]
28
32
  perform_next_job
29
- @user1.reload.posts_count.should == 1
33
+ @user1.reload.posts_count.should == 2
30
34
  @user1.reload.comments_count.should == 0
31
35
  @user2.reload.posts_count.should == 0
32
36
  @user2.reload.comments_count.should == 0
33
37
  @post1.reload.count_of_comments.should == 0
34
38
  @post2.reload.count_of_comments.should == 0
35
- end
36
39
 
37
- it "should update all counters once the jobs are done" do
38
- perform_all_jobs
40
+ # [ArAsyncCounterCache::IncrementCountersWorker, "User", 2, "comments_count"]
41
+ perform_next_job
42
+ @user1.reload.posts_count.should == 2
43
+ @user1.reload.comments_count.should == 0
44
+ @user2.reload.posts_count.should == 0
45
+ @user2.reload.comments_count.should == 2
46
+ @post1.reload.count_of_comments.should == 0
47
+ @post2.reload.count_of_comments.should == 0
48
+
49
+ # [ArAsyncCounterCache::IncrementCountersWorker, "Post", 1, "count_of_comments"]
50
+ perform_next_job
51
+ @user1.reload.posts_count.should == 2
52
+ @user1.reload.comments_count.should == 0
53
+ @user2.reload.posts_count.should == 0
54
+ @user2.reload.comments_count.should == 2
55
+ @post1.reload.count_of_comments.should == 2
56
+ @post2.reload.count_of_comments.should == 0
57
+
58
+ # [ArAsyncCounterCache::IncrementCountersWorker, "User", 1, "comments_count"]
59
+ perform_next_job
60
+ @user1.reload.posts_count.should == 2
61
+ @user1.reload.comments_count.should == 1
62
+ @user2.reload.posts_count.should == 0
63
+ @user2.reload.comments_count.should == 2
64
+ @post1.reload.count_of_comments.should == 2
65
+ @post2.reload.count_of_comments.should == 0
66
+
67
+ # [ArAsyncCounterCache::IncrementCountersWorker, "Post", 2, "count_of_comments"]
68
+ perform_next_job
39
69
  @user1.reload.posts_count.should == 2
40
70
  @user1.reload.comments_count.should == 1
41
71
  @user2.reload.posts_count.should == 0
42
72
  @user2.reload.comments_count.should == 2
43
73
  @post1.reload.count_of_comments.should == 2
44
74
  @post2.reload.count_of_comments.should == 1
45
- end
46
75
 
47
- it "should decrement synchronously" do
48
- perform_all_jobs
76
+ # Should be done...
77
+ Resque.size(:testing).should == 0
78
+
49
79
  @comment1.destroy
50
80
  @comment2.destroy
51
81
  @comment3.destroy
82
+
83
+ # Should be asynchronous...
84
+ @user1.reload.posts_count.should == 2
85
+ @user1.reload.comments_count.should == 1
86
+ @user2.reload.posts_count.should == 0
87
+ @user2.reload.comments_count.should == 2
88
+ @post1.reload.count_of_comments.should == 2
89
+ @post2.reload.count_of_comments.should == 1
90
+
91
+ perform_all_jobs
92
+
52
93
  @user1.reload.posts_count.should == 2
53
94
  @user1.reload.comments_count.should == 0
54
95
  @user2.reload.posts_count.should == 0
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar-async-counter-cache
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 25
4
5
  prerelease: false
5
6
  segments:
6
7
  - 0
7
- - 0
8
- - 3
9
- version: 0.0.3
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Aaron Gibralter
@@ -14,16 +15,18 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-06-17 00:00:00 -04:00
18
+ date: 2010-10-06 00:00:00 -04:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: activerecord
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
- - - ">="
27
+ - - ~>
26
28
  - !ruby/object:Gem::Version
29
+ hash: 9
27
30
  segments:
28
31
  - 2
29
32
  - 3
@@ -31,24 +34,55 @@ dependencies:
31
34
  version: 2.3.5
32
35
  type: :runtime
33
36
  version_requirements: *id001
34
- description: Increment ActiveRecord's counter cache column asynchronously (using Resque).
37
+ - !ruby/object:Gem::Dependency
38
+ name: resque
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 63
46
+ segments:
47
+ - 1
48
+ - 10
49
+ - 0
50
+ version: 1.10.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: resque-lock-timeout
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 21
62
+ segments:
63
+ - 0
64
+ - 2
65
+ - 1
66
+ version: 0.2.1
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ description: Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).
35
70
  email: aaron.gibralter@gmail.com
36
71
  executables: []
37
72
 
38
73
  extensions: []
39
74
 
40
75
  extra_rdoc_files:
41
- - README
76
+ - README.md
42
77
  files:
43
78
  - .gitignore
44
- - README
79
+ - README.md
45
80
  - Rakefile
46
81
  - VERSION
47
82
  - ar-async-counter-cache.gemspec
48
83
  - lib/ar-async-counter-cache.rb
49
84
  - lib/ar_async_counter_cache/active_record.rb
50
- - lib/ar_async_counter_cache/increment_counters_job.rb
51
- - pkg/ar-async-counter-cache-0.0.1.gem
85
+ - lib/ar_async_counter_cache/increment_counters_worker.rb
52
86
  - spec/ar_async_counter_cache/active_record_spec.rb
53
87
  - spec/integration_spec.rb
54
88
  - spec/models.rb
@@ -64,26 +98,30 @@ rdoc_options:
64
98
  require_paths:
65
99
  - lib
66
100
  required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
67
102
  requirements:
68
103
  - - ">="
69
104
  - !ruby/object:Gem::Version
105
+ hash: 3
70
106
  segments:
71
107
  - 0
72
108
  version: "0"
73
109
  required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
74
111
  requirements:
75
112
  - - ">="
76
113
  - !ruby/object:Gem::Version
114
+ hash: 3
77
115
  segments:
78
116
  - 0
79
117
  version: "0"
80
118
  requirements: []
81
119
 
82
120
  rubyforge_project:
83
- rubygems_version: 1.3.6
121
+ rubygems_version: 1.3.7
84
122
  signing_key:
85
123
  specification_version: 3
86
- summary: Increment ActiveRecord's counter cache column asynchronously (using Resque).
124
+ summary: Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).
87
125
  test_files:
88
126
  - spec/ar_async_counter_cache/active_record_spec.rb
89
127
  - spec/integration_spec.rb
data/README DELETED
@@ -1,68 +0,0 @@
1
- ar-async-counter-cache
2
- ----------------------
3
-
4
- This gem allows you to update ActiveRecord counter cache columns
5
- asynchronously. You may want to do this in situations where you want really
6
- speedy inserts and have models that "belong_to" many different parents.
7
-
8
- You use it like such:
9
-
10
- class User < ActiveRecord::Base
11
- has_many :comments
12
- has_many :posts
13
- end
14
-
15
- class Post < ActiveRecord::Base
16
- belongs_to :user, :async_counter_cache => true
17
- has_many :comments
18
- end
19
-
20
- class Comment < ActiveRecord::Base
21
- belongs_to :user, :async_counter_cache => true
22
- belongs_to :post, :async_counter_cache => "count_of_comments"
23
- end
24
-
25
- Notice, you may specify the name of the counter cache column just as you can
26
- with the normal belongs_to :counter_cache option. You also may not use both
27
- the :async_counter_cache and :counter_cache options in the same belongs_to
28
- call.
29
-
30
- All you should need to do is require this gem in your project that uses
31
- ActiveRecord and you should be good to go;
32
-
33
- e.g. In your Gemfile:
34
-
35
- gem 'ar-async-counter-cache', '0.0.1'
36
-
37
- and then in RAILS_ROOT/config/environment.rb somewhere:
38
-
39
- require 'ar-async-counter-cache'
40
-
41
- This gem has built-in support for Resque (http://github.com/defunkt/resque).
42
- Al you need is resque in your loadpath:
43
-
44
- e.g. In your Gemfile:
45
-
46
- gem 'resque', '1.9.4'
47
-
48
- By default, the Resque job is placed on the :counter_caches queue:
49
-
50
- @queue = :counter_caches
51
-
52
- However, you can change this:
53
-
54
- in RAILS_ROOT/config/environment.rb somewhere:
55
-
56
- ArAsyncCounterCache.resque_job_queue = :low_priority
57
-
58
- If you don't want to use Resque, you must define one instance method for your
59
- models using ar-async-counter-cache:
60
-
61
- e.g.:
62
-
63
- Comment#async_increment_counters
64
-
65
- That method should pass a message onto a queue for a background processor to
66
- handle later. That background processor should call:
67
-
68
- Comment#update_async_counters(:increment)
@@ -1,26 +0,0 @@
1
- module ArAsyncCounterCache
2
-
3
- def self.resque_job_queue=(sym)
4
- ArAsyncCounterCache::IncrementCountersJob.class_eval do
5
- @queue = sym
6
- end
7
- end
8
-
9
- class IncrementCountersJob
10
- @queue = :counter_caches
11
-
12
- # Take advantage of resque-retry if possible.
13
- begin
14
- require 'resque-retry'
15
- require 'active_record/base'
16
- extend Resque::Plugins::ExponentialBackoff
17
- @retry_exceptions = [::ActiveRecord::RecordNotFound]
18
- @backoff_strategy = [0, 10, 100]
19
- rescue LoadError
20
- end
21
-
22
- def self.perform(klass_name, id)
23
- Object.const_get(klass_name).find(id).update_async_counters(:increment)
24
- end
25
- end
26
- end
Binary file