actionable 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -7
- data/README.md +5 -0
- data/Rakefile +7 -1
- data/actionable.gemspec +2 -2
- data/lib/actionable.rb +2 -0
- data/lib/actionable/recurrence.rb +61 -0
- data/lib/actionable/sweep.rb +1 -0
- data/lib/actionable/target.rb +0 -1
- data/lib/actionable/version.rb +1 -1
- data/spec/{work_spec.rb → job_spec.rb} +21 -11
- data/spec/recurrence_spec.rb +99 -0
- data/spec/spec_helper.rb +8 -1
- data/spec/support/recurring_test_job.rb +12 -0
- data/spec/sweep_spec.rb +28 -24
- data/spec/{schedule_spec.rb → target_spec.rb} +0 -0
- metadata +19 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45a9bd1c736c4e4a5ad2b7346e96604fe762252f
|
4
|
+
data.tar.gz: afd9fa6c64ff5ca69285019c34605ed46c3abbdd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d63c6d2439e5923342173b1b998e122876c4d4533e24782fd2f4e5898ff0e57ed64dcbfe4306622684f5f1872227c203918fd0e579e5c41af2b502f27ccd61ec
|
7
|
+
data.tar.gz: b64f3d371ce3c95716162ed3ebf215351176253497ff7837d051b6966643aae9210101031a7d5b0b4b974738b076aa4d49a1f786327d21fc1e3d1e9eedb95df9
|
data/Gemfile
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'resque', github: '
|
4
|
-
gem 'redis'
|
5
|
-
gem 'redis-namespace'
|
6
|
-
|
3
|
+
gem 'resque', github: 'resque/resque', branch: 'master'
|
7
4
|
gem 'mongoid', github: 'mongoid/mongoid', branch: 'master'
|
8
|
-
gem 'moped'
|
9
|
-
gem 'origin'
|
10
|
-
|
11
5
|
gemspec
|
data/README.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
|
1
2
|
# Actionable
|
2
3
|
|
4
|
+
[![Code Climate](https://codeclimate.com/github/dluxemburg/actionable.png)](https://codeclimate.com/github/dluxemburg/actionable)
|
5
|
+
|
6
|
+
<img src="https://semaphoreapp.com/api/v1/projects/d32a2e65068b7b995bd9e9e2f8beb99a64c7f98d/36068/badge.png">
|
7
|
+
|
3
8
|
TODO: Write a gem description
|
4
9
|
|
5
10
|
## Installation
|
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require 'actionable/tasks'
|
3
3
|
require 'resque/tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
4
5
|
|
5
6
|
task "resque:setup" do
|
6
7
|
puts File.expand_path('../spec/support/*.rb', __FILE__)
|
@@ -13,4 +14,9 @@ task "resque:setup" do
|
|
13
14
|
Mongoid.configure do |config|
|
14
15
|
config.connect_to('actionable_test')
|
15
16
|
end
|
16
|
-
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
RSpec::Core::RakeTask.new(:spec)
|
21
|
+
|
22
|
+
task :default => :spec
|
data/actionable.gemspec
CHANGED
@@ -27,8 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency "rb-fsevent"
|
28
28
|
spec.add_development_dependency "terminal-notifier-guard"
|
29
29
|
|
30
|
-
spec.add_dependency "resque"
|
31
|
-
spec.add_dependency "mongoid"
|
30
|
+
spec.add_dependency "resque", "~> 2.0.0.pre.1"
|
31
|
+
spec.add_dependency "mongoid", "~> 4.0.0"
|
32
32
|
spec.add_dependency "rufus-scheduler"
|
33
33
|
|
34
34
|
|
data/lib/actionable.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rufus/scheduler'
|
2
|
+
|
3
|
+
module Actionable
|
4
|
+
class Recurrence
|
5
|
+
|
6
|
+
include Mongoid::Document
|
7
|
+
include Actionable::Target
|
8
|
+
|
9
|
+
store_in(collection: 'recurring_actionables')
|
10
|
+
|
11
|
+
field :recurrence_string
|
12
|
+
field :job_class, type: String
|
13
|
+
field :payload, type: Hash, default: {}
|
14
|
+
|
15
|
+
after_create :schedule_next
|
16
|
+
|
17
|
+
validate :valid_recurrence_string
|
18
|
+
validate :valid_job_class
|
19
|
+
|
20
|
+
def schedule_next
|
21
|
+
schedule_actionable(next_run_time,job_class,payload)
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_scheduled?
|
25
|
+
Actionable::Action.where(status: :scheduled, target_id: id).exists?
|
26
|
+
end
|
27
|
+
|
28
|
+
def ensure_scheduled
|
29
|
+
schedule_next unless next_scheduled?
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def next_run_time
|
35
|
+
Rufus::CronLine.new(recurrence_string).next_time.to_datetime
|
36
|
+
end
|
37
|
+
|
38
|
+
def job_class
|
39
|
+
unless read_attribute(:job_class).blank?
|
40
|
+
read_attribute(:job_class).constantize
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_recurrence_string
|
45
|
+
begin
|
46
|
+
next_run_time
|
47
|
+
rescue => e
|
48
|
+
errors.add(:recurrence_string,e.message)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_job_class
|
53
|
+
begin
|
54
|
+
job_class
|
55
|
+
rescue => e
|
56
|
+
errors.add(:job_class,e.message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/actionable/sweep.rb
CHANGED
data/lib/actionable/target.rb
CHANGED
data/lib/actionable/version.rb
CHANGED
@@ -1,20 +1,30 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
class TestWorker
|
4
|
+
|
5
|
+
attr_accessor :pid
|
6
|
+
|
7
|
+
def spawn
|
8
|
+
@pid = Kernel.spawn('bundle exec rake resque:work QUEUES=actionable,actionable_sweep')
|
9
|
+
end
|
10
|
+
|
11
|
+
def kill
|
12
|
+
system("kill -9 #{pid}")
|
13
|
+
end
|
3
14
|
|
4
|
-
def start_test_worker
|
5
|
-
spawn('bundle exec rake resque:work PIDFILE=resque.pid QUEUES=actionable,actionable_sweep INTERVAL=1')
|
6
15
|
end
|
7
16
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
17
|
+
def run_test_worker(id)
|
18
|
+
w = TestWorker.new
|
19
|
+
w.spawn
|
20
|
+
poll_for_finished(id)
|
21
|
+
w.kill
|
12
22
|
end
|
13
23
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
24
|
+
def poll_for_finished(id)
|
25
|
+
while TestModel.find(id).reload.number == 3
|
26
|
+
sleep(1)
|
27
|
+
end
|
18
28
|
end
|
19
29
|
|
20
30
|
describe Actionable::Job do
|
@@ -44,7 +54,7 @@ describe Actionable::Job do
|
|
44
54
|
|
45
55
|
it "should be executed by a worker in another processes" do
|
46
56
|
id = target.id
|
47
|
-
run_test_worker
|
57
|
+
run_test_worker(id)
|
48
58
|
expect(TestModel.find(id).number).to eq(6)
|
49
59
|
expect(TestModel.find(id).name).to eq('blue square')
|
50
60
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Actionable::Recurrence do
|
4
|
+
|
5
|
+
let(:every_five){ "*/5 * * * *" }
|
6
|
+
let(:new_args){
|
7
|
+
{ recurrence_string:every_five, job_class: RecurringTestJob }
|
8
|
+
}
|
9
|
+
|
10
|
+
let(:recurrence) {
|
11
|
+
Actionable::Recurrence.create(new_args)
|
12
|
+
}
|
13
|
+
|
14
|
+
it "should be instantiated with a recurrence string and a job class" do
|
15
|
+
r = Actionable::Recurrence.new(new_args)
|
16
|
+
expect(r).to be_valid
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should refuse to save with a bad recurrence string" do
|
20
|
+
new_args[:recurrence_string] = '* D * L * . *'
|
21
|
+
r = Actionable::Recurrence.new(new_args)
|
22
|
+
expect(r).to_not be_valid
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should refuse to save with a non-existent class" do
|
26
|
+
new_args[:job_class] = "NonExistentJob"
|
27
|
+
r = Actionable::Recurrence.new(new_args)
|
28
|
+
expect(r).to_not be_valid
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "should not add to the database until saved" do
|
33
|
+
r = Actionable::Recurrence.new(new_args)
|
34
|
+
expect(Actionable::Recurrence.count).to eq(0)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should add to the database afer being committed" do
|
38
|
+
r = Actionable::Recurrence.new(new_args)
|
39
|
+
r.save
|
40
|
+
expect(Actionable::Recurrence.count).to eq(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should know its job class" do
|
44
|
+
expect(recurrence.send(:job_class)).to eq(RecurringTestJob)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should create an actionable upon creation" do
|
48
|
+
expect(Actionable::Action.count).to eq(0)
|
49
|
+
Actionable::Recurrence.create(new_args)
|
50
|
+
expect(Actionable::Action.count).to eq(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when time passes" do
|
54
|
+
|
55
|
+
before do
|
56
|
+
Resque.inline = true
|
57
|
+
recurrence.valid?
|
58
|
+
Timecop.travel(5.minutes.from_now)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should run the first action and create a new one on sweep" do
|
62
|
+
expect(Actionable::Action.count).to eq(1)
|
63
|
+
Actionable::Sweep.perform
|
64
|
+
expect(Actionable::Action.count).to eq(2)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when no time passes" do
|
70
|
+
|
71
|
+
before do
|
72
|
+
Resque.inline = true
|
73
|
+
recurrence.valid?
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not run the first action or create a new one" do
|
77
|
+
expect(Actionable::Action.count).to eq(1)
|
78
|
+
Actionable::Sweep.perform
|
79
|
+
expect(Actionable::Action.count).to eq(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
context "over the course of an hour" do
|
85
|
+
|
86
|
+
it "should create and perform the job 11 times" do
|
87
|
+
RecurringTestJob.should_receive(:go).exactly(12).times
|
88
|
+
Timecop.travel(1.hour.from_now.change(minutes:0))
|
89
|
+
recurrence.valid?
|
90
|
+
60.times do
|
91
|
+
Actionable::Sweep.perform
|
92
|
+
Timecop.travel(1.minute.from_now)
|
93
|
+
end
|
94
|
+
expect(Actionable::Action.count).to be_within(1).of(12)
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -19,10 +19,17 @@ end
|
|
19
19
|
|
20
20
|
RSpec.configure do |config|
|
21
21
|
|
22
|
-
config.
|
22
|
+
config.after(:each) do
|
23
23
|
Resque.purge!
|
24
24
|
Mongoid.purge!
|
25
25
|
Timecop.return
|
26
26
|
end
|
27
27
|
|
28
|
+
config.before(:all) do
|
29
|
+
Resque.purge!
|
30
|
+
Mongoid.purge!
|
31
|
+
Timecop.return
|
32
|
+
Resque.inline = false
|
33
|
+
end
|
34
|
+
|
28
35
|
end
|
data/spec/sweep_spec.rb
CHANGED
@@ -6,31 +6,35 @@ describe Actionable::Sweep do
|
|
6
6
|
TestModel.create
|
7
7
|
}
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
9
|
+
context "single jobs" do
|
10
|
+
|
11
|
+
before do
|
12
|
+
target.schedule_actionable(1.hour.from_now,TestJob)
|
13
|
+
Timecop.travel(2.hours.from_now)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should leave one job in the to do list before being performed" do
|
17
|
+
expect(Resque.size('actionable')).to eq(0)
|
18
|
+
expect(Actionable::Action.to_do.count).to eq(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should move one job from the to do list to the queue after being performed" do
|
22
|
+
Actionable::Sweep.perform
|
23
|
+
expect(Actionable::Action.to_do.count).to eq(0)
|
24
|
+
expect(Resque.size('actionable')).to eq(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should enqueue a job that knows the id for its target" do
|
28
|
+
Actionable::Sweep.perform
|
29
|
+
expect(Resque.queue('actionable').pop['args'].first).to eq(target.actionables.first.id.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should update the actionable's status to 'enqueued'" do
|
33
|
+
expect(target.actionables.first.status).to eq(:scheduled)
|
34
|
+
Actionable::Sweep.perform
|
35
|
+
expect(target.actionables.first.status).to eq(:enqueued)
|
36
|
+
end
|
29
37
|
|
30
|
-
it "should update the actionable's status to 'enqueued'" do
|
31
|
-
expect(target.actionables.first.status).to eq(:scheduled)
|
32
|
-
Actionable::Sweep.perform
|
33
|
-
expect(target.actionables.first.status).to eq(:enqueued)
|
34
38
|
end
|
35
39
|
|
36
40
|
end
|
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Luxemburg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,30 +126,30 @@ dependencies:
|
|
126
126
|
name: resque
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ~>
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: 2.0.0.pre.1
|
132
132
|
type: :runtime
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - ~>
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: 2.0.0.pre.1
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: mongoid
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- -
|
143
|
+
- - ~>
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
145
|
+
version: 4.0.0
|
146
146
|
type: :runtime
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- -
|
150
|
+
- - ~>
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
152
|
+
version: 4.0.0
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: rufus-scheduler
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,18 +184,21 @@ files:
|
|
184
184
|
- lib/actionable/memory_store/action.rb
|
185
185
|
- lib/actionable/mongoid_store.rb
|
186
186
|
- lib/actionable/mongoid_store/action.rb
|
187
|
+
- lib/actionable/recurrence.rb
|
187
188
|
- lib/actionable/resque.rb
|
188
189
|
- lib/actionable/sweep.rb
|
189
190
|
- lib/actionable/target.rb
|
190
191
|
- lib/actionable/tasks.rb
|
191
192
|
- lib/actionable/version.rb
|
192
193
|
- spec/action_spec.rb
|
193
|
-
- spec/
|
194
|
+
- spec/job_spec.rb
|
195
|
+
- spec/recurrence_spec.rb
|
194
196
|
- spec/spec_helper.rb
|
197
|
+
- spec/support/recurring_test_job.rb
|
195
198
|
- spec/support/test_job.rb
|
196
199
|
- spec/support/test_model.rb
|
197
200
|
- spec/sweep_spec.rb
|
198
|
-
- spec/
|
201
|
+
- spec/target_spec.rb
|
199
202
|
homepage: ''
|
200
203
|
licenses:
|
201
204
|
- MIT
|
@@ -223,9 +226,11 @@ summary: Store stuff in Mongo, keep track of it in Redis, log lots of stuff, inc
|
|
223
226
|
test help
|
224
227
|
test_files:
|
225
228
|
- spec/action_spec.rb
|
226
|
-
- spec/
|
229
|
+
- spec/job_spec.rb
|
230
|
+
- spec/recurrence_spec.rb
|
227
231
|
- spec/spec_helper.rb
|
232
|
+
- spec/support/recurring_test_job.rb
|
228
233
|
- spec/support/test_job.rb
|
229
234
|
- spec/support/test_model.rb
|
230
235
|
- spec/sweep_spec.rb
|
231
|
-
- spec/
|
236
|
+
- spec/target_spec.rb
|