sidekiq-activerecord 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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.idea
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sidekiq-activerecord.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sidekiq-activerecord (0.0.1)
5
+ activerecord (~> 4.1.1)
6
+ sidekiq (>= 2.16.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (4.1.1)
12
+ activesupport (= 4.1.1)
13
+ builder (~> 3.1)
14
+ activerecord (4.1.1)
15
+ activemodel (= 4.1.1)
16
+ activesupport (= 4.1.1)
17
+ arel (~> 5.0.0)
18
+ activesupport (4.1.1)
19
+ i18n (~> 0.6, >= 0.6.9)
20
+ json (~> 1.7, >= 1.7.7)
21
+ minitest (~> 5.1)
22
+ thread_safe (~> 0.1)
23
+ tzinfo (~> 1.1)
24
+ arel (5.0.1.20140414130214)
25
+ builder (3.2.2)
26
+ celluloid (0.15.2)
27
+ timers (~> 1.1.0)
28
+ connection_pool (2.0.0)
29
+ database_cleaner (1.2.0)
30
+ diff-lcs (1.2.5)
31
+ factory_girl (4.2.0)
32
+ activesupport (>= 3.0.0)
33
+ i18n (0.6.9)
34
+ json (1.8.1)
35
+ minitest (5.3.4)
36
+ redis (3.0.7)
37
+ redis-namespace (1.4.1)
38
+ redis (~> 3.0.4)
39
+ rspec (2.14.1)
40
+ rspec-core (~> 2.14.0)
41
+ rspec-expectations (~> 2.14.0)
42
+ rspec-mocks (~> 2.14.0)
43
+ rspec-core (2.14.8)
44
+ rspec-expectations (2.14.5)
45
+ diff-lcs (>= 1.1.3, < 2.0)
46
+ rspec-mocks (2.14.6)
47
+ sidekiq (3.0.2)
48
+ celluloid (>= 0.15.2)
49
+ connection_pool (>= 2.0.0)
50
+ json
51
+ redis (>= 3.0.6)
52
+ redis-namespace (>= 1.3.1)
53
+ sqlite3 (1.3.9)
54
+ thread_safe (0.3.3)
55
+ timers (1.1.0)
56
+ tzinfo (1.1.0)
57
+ thread_safe (~> 0.1)
58
+
59
+ PLATFORMS
60
+ ruby
61
+
62
+ DEPENDENCIES
63
+ database_cleaner (~> 1.2.0)
64
+ factory_girl (~> 4.0)
65
+ rspec (>= 2.14)
66
+ sidekiq-activerecord!
67
+ sqlite3 (~> 1.3.9)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Adam Farhi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Sidekiq::Activerecord
2
+
3
+ Encapsulates various interactions between Sidekiq and ActiveRecord.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'sidekiq-activerecord'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install sidekiq-activerecord
18
+
19
+ # Usage
20
+
21
+ If you've been using Sidekiq for a while, you've probably noticed a recurring pattern in your workers;
22
+
23
+ ## Child-Parent Workers
24
+ A parent worker which goes over some model collection and enqueues a child worker for each model in the collection.
25
+
26
+ ```ruby
27
+ class ParentWorker
28
+ include Sidekiq::Worker
29
+
30
+ def perform
31
+ User.active.each do |user|
32
+ ChildWorker.perform_async(user.id)
33
+ end
34
+ end
35
+
36
+ end
37
+ ```
38
+
39
+ ## Sidekiq::ManagerWorker - Example
40
+
41
+ ```ruby
42
+ class UserTaskWorker
43
+ include Sidekiq::TaskWorker
44
+ end
45
+
46
+ class UserSyncer
47
+ include Sidekiq::ManagerWorker
48
+
49
+ sidekiq_delegate_task_to :user_task_worker # or UserTaskWorker
50
+ sidekiq_manager_options :batch_size => 500,
51
+ :identifier_key => :user_token,
52
+ :additional_keys => [:status]
53
+ end
54
+
55
+ UserSyncer.perform_query_async(User.active, :batch_size => 300)
56
+ ```
57
+
58
+ ## Model Task Workers
59
+
60
+ A worker which gets a model.id (like ChildWorker above) loads it, validates it and runs some logic on the model.
61
+
62
+ ```ruby
63
+ class ModelTaskWorker
64
+ include Sidekiq::Worker
65
+
66
+ def perform(user_id)
67
+ user = User.find(user_id)
68
+ return unless user.present?
69
+ return unless user.active?
70
+
71
+ UserService.run(user)
72
+ end
73
+
74
+ end
75
+ ```
76
+
77
+ ## Sidekiq::TaskWorker - Example
78
+
79
+ ```ruby
80
+ class UserMailerTaskWorker
81
+ include Sidekiq::TaskWorker
82
+
83
+ sidekiq_task_model :user_model # or UserModel
84
+ sidekiq_task_options :identifier_key => :token
85
+
86
+ def perform_on_model(user, email_type)
87
+ UserMailer.deliver_registration_confirmation(user, email_type)
88
+ end
89
+
90
+ # optional
91
+ def not_found_model(token)
92
+ Log.error "User not found for token:#{token}"
93
+ end
94
+
95
+ # optional
96
+ def model_valid?(user)
97
+ user.active?
98
+ end
99
+
100
+ # optional
101
+ def invalid_model(user)
102
+ Log.error "User #{user.token} is invalid"
103
+ end
104
+
105
+ end
106
+
107
+
108
+ UserMailerTaskWorker.perform(user.id, :new_email)
109
+ ```
110
+
111
+ ## Contributing
112
+
113
+ 1. Fork it ( http://github.com/<my-github-username>/sidekiq-activerecord/fork )
114
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
115
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
116
+ 4. Push to the branch (`git push origin my-new-feature`)
117
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ module Sidekiq
2
+ module Activerecord
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+
2
+ # gems
3
+ require 'sidekiq'
4
+ require 'active_record'
5
+
6
+ require "sidekiq/activerecord/version"
7
+
8
+ # internal
9
+ require 'sidekiq/task_worker'
10
+ require 'sidekiq/manager_worker'
@@ -0,0 +1,144 @@
1
+ module Sidekiq
2
+ module ManagerWorker
3
+ extend Sidekiq::Worker
4
+
5
+ DEFAULT_IDENTIFIER_KEY = :id
6
+ DEFAULT_BATCH_SIZE = 1000
7
+
8
+ def self.included(base)
9
+ base.extend(ClassMethods)
10
+ base.class_attribute :sidekiq_manager_options_hash
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ # For a given model collection, it delegates each model to a sub-worker (e.g TaskWorker)
16
+ # Specify the TaskWoker with the `sidekiq_delegate_task_to` method.
17
+ #
18
+ # @param models_query ActiveRecord::Relation
19
+ # @param options Hash
20
+ # :worker_class - the worker class to delegate the task to. Alternative to the default `sidekiq_delegate_task_to`
21
+ # :identifier_key - the model identifier column. Default 'id'
22
+ # :additional_keys - additional model keys
23
+ # :batch_size - Specifies the size of the batch. Default to 1000.
24
+ #
25
+ # @example:
26
+ # class UserTaskWorker
27
+ # include Sidekiq::TaskWorker
28
+ # end
29
+ #
30
+ # class UserSyncer
31
+ # include Sidekiq::ManagerWorker
32
+ #
33
+ # sidekiq_delegate_task_to :user_task_worker # or UserTaskWorker
34
+ # sidekiq_manager_options :batch_size => 500,
35
+ # :identifier_key => :user_token,
36
+ # :additional_keys => [:status]
37
+ # end
38
+ #
39
+ # UserSyncer.perform_query_async(User.active, :batch_size => 300)
40
+ #
41
+ #
42
+ # is equivalent to doing:
43
+ # User.active.each {|user| UserTaskWorker.peform(user.id) }
44
+ #
45
+ def perform_query_async(models_query, options={})
46
+ set_runtime_options(options)
47
+ models = models_query.select(selected_attributes)
48
+ models.find_in_batches(batch_size: batch_size) do |models_batch|
49
+ model_attributes = models_batch.map { |model| model_attributes(model) }
50
+ Sidekiq::Client.push_bulk('class' => worker_class, 'args' => model_attributes)
51
+ end
52
+ # set_runtime_options(nil)
53
+ end
54
+
55
+ # @required
56
+ # The task worker to delegate to.
57
+ # @param worker_klass (Sidekiq::Worker, Symbol) - UserTaskWorker or :user_task_worker
58
+ def sidekiq_delegate_task_to(worker_klass)
59
+ if worker_klass.is_a?(String) or is_a?(Symbol)
60
+ worker_klass.to_s.split('_').collect(&:capitalize).join.constantize
61
+ else
62
+ worker_klass
63
+ end
64
+ self.get_sidekiq_manager_options[:worker_class] = worker_klass
65
+ end
66
+
67
+ # Allows customization for this type of ManagerWorker.
68
+ # Legal options:
69
+ #
70
+ # :worker_class - the worker class to delegate the task to. Alternative to `sidekiq_delegate_task_to`
71
+ # :identifier_key - the model identifier column. Default 'id'
72
+ # :additional_keys - additional model keys
73
+ # :batch_size - Specifies the size of the batch. Default to 1000.
74
+ def sidekiq_manager_options(opts={})
75
+ self.sidekiq_manager_options_hash = get_sidekiq_manager_options.merge((opts || {}).symbolize_keys!)
76
+ end
77
+
78
+
79
+ # private
80
+
81
+ def default_worker_manager_options
82
+ {
83
+ :identifier_key => DEFAULT_IDENTIFIER_KEY,
84
+ :additional_keys => [],
85
+ :worker_class => nil,
86
+ :batch_size => DEFAULT_BATCH_SIZE,
87
+ }
88
+ end
89
+
90
+ # returns the model attributes array:
91
+ # [model_id, attr1, attr2, ...]
92
+ def model_attributes(model)
93
+ additional_attributes = additional_keys.map { |key| model.send(key) }
94
+ id_attribute = model.send(identifier_key)
95
+ additional_attributes.unshift(id_attribute)
96
+ end
97
+
98
+ def selected_attributes
99
+ attrs = [identifier_key, additional_keys]
100
+ attrs << DEFAULT_IDENTIFIER_KEY unless default_identifier? # :id must be included
101
+ attrs
102
+ end
103
+
104
+ def worker_class
105
+ raise NotImplementedError.new('`worker_class` was not specified') unless manager_options[:worker_class].present?
106
+ manager_options[:worker_class]
107
+ end
108
+
109
+ def default_identifier?
110
+ identifier_key == DEFAULT_IDENTIFIER_KEY
111
+ end
112
+
113
+ def identifier_key
114
+ manager_options[:identifier_key]
115
+ end
116
+
117
+ def additional_keys
118
+ manager_options[:additional_keys]
119
+ end
120
+
121
+ def batch_size
122
+ manager_options[:batch_size]
123
+ end
124
+
125
+ def manager_options
126
+ self.get_sidekiq_manager_options.merge(runtime_options)
127
+ end
128
+
129
+ def get_sidekiq_manager_options
130
+ self.sidekiq_manager_options_hash ||= default_worker_manager_options
131
+ end
132
+
133
+ def runtime_options
134
+ @sidekiq_manager_runtime_options || {}
135
+ end
136
+
137
+ def set_runtime_options(options)
138
+ options = options.delete_if { |k, v| v.nil? } if options.present?
139
+ @sidekiq_manager_runtime_options = options
140
+ end
141
+
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,117 @@
1
+ module Sidekiq
2
+ module TaskWorker
3
+ extend Sidekiq::Worker
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ base.class_attribute :sidekiq_task_options_hash
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # @example:
13
+ # class UserMailerTaskWorker
14
+ # include Sidekiq::TaskWorker
15
+ #
16
+ # sidekiq_task_model :user_model # or UserModel
17
+ # sidekiq_task_options :identifier_key => :token
18
+ #
19
+ # def perform_on_model(user, email_type)
20
+ # UserMailer.deliver_registration_confirmation(user, email_type)
21
+ # end
22
+ #
23
+ # def not_found_model(token)
24
+ # Log.error "User not found for token:#{token}"
25
+ # end
26
+ #
27
+ # def model_valid?(user)
28
+ # user.active?
29
+ # end
30
+ #
31
+ # def invalid_model(user)
32
+ # Log.error "User #{user.token} is invalid"
33
+ # end
34
+ #
35
+ # end
36
+ #
37
+ #
38
+ # UserMailerTaskWorker.perform(user.id, :new_email)
39
+ #
40
+ def perform(identifier, *args)
41
+ model = fetch_model(identifier)
42
+ return not_found_model(identifier) unless model.present?
43
+
44
+ if model_valid?(model)
45
+ perform_on_model(model, *args)
46
+ else
47
+ invalid_model(model)
48
+ end
49
+ end
50
+
51
+ def sidekiq_task_model(model_klass)
52
+ if model_klass.is_a?(String) or model_klass.is_a?(Symbol)
53
+ model_klass = model_klass.to_s.split('_').collect(&:capitalize).join.constantize
54
+ else
55
+ model_klass
56
+ end
57
+ self.get_sidekiq_task_options[:model_class] = model_klass
58
+ end
59
+
60
+ def perform_on_model(model)
61
+ model
62
+ end
63
+
64
+ # recheck the if one of the items is still valid
65
+ def model_valid?(model)
66
+ true
67
+ end
68
+
69
+ # Hook to handel an invalid model
70
+ def invalid_model(model)
71
+ end
72
+
73
+ # Hook to handel not found model
74
+ def not_found_model(identifier)
75
+ end
76
+
77
+
78
+ # private
79
+
80
+ def fetch_model(identifier)
81
+ model_class.find_by(identifier_key => identifier)
82
+ end
83
+
84
+ def model_class
85
+ klass = self.get_sidekiq_task_options[:model_class]
86
+ raise NotImplementedError.new('`model_class` was not specified') unless klass.present?
87
+ klass
88
+ end
89
+
90
+ def identifier_key
91
+ self.get_sidekiq_task_options[:identifier_key]
92
+ end
93
+
94
+ #
95
+ # Allows customization for this type of TaskWorker.
96
+ # Legal options:
97
+ #
98
+ # :identifier_key - the model identifier column. Default 'id'
99
+ def sidekiq_task_options(opts={})
100
+ self.sidekiq_task_options_hash = get_sidekiq_task_options.merge((opts || {}).symbolize_keys!)
101
+ end
102
+
103
+ def get_sidekiq_task_options
104
+ self.sidekiq_task_options_hash ||= default_worker_task_options
105
+ end
106
+
107
+ def default_worker_task_options
108
+ {
109
+ :identifier_key => :id,
110
+ :model_class => nil
111
+ }
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sidekiq/activerecord/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sidekiq-activerecord"
8
+ spec.version = Sidekiq::Activerecord::VERSION
9
+ spec.authors = ["Adam Farhi"]
10
+ spec.email = ["yelled3@gmail.com"]
11
+ spec.summary = 'Encapsulates various interactions between Sidekiq and ActiveRecord'
12
+ spec.description = 'Encapsulates various interactions between Sidekiq and ActiveRecord'
13
+ spec.homepage = "https://github.com/yelled3/sidekiq-activerecord"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'sidekiq', '>= 2.16.0'
22
+ spec.add_dependency 'activerecord', '~> 4.1.1'
23
+
24
+ spec.add_development_dependency "rspec", ">= 2.14"
25
+ spec.add_development_dependency "database_cleaner", '~> 1.2.0'
26
+ spec.add_development_dependency 'sqlite3', '~> 1.3.9'
27
+ spec.add_development_dependency 'factory_girl', "~> 4.0"
28
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::ManagerWorker do
4
+
5
+ before do
6
+ allow(Sidekiq::Client).to receive(:push_bulk)
7
+ end
8
+
9
+ class MockUserWorker; end
10
+
11
+ let(:worker_class) { MockUserWorker }
12
+ let(:sidekiq_client) { Sidekiq::Client }
13
+
14
+ class UserManagerWorker
15
+ include Sidekiq::ManagerWorker
16
+ sidekiq_delegate_task_to MockUserWorker
17
+ end
18
+
19
+ let!(:user_1) { create(:user, :active) }
20
+ let!(:user_2) { create(:user, :active) }
21
+ let!(:user_3) { create(:user, :active) }
22
+ let!(:user_4) { create(:user, :banned) }
23
+
24
+ let(:models_query) { User.active }
25
+
26
+ describe 'perform_query_async' do
27
+
28
+ def run_worker(options = {})
29
+ UserManagerWorker.perform_query_async(models_query, options)
30
+ end
31
+
32
+ def mock_options(options)
33
+ UserManagerWorker.send(:sidekiq_manager_options, options)
34
+ end
35
+
36
+ def batch_args(*ids)
37
+ {'class' => worker_class, 'args' => ids.map{ |id| [id] }}
38
+ end
39
+
40
+ let(:model_ids) { [[user_1.id], [user_2.id], [user_3.id]] }
41
+
42
+ context 'when the worker_class is specified' do
43
+
44
+ class MockCustomWorker; end
45
+
46
+ let(:custom_worker_class) { MockCustomWorker }
47
+
48
+ def batch_args(*ids)
49
+ {'class' => custom_worker_class, 'args' => ids.map{ |id| [id] }}
50
+ end
51
+
52
+ context 'as method arguments' do
53
+
54
+ it 'pushes a bulk of all user ids for the specified worker_class' do
55
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1.id, user_2.id, user_3.id) )
56
+ run_worker({:worker_class => custom_worker_class})
57
+ end
58
+ end
59
+
60
+ context 'as sidekiq_delegate_task_to' do
61
+
62
+ around do |example|
63
+ UserManagerWorker.send(:sidekiq_delegate_task_to, custom_worker_class)
64
+ example.run
65
+ UserManagerWorker.send(:sidekiq_delegate_task_to, worker_class)
66
+ end
67
+
68
+ it 'pushes a bulk of all user ids for the specified worker_class' do
69
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1.id, user_2.id, user_3.id) )
70
+ run_worker
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ context 'when the batch size is specified' do
77
+
78
+ let(:batch_size) { 2 }
79
+
80
+ context 'as method arguments' do
81
+ it 'pushes a bulk of user ids batches' do
82
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1.id, user_2.id) )
83
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_3.id) )
84
+ run_worker({batch_size: batch_size})
85
+ end
86
+ end
87
+
88
+ context 'as sidekiq_manager_options' do
89
+
90
+ around do |example|
91
+ mock_options(:batch_size => batch_size)
92
+ example.run
93
+ mock_options(:batch_size => Sidekiq::ManagerWorker::DEFAULT_BATCH_SIZE)
94
+ end
95
+
96
+ it 'pushes a bulk of user ids batches' do
97
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1.id, user_2.id) )
98
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_3.id) )
99
+ run_worker
100
+ end
101
+ end
102
+ end
103
+
104
+ context 'when the additional_keys are specified' do
105
+
106
+ let(:additional_keys) { [:email, :status] }
107
+
108
+ def batch_args(*users)
109
+ {'class' => worker_class, 'args' => users.map{ |user| [user.id, user.email, user.status] }}
110
+ end
111
+
112
+ context 'as method arguments' do
113
+ it 'pushes a bulk of all user ids and additional_keys' do
114
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1, user_2, user_3) )
115
+ run_worker({additional_keys: additional_keys})
116
+ end
117
+ end
118
+
119
+ context 'as sidekiq_manager_options' do
120
+ around do |example|
121
+ mock_options(:additional_keys => additional_keys)
122
+ example.run
123
+ mock_options(:additional_keys => [])
124
+ end
125
+
126
+ it 'pushes a bulk of all user ids and additional_keys' do
127
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1, user_2, user_3) )
128
+ run_worker
129
+ end
130
+ end
131
+
132
+ end
133
+
134
+ context 'when the identifier_key is specified' do
135
+
136
+ def batch_args(*users)
137
+ {'class' => worker_class, 'args' => users.map{ |user| [user.email] }}
138
+ end
139
+
140
+ let(:identifier_key) { :email }
141
+
142
+ context 'as method arguments' do
143
+ it 'pushes a bulk of all user emails as the identifier_key' do
144
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1, user_2, user_3) )
145
+ run_worker({identifier_key: identifier_key})
146
+ end
147
+ end
148
+
149
+ context 'as sidekiq_manager_options' do
150
+
151
+ around do |example|
152
+ mock_options(:identifier_key => identifier_key)
153
+ example.run
154
+ mock_options(:identifier_key => Sidekiq::ManagerWorker::DEFAULT_IDENTIFIER_KEY)
155
+ end
156
+
157
+ it 'pushes a bulk of all user emails as the identifier_key' do
158
+ expect(sidekiq_client).to receive(:push_bulk).with( batch_args(user_1, user_2, user_3) )
159
+ run_worker
160
+ end
161
+ end
162
+
163
+ end
164
+
165
+ end
166
+
167
+ end
@@ -0,0 +1,150 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sidekiq::TaskWorker do
4
+
5
+ class UserTaskWorker
6
+ include Sidekiq::TaskWorker
7
+ end
8
+
9
+ let!(:user) { create(:user, :active) }
10
+
11
+ subject(:task_worker) {UserTaskWorker }
12
+
13
+ def run_worker
14
+ task_worker.perform(user.id)
15
+ end
16
+
17
+ describe 'sidekiq_task_model' do
18
+
19
+ context 'when the identifier_key is specified' do
20
+ before do
21
+ class UserTaskWorker
22
+ sidekiq_task_model User
23
+ sidekiq_task_options :identifier_key => :email
24
+ end
25
+ end
26
+
27
+ after do
28
+ class UserTaskWorker
29
+ sidekiq_task_model nil
30
+ sidekiq_task_options :identifier_key => :id
31
+ end
32
+ end
33
+
34
+ it 'sets the identifier_key' do
35
+ identifier = task_worker.send(:identifier_key)
36
+ expect(identifier).to eq :email
37
+ end
38
+
39
+ it 'calls the perform_on_model with the model' do
40
+ expect(task_worker).to receive(:perform_on_model).with(user)
41
+ task_worker.perform(user.email)
42
+ end
43
+ end
44
+
45
+ context 'when a Class is specified' do
46
+
47
+ before do
48
+ class UserTaskWorker
49
+ sidekiq_task_model User
50
+ end
51
+ end
52
+
53
+ it 'sets the model' do
54
+ klass = task_worker.send(:model_class)
55
+ expect(klass).to eq User
56
+ end
57
+
58
+ end
59
+
60
+ context 'when a class name is specified' do
61
+ before do
62
+ class UserTaskWorker
63
+ sidekiq_task_model :user
64
+ end
65
+ end
66
+
67
+ it 'sets the model' do
68
+ klass = task_worker.send(:model_class)
69
+ expect(klass).to eq User
70
+ end
71
+
72
+ context 'when the model is not found' do
73
+
74
+ let(:trash_id) { user.id + 10 }
75
+
76
+ def run_worker
77
+ task_worker.perform(trash_id)
78
+ end
79
+
80
+ it 'calls the not_found_model hook' do
81
+ expect(task_worker).to receive(:not_found_model).with(trash_id)
82
+ run_worker
83
+ end
84
+
85
+ it 'skips the perform_on_model' do
86
+ expect(task_worker).to_not receive(:perform_on_model)
87
+ run_worker
88
+ end
89
+ end
90
+
91
+ context 'when the mode is found' do
92
+
93
+ context 'when the model validation is specified' do
94
+ it 'calls the model_valid? hook' do
95
+ expect(task_worker).to receive(:model_valid?).with(user)
96
+ run_worker
97
+ end
98
+ end
99
+
100
+ context 'when the model is valid' do
101
+
102
+ before do
103
+ allow(task_worker).to receive(:model_valid?).and_return(true)
104
+ end
105
+
106
+ it 'calls the perform_on_model with the model' do
107
+ expect(task_worker).to receive(:perform_on_model).with(user)
108
+ run_worker
109
+ end
110
+
111
+ describe 'perform_on_model' do
112
+ context 'when passing only the model identifier' do
113
+ it 'calls the perform_on_model with the model' do
114
+ expect(task_worker).to receive(:perform_on_model).with(user)
115
+ run_worker
116
+ end
117
+ end
118
+
119
+ context 'when passing additional arguments' do
120
+ it 'calls the perform_on_model with the model' do
121
+ expect(task_worker).to receive(:perform_on_model).with(user, user.email)
122
+ task_worker.perform(user.id, user.email)
123
+ end
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+ context 'when the model is invalid' do
130
+
131
+ before do
132
+ allow(task_worker).to receive(:model_valid?).and_return(false)
133
+ end
134
+
135
+ it 'calls the invalid_model hook' do
136
+ expect(task_worker).to receive(:invalid_model).with(user)
137
+ run_worker
138
+ end
139
+
140
+ it 'skips the perform_on_model' do
141
+ expect(task_worker).to_not receive(:perform_on_model)
142
+ run_worker
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,36 @@
1
+ require "sidekiq"
2
+ require "sidekiq/activerecord"
3
+ require 'factory_girl'
4
+ require 'database_cleaner'
5
+ require 'support'
6
+
7
+ RSpec.configure do |config|
8
+ config.alias_example_to :expect_it
9
+
10
+ # config.full_backtrace = true
11
+
12
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
13
+ # examples within a transaction, remove the following line or assign false
14
+ # instead of true.
15
+ # config.use_transactional_fixtures = true
16
+
17
+ config.expect_with :rspec do |config|
18
+ config.syntax = :expect
19
+ end
20
+
21
+ config.include FactoryGirl::Syntax::Methods # Don't need to write FactoryGirl.create => create
22
+
23
+ config.before(:suite) do
24
+ DatabaseCleaner.strategy = :transaction
25
+ DatabaseCleaner.clean_with(:truncation)
26
+ end
27
+
28
+ config.before(:each) do
29
+ DatabaseCleaner.start
30
+ end
31
+
32
+ config.after(:each) do
33
+ DatabaseCleaner.clean
34
+ end
35
+ end
36
+
data/spec/support.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "active_record"
2
+
3
+ def set_database
4
+ db_config = {:adapter => "sqlite3", :database => ":memory:"}
5
+ ActiveRecord::Base.establish_connection(db_config)
6
+ connection = ActiveRecord::Base.connection
7
+
8
+ connection.create_table :users, force: true do |t|
9
+ t.string :name
10
+ t.string :email
11
+ t.string :status
12
+ t.timestamps
13
+ end
14
+ end
15
+
16
+ set_database
17
+
18
+ class User < ActiveRecord::Base
19
+ scope :active, -> { where(:status => :active) }
20
+ scope :banned, -> { where(:status => :banned) }
21
+ end
22
+
23
+
24
+ FactoryGirl.define do
25
+ factory :user do
26
+
27
+ sequence(:name) { |n| "name-#{n}" }
28
+ sequence(:email) { |n| "email-#{n}" }
29
+
30
+ trait :active do
31
+ status 'active'
32
+ end
33
+
34
+ trait :banned do
35
+ status 'banned'
36
+ end
37
+
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq-activerecord
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Adam Farhi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-05-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sidekiq
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.16.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.16.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 4.1.1
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 4.1.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ - !ruby/object:Gem::Dependency
63
+ name: database_cleaner
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.2.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.2.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: sqlite3
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.9
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.3.9
94
+ - !ruby/object:Gem::Dependency
95
+ name: factory_girl
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '4.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '4.0'
110
+ description: Encapsulates various interactions between Sidekiq and ActiveRecord
111
+ email:
112
+ - yelled3@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - .rspec
119
+ - Gemfile
120
+ - Gemfile.lock
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - lib/sidekiq/activerecord.rb
125
+ - lib/sidekiq/activerecord/version.rb
126
+ - lib/sidekiq/manager_worker.rb
127
+ - lib/sidekiq/task_worker.rb
128
+ - sidekiq-activerecord.gemspec
129
+ - spec/lib/sidekiq/manager_worker.rb
130
+ - spec/lib/sidekiq/task_worker.rb
131
+ - spec/spec_helper.rb
132
+ - spec/support.rb
133
+ homepage: https://github.com/yelled3/sidekiq-activerecord
134
+ licenses:
135
+ - MIT
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 1.8.24
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: Encapsulates various interactions between Sidekiq and ActiveRecord
158
+ test_files:
159
+ - spec/lib/sidekiq/manager_worker.rb
160
+ - spec/lib/sidekiq/task_worker.rb
161
+ - spec/spec_helper.rb
162
+ - spec/support.rb
163
+ has_rdoc: