actionable 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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +11 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +16 -0
- data/actionable.gemspec +37 -0
- data/lib/actionable.rb +31 -0
- data/lib/actionable/job.rb +35 -0
- data/lib/actionable/memory_store.rb +49 -0
- data/lib/actionable/memory_store/action.rb +83 -0
- data/lib/actionable/mongoid_store.rb +27 -0
- data/lib/actionable/mongoid_store/action.rb +31 -0
- data/lib/actionable/resque.rb +17 -0
- data/lib/actionable/sweep.rb +16 -0
- data/lib/actionable/target.rb +55 -0
- data/lib/actionable/tasks.rb +2 -0
- data/lib/actionable/version.rb +3 -0
- data/spec/action_spec.rb +71 -0
- data/spec/schedule_spec.rb +42 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/test_job.rb +10 -0
- data/spec/support/test_model.rb +15 -0
- data/spec/sweep_spec.rb +36 -0
- data/spec/work_spec.rb +54 -0
- metadata +231 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35e414933341959d1347c1cb41e4e2bae53e880d
|
4
|
+
data.tar.gz: 19d6241afa6343d0a0e69033fafe1600fa54655e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e3ca8e2f1875fd0a5301f96e6293ca62c60dcb835cd21442779995489730c0056ea2d14d81dad33110ea450964707c459d0bc22f179ce59b94b80bd37d1fb56
|
7
|
+
data.tar.gz: 9815bef64be1a7d64100144278bf6d8404551915f618f9215e4f5910178c77b736de4eefb6f69b44f5741bd99646cdc6d1e312f0cee3d9507b4a0f0611944302
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Daniel Luxemburg
|
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,29 @@
|
|
1
|
+
# Actionable
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'actionable'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install actionable
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'actionable/tasks'
|
3
|
+
require 'resque/tasks'
|
4
|
+
|
5
|
+
task "resque:setup" do
|
6
|
+
puts File.expand_path('../spec/support/*.rb', __FILE__)
|
7
|
+
Dir[File.expand_path('../spec/support/*.rb', __FILE__)].each do |f|
|
8
|
+
puts f
|
9
|
+
require f
|
10
|
+
end
|
11
|
+
|
12
|
+
puts TestJob.class.to_s
|
13
|
+
Mongoid.configure do |config|
|
14
|
+
config.connect_to('actionable_test')
|
15
|
+
end
|
16
|
+
end
|
data/actionable.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'actionable/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "actionable"
|
8
|
+
spec.version = Actionable::VERSION
|
9
|
+
spec.authors = ["Daniel Luxemburg"]
|
10
|
+
spec.email = ["daniel.luxemburg@gmail.com"]
|
11
|
+
spec.description = %q{Better jobs. Good jobs.}
|
12
|
+
spec.summary = %q{Store stuff in Mongo, keep track of it in Redis, log lots of stuff, include test help}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "timecop"
|
26
|
+
spec.add_development_dependency "guard-rspec"
|
27
|
+
spec.add_development_dependency "rb-fsevent"
|
28
|
+
spec.add_development_dependency "terminal-notifier-guard"
|
29
|
+
|
30
|
+
spec.add_dependency "resque"
|
31
|
+
spec.add_dependency "mongoid"
|
32
|
+
spec.add_dependency "rufus-scheduler"
|
33
|
+
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
data/lib/actionable.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "mongoid"
|
2
|
+
require "resque"
|
3
|
+
|
4
|
+
require "actionable/version"
|
5
|
+
|
6
|
+
require "actionable/memory_store"
|
7
|
+
require "actionable/memory_store/action"
|
8
|
+
|
9
|
+
require "actionable/mongoid_store"
|
10
|
+
require "actionable/mongoid_store/action"
|
11
|
+
|
12
|
+
require "actionable/target"
|
13
|
+
require "actionable/sweep"
|
14
|
+
require "actionable/job"
|
15
|
+
|
16
|
+
module Actionable
|
17
|
+
def self.store
|
18
|
+
self.const_get(store_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.set_store(name)
|
22
|
+
@store_name = name
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.store_name
|
26
|
+
@store_name ||= "MongoidStore"
|
27
|
+
end
|
28
|
+
|
29
|
+
Action = SimpleDelegator.new(MongoidStore::Action)
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Actionable
|
2
|
+
class Job
|
3
|
+
|
4
|
+
attr_reader :actionable
|
5
|
+
|
6
|
+
def self.queue
|
7
|
+
@queue ||= 'actionable'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.after_enqueue(id)
|
11
|
+
actionable = Actionable::Action.find(id)
|
12
|
+
actionable.update_attributes(status: :enqueued)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.perform(id)
|
16
|
+
actionable = Actionable::Action.find(id)
|
17
|
+
actionable.update_attributes(status: :working)
|
18
|
+
new(actionable).perform
|
19
|
+
actionable.update_attributes(status: :complete)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(actionable)
|
23
|
+
@actionable = actionable
|
24
|
+
end
|
25
|
+
|
26
|
+
def target
|
27
|
+
@target ||= actionable.target
|
28
|
+
end
|
29
|
+
|
30
|
+
def payload
|
31
|
+
@payload ||= HashWithIndifferentAccess.new(actionable.payload)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Actionable
|
2
|
+
module MemoryStore
|
3
|
+
|
4
|
+
def self.insert(model)
|
5
|
+
key = (model[:id] ||= Moped::BSON::ObjectId.new.to_s)
|
6
|
+
set(key,model)
|
7
|
+
add(hash["target_#{model[:target_id]}"],model) if model[:target_id].present?
|
8
|
+
model
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.find(key)
|
12
|
+
model = hash[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find_by_target_id(target_id)
|
16
|
+
hash["target_#{target_id}"]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def self.hash
|
22
|
+
@hash ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.set(key,value)
|
26
|
+
hash[key] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get(key)
|
30
|
+
hash[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.clear
|
34
|
+
@hash = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.count
|
38
|
+
hash.length
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.add(key,model)
|
42
|
+
list = (hash[key] ||= [])
|
43
|
+
list << model
|
44
|
+
set(key,list)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module Actionable
|
4
|
+
module MemoryStore
|
5
|
+
class Action
|
6
|
+
|
7
|
+
include ActiveModel::Model
|
8
|
+
attr_accessor :id, :execution_time, :payload, :job_class_name
|
9
|
+
attr_accessor :target, :target_id, :target_class_name, :status
|
10
|
+
|
11
|
+
def self.create(data)
|
12
|
+
this = new(data)
|
13
|
+
this.status = :new
|
14
|
+
this
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.build(data)
|
18
|
+
this = new(data)
|
19
|
+
this.status = :new
|
20
|
+
this
|
21
|
+
end
|
22
|
+
|
23
|
+
def target
|
24
|
+
@target ||= get_target_from_definition
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
Actionable::MemoryStore.insert(for_save)
|
29
|
+
end
|
30
|
+
|
31
|
+
def enqueue
|
32
|
+
Resque.enqueue(job_class,id.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def cancel
|
36
|
+
update_attributes(status: :cancelled)
|
37
|
+
end
|
38
|
+
|
39
|
+
def reschedule_for(execution_time)
|
40
|
+
update_attributes(execution_time: execution_time)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def set_target_definition
|
46
|
+
target_class = target.class.to_s
|
47
|
+
target_id = target.id
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_target_from_definition
|
51
|
+
return nil unless target_class.present? && target_id.present?
|
52
|
+
target_class.find(target_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
def job_class
|
56
|
+
job_class_name.constantize if job_class_name.present?
|
57
|
+
end
|
58
|
+
|
59
|
+
def target_class
|
60
|
+
target_class_name.constantize if target_class_name.present?
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_attributes(hash)
|
64
|
+
hash.each_pair do |key,value|
|
65
|
+
instance_variable_set(:"@#{key}",value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def for_save
|
72
|
+
if @target.present? && (@target_id.nil? || @target_class.nil?)
|
73
|
+
set_target_definition
|
74
|
+
end
|
75
|
+
json = as_json
|
76
|
+
json.delete(:target)
|
77
|
+
json
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
|
3
|
+
module Actionable
|
4
|
+
module MongoidStore
|
5
|
+
|
6
|
+
def self.find(key)
|
7
|
+
Action.find(key)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find_by_target_id(target_id)
|
11
|
+
Action.where(target_id:target_id)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.count
|
15
|
+
Action.count
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create(*args)
|
19
|
+
Action.create(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.build(*args)
|
23
|
+
Action.build(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
|
3
|
+
module Actionable
|
4
|
+
module MongoidStore
|
5
|
+
class Action < MemoryStore::Action
|
6
|
+
|
7
|
+
include Mongoid::Document
|
8
|
+
|
9
|
+
store_in(collection: 'actionables')
|
10
|
+
|
11
|
+
field :execution_time, type: DateTime
|
12
|
+
field :payload, type: Hash
|
13
|
+
field :job_class_name, type: String
|
14
|
+
field :status, type: Symbol, default: :new
|
15
|
+
field :target_id, type: Moped::BSON::ObjectId
|
16
|
+
field :target_class_name, type: String
|
17
|
+
|
18
|
+
scope :scheduled, where(status: :scheduled)
|
19
|
+
scope :scheduled_for_between, ->(from,to){
|
20
|
+
between(execution_time:[from,to])
|
21
|
+
}
|
22
|
+
scope :scheduled_for_before, ->(to){ where(:execution_time.lt => to) }
|
23
|
+
|
24
|
+
def self.to_do
|
25
|
+
scheduled.scheduled_for_before(Time.now.to_datetime)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module Actionable
|
4
|
+
module Target
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
attr_accessor :actionable_ids
|
9
|
+
attr_accessor :actionable_errors
|
10
|
+
|
11
|
+
def actionables
|
12
|
+
@actionables ||= Actionable::Action.where(target_id: id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def actionable_errors
|
16
|
+
@actionable_errors
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def schedule_actionable(execution_time,job_class_name,payload={})
|
21
|
+
|
22
|
+
# force memoized values to refresh
|
23
|
+
@actionables = nil
|
24
|
+
@actionable_errors = nil
|
25
|
+
|
26
|
+
# set model values
|
27
|
+
actionable = Actionable::Action.build(
|
28
|
+
target_id:id,
|
29
|
+
target_class_name: self.class.to_s,
|
30
|
+
payload: payload,
|
31
|
+
execution_time: execution_time,
|
32
|
+
job_class_name: job_class_name
|
33
|
+
)
|
34
|
+
|
35
|
+
# yield the model to a block for
|
36
|
+
# situation-specific adjustments
|
37
|
+
yield(actionable) if block_given?
|
38
|
+
|
39
|
+
# set status to saved now that
|
40
|
+
# initialization is complete
|
41
|
+
actionable.status = :scheduled
|
42
|
+
|
43
|
+
# save the new model record
|
44
|
+
unless actionable.save
|
45
|
+
|
46
|
+
# keep error values around for inspection
|
47
|
+
@actionable_errors = actionable.errors
|
48
|
+
false
|
49
|
+
end
|
50
|
+
true
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/spec/action_spec.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Actionable::MemoryStore::Action do
|
4
|
+
|
5
|
+
it "should be be defined" do
|
6
|
+
expect(defined?(Actionable::MemoryStore::Action)).to be_true
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Actionable::MemoryStore do
|
12
|
+
|
13
|
+
let(:payload) {
|
14
|
+
{payload: {name: 'daniel', number: 37}}
|
15
|
+
}
|
16
|
+
|
17
|
+
it "should not persist data when not saved" do
|
18
|
+
j = Actionable::MemoryStore::Action.new(payload)
|
19
|
+
expect(Actionable::MemoryStore.count).to eq(0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should persist data when inserted" do
|
23
|
+
j = Actionable::MemoryStore::Action.new(payload).save
|
24
|
+
expect(Actionable::MemoryStore.count).to eq(1)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Actionable::MongoidStore do
|
30
|
+
|
31
|
+
let(:payload) {
|
32
|
+
{payload: {name: 'daniel', number: 37}}
|
33
|
+
}
|
34
|
+
|
35
|
+
it "should not persist data when not saved" do
|
36
|
+
j = Actionable::MongoidStore::Action.new(payload)
|
37
|
+
expect(Actionable::MongoidStore.count).to eq(0)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should persist data when inserted" do
|
41
|
+
j = Actionable::MongoidStore::Action.new(payload).save
|
42
|
+
expect(Actionable::MongoidStore.count).to eq(1)
|
43
|
+
end
|
44
|
+
|
45
|
+
context "scheduled for one hour from now" do
|
46
|
+
|
47
|
+
let(:payload){
|
48
|
+
{payload: {name: 'daniel', number: 37}, execution_time: 1.hour.from_now }
|
49
|
+
}
|
50
|
+
|
51
|
+
before do
|
52
|
+
j = Actionable::MongoidStore::Action.new(payload).save
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should be available during the correct time window" do
|
56
|
+
expect(Actionable::MongoidStore::Action.between(execution_time:[
|
57
|
+
30.minutes.from_now,
|
58
|
+
90.minutes.from_now
|
59
|
+
]).count).to eq(1)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not be available during the incorrect time window" do
|
63
|
+
expect(Actionable::MongoidStore::Action.between(execution_time:[
|
64
|
+
15.minutes.from_now,
|
65
|
+
45.minutes.from_now
|
66
|
+
]).count).to eq(0)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Actionable::Target do
|
4
|
+
|
5
|
+
context "given an actionable for an hour from now" do
|
6
|
+
|
7
|
+
let(:target) {
|
8
|
+
TestModel.create({
|
9
|
+
color:'blue',
|
10
|
+
shape:'square',
|
11
|
+
number:3
|
12
|
+
})
|
13
|
+
}
|
14
|
+
|
15
|
+
before do
|
16
|
+
target.schedule_actionable(1.hour.from_now,TestJob,{number:3})
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be added to the database" do
|
20
|
+
expect(Actionable::MongoidStore.count).to eq(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not be included as a 'to do'" do
|
24
|
+
expect(Actionable::Action.to_do.count).to eq(0)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not be included as a 'scheduled'" do
|
28
|
+
expect(Actionable::Action.scheduled.count).to eq(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
context "2 hours later" do
|
32
|
+
|
33
|
+
it "should be included as a 'to do'" do
|
34
|
+
Timecop.travel(2.hours.from_now)
|
35
|
+
expect(Actionable::Action.to_do.count).to eq(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler.require(:default,:development)
|
3
|
+
|
4
|
+
Dir[File.expand_path('../support/*.rb', __FILE__)].each {|f| require f}
|
5
|
+
|
6
|
+
Mongoid.configure do |config|
|
7
|
+
config.connect_to('actionable_test')
|
8
|
+
end
|
9
|
+
|
10
|
+
Resque.redis = Redis.new
|
11
|
+
Resque.redis.namespace = "resque:actionable_test:"
|
12
|
+
|
13
|
+
module Resque
|
14
|
+
def self.purge!
|
15
|
+
self.redis.keys('*')
|
16
|
+
self.redis.del(*keys) unless keys.count == 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
|
22
|
+
config.before(:each) do
|
23
|
+
Resque.purge!
|
24
|
+
Mongoid.purge!
|
25
|
+
Timecop.return
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/spec/sweep_spec.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Actionable::Sweep do
|
4
|
+
|
5
|
+
let(:target) {
|
6
|
+
TestModel.create
|
7
|
+
}
|
8
|
+
|
9
|
+
before do
|
10
|
+
target.schedule_actionable(1.hour.from_now,TestJob)
|
11
|
+
Timecop.travel(2.hours.from_now)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should leave one job in the to do list before being performed" do
|
15
|
+
expect(Resque.size('actionables')).to eq(0)
|
16
|
+
expect(Actionable::Action.to_do.count).to eq(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should move one job from the to do list to the queue after being performed" do
|
20
|
+
Actionable::Sweep.perform
|
21
|
+
expect(Actionable::Action.to_do.count).to eq(0)
|
22
|
+
expect(Resque.size('actionables')).to eq(1)
|
23
|
+
end
|
24
|
+
|
25
|
+
pending "should enqueue a job that knows the id for its target" do
|
26
|
+
Actionable::Sweep.perform
|
27
|
+
expect(Resque.queue('actionables').pop['args'].first).to eq(target.actionables.first.id.to_s)
|
28
|
+
end
|
29
|
+
|
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
|
+
end
|
35
|
+
|
36
|
+
end
|
data/spec/work_spec.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
|
4
|
+
def start_test_worker
|
5
|
+
spawn('bundle exec rake resque:work PIDFILE=resque.pid QUEUES=actionable,actionable_sweep INTERVAL=1')
|
6
|
+
end
|
7
|
+
|
8
|
+
def kill_test_worker
|
9
|
+
pid = File.new(File.expand_path("../../resque.pid",__FILE__)).read
|
10
|
+
system("kill -9 #{pid}")
|
11
|
+
system("rm resque.pid")
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_test_worker
|
15
|
+
start_test_worker
|
16
|
+
sleep(5)
|
17
|
+
kill_test_worker
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Actionable::Job do
|
21
|
+
|
22
|
+
context "scheduled for one minute ago" do
|
23
|
+
|
24
|
+
let(:target) {
|
25
|
+
TestModel.create({
|
26
|
+
color:'blue',
|
27
|
+
shape:'square',
|
28
|
+
number:3
|
29
|
+
})
|
30
|
+
}
|
31
|
+
|
32
|
+
before do
|
33
|
+
target.schedule_actionable(1.minute.ago,TestJob,{number:2})
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be executed by a worker manually" do
|
37
|
+
Resque.inline = true
|
38
|
+
id = target.id
|
39
|
+
Actionable::Sweep.perform
|
40
|
+
expect(TestModel.find(id).number).to eq(6)
|
41
|
+
expect(TestModel.find(id).name).to eq('blue square')
|
42
|
+
Resque.inline = false
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be executed by a worker in another processes" do
|
46
|
+
id = target.id
|
47
|
+
run_test_worker
|
48
|
+
expect(TestModel.find(id).number).to eq(6)
|
49
|
+
expect(TestModel.find(id).name).to eq('blue square')
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: actionable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Luxemburg
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: timecop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rb-fsevent
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: terminal-notifier-guard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: resque
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: mongoid
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rufus-scheduler
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - '>='
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: Better jobs. Good jobs.
|
168
|
+
email:
|
169
|
+
- daniel.luxemburg@gmail.com
|
170
|
+
executables: []
|
171
|
+
extensions: []
|
172
|
+
extra_rdoc_files: []
|
173
|
+
files:
|
174
|
+
- .gitignore
|
175
|
+
- Gemfile
|
176
|
+
- Guardfile
|
177
|
+
- LICENSE.txt
|
178
|
+
- README.md
|
179
|
+
- Rakefile
|
180
|
+
- actionable.gemspec
|
181
|
+
- lib/actionable.rb
|
182
|
+
- lib/actionable/job.rb
|
183
|
+
- lib/actionable/memory_store.rb
|
184
|
+
- lib/actionable/memory_store/action.rb
|
185
|
+
- lib/actionable/mongoid_store.rb
|
186
|
+
- lib/actionable/mongoid_store/action.rb
|
187
|
+
- lib/actionable/resque.rb
|
188
|
+
- lib/actionable/sweep.rb
|
189
|
+
- lib/actionable/target.rb
|
190
|
+
- lib/actionable/tasks.rb
|
191
|
+
- lib/actionable/version.rb
|
192
|
+
- spec/action_spec.rb
|
193
|
+
- spec/schedule_spec.rb
|
194
|
+
- spec/spec_helper.rb
|
195
|
+
- spec/support/test_job.rb
|
196
|
+
- spec/support/test_model.rb
|
197
|
+
- spec/sweep_spec.rb
|
198
|
+
- spec/work_spec.rb
|
199
|
+
homepage: ''
|
200
|
+
licenses:
|
201
|
+
- MIT
|
202
|
+
metadata: {}
|
203
|
+
post_install_message:
|
204
|
+
rdoc_options: []
|
205
|
+
require_paths:
|
206
|
+
- lib
|
207
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - '>='
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '0'
|
212
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - '>='
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: '0'
|
217
|
+
requirements: []
|
218
|
+
rubyforge_project:
|
219
|
+
rubygems_version: 2.0.0
|
220
|
+
signing_key:
|
221
|
+
specification_version: 4
|
222
|
+
summary: Store stuff in Mongo, keep track of it in Redis, log lots of stuff, include
|
223
|
+
test help
|
224
|
+
test_files:
|
225
|
+
- spec/action_spec.rb
|
226
|
+
- spec/schedule_spec.rb
|
227
|
+
- spec/spec_helper.rb
|
228
|
+
- spec/support/test_job.rb
|
229
|
+
- spec/support/test_model.rb
|
230
|
+
- spec/sweep_spec.rb
|
231
|
+
- spec/work_spec.rb
|