ar-resque-counter-cache 0.0.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.
@@ -0,0 +1,81 @@
1
+ ar-resque-counter-cache (formerly ar-async-counter-cache)
2
+ ---------------------------------------------------------
3
+
4
+ This gem allows you to update ActiveRecord (2.3.x) counter cache columns
5
+ asynchronously using Resque (http://github.com/defunkt/resque). You may want to
6
+ do this in situations where you want really speedy inserts and have models that
7
+ "belong_to" many different parents; that is, you want the request making the
8
+ INSERT to return before waiting for the many UPDATE...SET counter cache SQL
9
+ queries to finish. You may also want to use this gem to avoid "Mysql::Error:
10
+ Lock wait timeout exceeded" issues: if you have a lot of children being created
11
+ at a time for a single parent row, MySQL can run into lock timeouts while
12
+ waiting for parent row to update its counter cache over and over. A while ago,
13
+ I remember
14
+ [seeing](http://robots.thoughtbot.com/post/159805685/tuning-the-toad) that
15
+ Thoughtbot was having a similar issue in its Hoptoad service...
16
+
17
+ How does ar-resque-counter-cache address these issues? It uses Redis as a
18
+ temporary counter cache and Resque to actually update the counter cache column
19
+ sometime in the future. For example, let's say a single Post gets 1000 comments
20
+ very quickly. This will set a key in Redis indicating that there is a delta of
21
+ +1000 for that Post's comments_count column. It will also queue 1000 Resque
22
+ jobs. This is where resque-lock-timeout comes in. Only one of those jobs will
23
+ be allowed to run at a time. Once a job acquires the lock it removes all other
24
+ instances of that job from the queue (see
25
+ IncrementCountersWorker.around\_perform\_lock1).
26
+
27
+ You use it like such:
28
+
29
+ class User < ActiveRecord::Base
30
+ has_many :comments
31
+ has_many :posts
32
+ end
33
+
34
+ class Post < ActiveRecord::Base
35
+ belongs_to :user, :async_counter_cache => true
36
+ has_many :comments
37
+ end
38
+
39
+ class Comment < ActiveRecord::Base
40
+ belongs_to :user, :async_counter_cache => true
41
+ belongs_to :post, :async_counter_cache => "count_of_comments"
42
+ end
43
+
44
+ Notice, you may specify the name of the counter cache column just as you can
45
+ with the normal belongs_to `:counter_cache` option. You also may not use both
46
+ the `:async_counter_cache` and `:counter_cache` options in the same belongs_to
47
+ call.
48
+
49
+ All you should need to do is require this gem in your project that uses
50
+ ActiveRecord and you should be good to go;
51
+
52
+ e.g. In your Gemfile:
53
+
54
+ gem 'ar-resque-counter-cache', 'x.x.x'
55
+
56
+ and then in RAILS_ROOT/config/environment.rb somewhere:
57
+
58
+ require 'ar-resque-counter-cache'
59
+
60
+ By default, the Resque job is placed on the `:counter_caches` queue:
61
+
62
+ @queue = :counter_caches
63
+
64
+ However, you can change this:
65
+
66
+ in RAILS_ROOT/config/environment.rb somewhere:
67
+
68
+ ArAsyncCounterCache.resque_job_queue = :low_priority
69
+
70
+ `ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue` can also be
71
+ used to increment/decrement arbitrary counter cache columns (outside of
72
+ belongs_to associations):
73
+
74
+ ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(klass, id, column, direction)
75
+
76
+ Where:
77
+
78
+ * `klass` is the Class of the ActiveRecord object
79
+ * `id` is the id of the object
80
+ * `column` is the counter cache column
81
+ * `direction` is either `:increment` or `:decrement`
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "ar-resque-counter-cache"
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
+ gemspec.email = "aaron.gibralter@gmail.com"
8
+ gemspec.homepage = "http://github.com/agibralter/ar-resque-counter-cache"
9
+ gemspec.authors = ["Aaron Gibralter"]
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")
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: gem install jeweler"
18
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ar-resque-counter-cache}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Gibralter"]
12
+ s.date = %q{2010-10-13}
13
+ s.description = %q{Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).}
14
+ s.email = %q{aaron.gibralter@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ "README.md",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "ar-resque-counter-cache.gemspec",
23
+ "lib/ar-resque-counter-cache.rb",
24
+ "lib/ar_resque_counter_cache/active_record.rb",
25
+ "lib/ar_resque_counter_cache/increment_counters_worker.rb",
26
+ "spec/ar_resque_counter_cache/active_record_spec.rb",
27
+ "spec/integration_spec.rb",
28
+ "spec/models.rb",
29
+ "spec/redis-test.conf",
30
+ "spec/spec_helper.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/agibralter/ar-resque-counter-cache}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.7}
36
+ s.summary = %q{Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).}
37
+ s.test_files = [
38
+ "spec/ar_resque_counter_cache/active_record_spec.rb",
39
+ "spec/integration_spec.rb",
40
+ "spec/models.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<activerecord>, ["~> 2.3.5"])
50
+ s.add_runtime_dependency(%q<resque>, ["~> 1.10.0"])
51
+ s.add_runtime_dependency(%q<resque-lock-timeout>, ["~> 0.2.1"])
52
+ else
53
+ s.add_dependency(%q<activerecord>, ["~> 2.3.5"])
54
+ s.add_dependency(%q<resque>, ["~> 1.10.0"])
55
+ s.add_dependency(%q<resque-lock-timeout>, ["~> 0.2.1"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<activerecord>, ["~> 2.3.5"])
59
+ s.add_dependency(%q<resque>, ["~> 1.10.0"])
60
+ s.add_dependency(%q<resque-lock-timeout>, ["~> 0.2.1"])
61
+ end
62
+ end
63
+
@@ -0,0 +1,5 @@
1
+ require 'active_record'
2
+ require 'ar_resque_counter_cache/increment_counters_worker'
3
+ require 'ar_resque_counter_cache/active_record'
4
+
5
+ ActiveRecord::Base.send(:include, ArAsyncCounterCache::ActiveRecord)
@@ -0,0 +1,72 @@
1
+ module ArAsyncCounterCache
2
+
3
+ module ActiveRecord
4
+
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)
12
+ end
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def belongs_to(association_id, options={})
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
29
+ # Let's make the column read-only like the normal belongs_to
30
+ # 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)
34
+ end
35
+ end
36
+
37
+ def async_counter_types
38
+ @async_counter_types ||= {}
39
+ end
40
+
41
+ private
42
+
43
+ def add_callbacks(parent_class, parent_id_column, column)
44
+ base_method_name = "async_counter_cache_#{parent_class}_#{column}"
45
+ # Define after_create callback method.
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)
50
+ end
51
+ end
52
+ after_create(method_name)
53
+ # Define after_destroy callback method.
54
+ method_name = "#{base_method_name}_after_destroy".to_sym
55
+ define_method(method_name) do
56
+ if parent_id = send(parent_id_column)
57
+ ArAsyncCounterCache::IncrementCountersWorker.cache_and_enqueue(parent_class, parent_id, column, :decrement)
58
+ end
59
+ end
60
+ after_destroy(method_name)
61
+ end
62
+
63
+ def async_counter_cache_column(opt)
64
+ opt === true ? "#{self.table_name}_count" : opt
65
+ end
66
+ end
67
+
68
+ def self.included(base)
69
+ base.extend(ClassMethods)
70
+ end
71
+ end
72
+ 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-resque-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
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe ArAsyncCounterCache::ActiveRecord do
4
+
5
+ context "callbacks" do
6
+
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!")
12
+ end
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
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe "integration" do
4
+
5
+ before(:each) do
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ @user1 = User.create(:name => "Susan")
9
+ @user2 = User.create(:name => "Bob")
10
+ @post1 = @user1.posts.create(:body => "I have a cat!")
11
+ @post2 = @user1.posts.create(:body => "I have a mouse!")
12
+ @comment1 = @post1.comments.create(:body => "Your cat sucks!", :user => @user2)
13
+ @comment2 = @post1.comments.create(:body => "No it doesn't!", :user => @user1)
14
+ @comment3 = @post2.comments.create(:body => "Your mouse also sucks!", :user => @user2)
15
+ end
16
+
17
+ it "should increment/decrement counter caches asynchronously in batches" do
18
+ # Should be asynchronous...
19
+ @user1.posts_count.should == 0
20
+ @user1.comments_count.should == 0
21
+ @user2.posts_count.should == 0
22
+ @user2.comments_count.should == 0
23
+ @post1.count_of_comments.should == 0
24
+ @post2.count_of_comments.should == 0
25
+
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"]
32
+ perform_next_job
33
+ @user1.reload.posts_count.should == 2
34
+ @user1.reload.comments_count.should == 0
35
+ @user2.reload.posts_count.should == 0
36
+ @user2.reload.comments_count.should == 0
37
+ @post1.reload.count_of_comments.should == 0
38
+ @post2.reload.count_of_comments.should == 0
39
+
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
69
+ @user1.reload.posts_count.should == 2
70
+ @user1.reload.comments_count.should == 1
71
+ @user2.reload.posts_count.should == 0
72
+ @user2.reload.comments_count.should == 2
73
+ @post1.reload.count_of_comments.should == 2
74
+ @post2.reload.count_of_comments.should == 1
75
+
76
+ # Should be done...
77
+ Resque.size(:testing).should == 0
78
+
79
+ @comment1.destroy
80
+ @comment2.destroy
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
+
93
+ @user1.reload.posts_count.should == 2
94
+ @user1.reload.comments_count.should == 0
95
+ @user2.reload.posts_count.should == 0
96
+ @user2.reload.comments_count.should == 0
97
+ @post1.reload.count_of_comments.should == 0
98
+ @post2.reload.count_of_comments.should == 0
99
+ end
100
+
101
+ def perform_next_job
102
+ return unless job = @worker.reserve
103
+ @worker.perform(job)
104
+ @worker.done_working
105
+ end
106
+
107
+ def perform_all_jobs
108
+ while job = @worker.reserve
109
+ @worker.perform(job)
110
+ @worker.done_working
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,42 @@
1
+ ActiveRecord::Migration.verbose = false
2
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
3
+
4
+ class CreateModelsForTest < ActiveRecord::Migration
5
+ def self.up
6
+ create_table :users do |t|
7
+ t.string :name
8
+ t.integer :posts_count, :default => 0
9
+ t.integer :comments_count, :default => 0
10
+ end
11
+ create_table :posts do |t|
12
+ t.string :body
13
+ t.belongs_to :user
14
+ t.integer :count_of_comments, :default => 0
15
+ end
16
+ create_table :comments do |t|
17
+ t.string :body
18
+ t.belongs_to :user
19
+ t.belongs_to :post
20
+ end
21
+ end
22
+ def self.down
23
+ drop_table(:users)
24
+ drop_table(:posts)
25
+ drop_table(:comments)
26
+ end
27
+ end
28
+
29
+ class User < ActiveRecord::Base
30
+ has_many :comments
31
+ has_many :posts
32
+ end
33
+
34
+ class Post < ActiveRecord::Base
35
+ belongs_to :user, :async_counter_cache => true
36
+ has_many :comments
37
+ end
38
+
39
+ class Comment < ActiveRecord::Base
40
+ belongs_to :user, :async_counter_cache => true
41
+ belongs_to :post, :async_counter_cache => "count_of_comments"
42
+ end
@@ -0,0 +1,13 @@
1
+ daemonize yes
2
+ pidfile ./redis-test.pid
3
+ port 9736
4
+ timeout 300
5
+ save 900 1
6
+ save 300 10
7
+ save 60 10000
8
+ dbfilename ./dump.rdb
9
+ dir .
10
+ loglevel debug
11
+ logfile stdout
12
+ databases 16
13
+ glueoutputbuf yes
@@ -0,0 +1,44 @@
1
+ spec_dir = File.expand_path(File.dirname(__FILE__))
2
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
3
+
4
+ require 'rubygems'
5
+ # Ensure resque for tests.
6
+ require 'resque'
7
+ require 'ar-resque-counter-cache'
8
+ require 'spec'
9
+ require 'models'
10
+
11
+ cwd = Dir.getwd
12
+ Dir.chdir(spec_dir)
13
+
14
+ if !system("which redis-server")
15
+ puts '', "** can't find `redis-server` in your path"
16
+ abort ''
17
+ end
18
+
19
+ at_exit do
20
+ if (pid = `cat redis-test.pid`.strip) =~ /^\d+$/
21
+ puts "Killing test redis server with pid #{pid}..."
22
+ `rm -f dump.rdb`
23
+ `rm -f redis-test.pid`
24
+ Process.kill("KILL", pid.to_i)
25
+ Dir.chdir(cwd)
26
+ end
27
+ end
28
+
29
+ puts "Starting redis for testing at localhost:9736..."
30
+ `redis-server #{spec_dir}/redis-test.conf`
31
+ Resque.redis = '127.0.0.1:9736'
32
+
33
+ Spec::Runner.configure do |config|
34
+ config.before(:all) do
35
+ ArAsyncCounterCache.resque_job_queue = :testing
36
+ end
37
+ config.before(:each) do
38
+ ActiveRecord::Base.silence { CreateModelsForTest.migrate(:up) }
39
+ Resque.redis.flushall
40
+ end
41
+ config.after(:each) do
42
+ ActiveRecord::Base.silence { CreateModelsForTest.migrate(:down) }
43
+ end
44
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar-resque-counter-cache
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Aaron Gibralter
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-13 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 9
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 5
34
+ version: 2.3.5
35
+ type: :runtime
36
+ version_requirements: *id001
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).
70
+ email: aaron.gibralter@gmail.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files:
76
+ - README.md
77
+ files:
78
+ - README.md
79
+ - Rakefile
80
+ - VERSION
81
+ - ar-resque-counter-cache.gemspec
82
+ - lib/ar-resque-counter-cache.rb
83
+ - lib/ar_resque_counter_cache/active_record.rb
84
+ - lib/ar_resque_counter_cache/increment_counters_worker.rb
85
+ - spec/ar_resque_counter_cache/active_record_spec.rb
86
+ - spec/integration_spec.rb
87
+ - spec/models.rb
88
+ - spec/redis-test.conf
89
+ - spec/spec_helper.rb
90
+ has_rdoc: true
91
+ homepage: http://github.com/agibralter/ar-resque-counter-cache
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --charset=UTF-8
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.3.7
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Increment ActiveRecord's counter cache column asynchronously using Resque (and resque-lock-timeout).
124
+ test_files:
125
+ - spec/ar_resque_counter_cache/active_record_spec.rb
126
+ - spec/integration_spec.rb
127
+ - spec/models.rb
128
+ - spec/spec_helper.rb