ar-resque-counter-cache 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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