say_when 1.0.0 → 2.0.0

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -0
  4. data/Guardfile +50 -0
  5. data/README.md +135 -2
  6. data/Rakefile +1 -0
  7. data/lib/say_when.rb +33 -18
  8. data/lib/say_when/configuration.rb +16 -0
  9. data/lib/say_when/cron_expression.rb +19 -21
  10. data/lib/say_when/poller/base_poller.rb +108 -0
  11. data/lib/say_when/poller/celluloid_poller.rb +30 -0
  12. data/lib/say_when/poller/concurrent_poller.rb +31 -0
  13. data/lib/say_when/poller/simple_poller.rb +37 -0
  14. data/lib/say_when/processor/active_job_strategy.rb +35 -0
  15. data/lib/say_when/processor/simple_strategy.rb +13 -0
  16. data/lib/say_when/processor/test_strategy.rb +21 -0
  17. data/lib/say_when/scheduler.rb +67 -101
  18. data/lib/say_when/storage/active_record_strategy.rb +204 -0
  19. data/lib/say_when/storage/base_job.rb +96 -0
  20. data/lib/say_when/storage/memory_strategy.rb +140 -0
  21. data/lib/say_when/tasks.rb +15 -3
  22. data/lib/say_when/triggers/base.rb +3 -3
  23. data/lib/say_when/triggers/cron_strategy.rb +2 -3
  24. data/lib/say_when/triggers/instance_strategy.rb +3 -4
  25. data/lib/say_when/triggers/once_strategy.rb +3 -4
  26. data/lib/say_when/utils.rb +16 -0
  27. data/lib/say_when/version.rb +1 -1
  28. data/say_when.gemspec +10 -5
  29. data/test/minitest_helper.rb +45 -15
  30. data/test/say_when/configuration_test.rb +14 -0
  31. data/test/say_when/cron_expression_test.rb +140 -0
  32. data/test/say_when/poller/base_poller_test.rb +42 -0
  33. data/test/say_when/poller/celluloid_poller_test.rb +17 -0
  34. data/test/say_when/poller/concurrent_poller_test.rb +19 -0
  35. data/test/say_when/poller/simple_poller_test.rb +27 -0
  36. data/test/say_when/processor/active_job_strategy_test.rb +31 -0
  37. data/test/say_when/processor/simple_strategy_test.rb +15 -0
  38. data/test/say_when/scheduler_test.rb +41 -57
  39. data/test/say_when/storage/active_record_strategy_test.rb +134 -0
  40. data/test/say_when/storage/memory_strategy_test.rb +96 -0
  41. data/test/say_when/triggers/cron_strategy_test.rb +11 -0
  42. data/test/say_when/triggers/instance_strategy_test.rb +13 -0
  43. data/test/say_when/triggers/once_strategy_test.rb +2 -2
  44. data/test/say_when_test.rb +20 -0
  45. metadata +110 -36
  46. data/lib/say_when/base_job.rb +0 -96
  47. data/lib/say_when/processor/active_messaging.rb +0 -21
  48. data/lib/say_when/processor/base.rb +0 -19
  49. data/lib/say_when/processor/shoryuken.rb +0 -14
  50. data/lib/say_when/processor/simple.rb +0 -17
  51. data/lib/say_when/storage/active_record/acts.rb +0 -92
  52. data/lib/say_when/storage/active_record/job.rb +0 -100
  53. data/lib/say_when/storage/active_record/job_execution.rb +0 -14
  54. data/lib/say_when/storage/memory/base.rb +0 -36
  55. data/lib/say_when/storage/memory/job.rb +0 -53
  56. data/test/say_when/cron_expression_spec.rb +0 -74
  57. data/test/say_when/processor/active_messaging_test.rb +0 -41
  58. data/test/say_when/storage/active_record/job_test.rb +0 -90
  59. data/test/say_when/storage/memory/job_test.rb +0 -32
  60. data/test/say_when/storage/memory/trigger_test.rb +0 -54
  61. data/test/support/models.rb +0 -33
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+
3
+ module SayWhen
4
+ module Storage
5
+ module BaseJob
6
+ # ready to be run, just waiting for its turn
7
+ STATE_WAITING = 'waiting'
8
+
9
+ # has been acquired b/c it is time to be triggered
10
+ STATE_ACQUIRED = 'acquired'
11
+
12
+ # related job for the trigger is executing
13
+ STATE_EXECUTING = 'executing'
14
+
15
+ # "Complete" means the trigger has no remaining fire times
16
+ STATE_COMPLETE = 'complete'
17
+
18
+ # A Trigger arrives at the error state when the scheduler
19
+ # attempts to fire it, but cannot due to an error creating and executing
20
+ # its related job.
21
+ STATE_ERROR = 'error'
22
+
23
+ def lock
24
+ @lock ||= Mutex.new
25
+ end
26
+
27
+ def trigger
28
+ @trigger ||= load_trigger
29
+ end
30
+
31
+ def fired(fired_at = Time.now)
32
+ lock.synchronize do
33
+ self.last_fire_at = fired_at
34
+ self.next_fire_at = trigger.next_fire_at(last_fire_at + 1.second) rescue nil
35
+
36
+ if next_fire_at.nil?
37
+ self.status = STATE_COMPLETE
38
+ else
39
+ self.status = STATE_WAITING
40
+ end
41
+ end
42
+ end
43
+
44
+ def release
45
+ lock.synchronize do
46
+ if self.status == STATE_ACQUIRED
47
+ self.status = STATE_WAITING
48
+ end
49
+ end
50
+ end
51
+
52
+ def execute
53
+ execute_job(data)
54
+ end
55
+
56
+ def load_trigger
57
+ strategy = trigger_strategy || :once
58
+ require "say_when/triggers/#{strategy}_strategy"
59
+ trigger_class_name = "SayWhen::Triggers::#{strategy.to_s.camelize}Strategy"
60
+ trigger_class = trigger_class_name.constantize
61
+ trigger_class.new((trigger_options || {}).merge(:job=>self))
62
+ end
63
+
64
+ def execute_job(options)
65
+ task_method = (job_method || 'execute').to_s
66
+ task = get_task(task_method)
67
+ task.send(task_method, options)
68
+ end
69
+
70
+ def get_task(task_method)
71
+ task = nil
72
+
73
+ if job_class
74
+ tc = job_class.constantize
75
+ if tc.respond_to?(task_method)
76
+ task = tc
77
+ else
78
+ to = tc.new
79
+ if to.respond_to?(task_method)
80
+ task = to
81
+ else
82
+ raise "Neither '#{job_class}' class nor instance respond to '#{task_method}'"
83
+ end
84
+ end
85
+ elsif scheduled
86
+ if scheduled.respond_to?(task_method)
87
+ task = scheduled
88
+ else
89
+ raise "Scheduled '#{scheduled.inspect}' does not respond to '#{task_method}'"
90
+ end
91
+ end
92
+ task
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,140 @@
1
+ require 'active_record'
2
+ require 'say_when/storage/base_job'
3
+
4
+ module SayWhen
5
+ module Storage
6
+ class MemoryStrategy
7
+ class << self
8
+ def acquire_next(no_later_than = nil)
9
+ SayWhen::Storage::MemoryStrategy::Job.acquire_next(no_later_than)
10
+ end
11
+
12
+ def reset_acquired(older_than_seconds)
13
+ SayWhen::Storage::MemoryStrategy::Job.reset_acquired(older_than_seconds)
14
+ end
15
+
16
+ def fired(job, fired_at = Time.now)
17
+ job.fired(fired_at)
18
+ end
19
+
20
+ def release(job)
21
+ job.release
22
+ end
23
+
24
+ def create(job)
25
+ SayWhen::Storage::MemoryStrategy::Job.create(job)
26
+ end
27
+
28
+ def serialize(job)
29
+ job.to_hash
30
+ end
31
+
32
+ def deserialize(job)
33
+ SayWhen::Storage::MemoryStrategy::Job.new(job)
34
+ end
35
+ end
36
+
37
+ class Job
38
+ include SayWhen::Storage::BaseJob
39
+
40
+ class << self
41
+ def acquire_lock
42
+ @acquire_lock ||= Mutex.new
43
+ end
44
+
45
+ def jobs
46
+ @jobs ||= SortedSet.new
47
+ end
48
+
49
+ def props
50
+ @props ||= []
51
+ end
52
+
53
+ def reset_acquired(older_than_seconds)
54
+ return unless older_than_seconds.to_i > 0
55
+ older_than = (Time.now - older_than_seconds.to_i)
56
+ acquire_lock.synchronize do
57
+ jobs.select do |j|
58
+ j.status == SayWhen::Storage::BaseJob::STATE_ACQUIRED && j.updated_at < older_than
59
+ end.each{ |j| j.status = SayWhen::Storage::BaseJob::STATE_WAITING }
60
+ end
61
+ end
62
+
63
+ def acquire_next(no_later_than)
64
+ acquire_lock.synchronize do
65
+ next_job = jobs.detect(nil) do |j|
66
+ (j.status == SayWhen::Storage::BaseJob::STATE_WAITING) &&
67
+ (j.next_fire_at.to_i <= no_later_than.to_i)
68
+ end
69
+ if next_job
70
+ next_job.status = SayWhen::Storage::BaseJob::STATE_ACQUIRED
71
+ next_job.updated_at = Time.now
72
+ end
73
+ next_job
74
+ end
75
+ end
76
+
77
+ def create(job)
78
+ if existing_job = find_named_job(job[:group], job[:name])
79
+ self.jobs.delete(existing_job)
80
+ end
81
+
82
+ new(job).save
83
+ end
84
+
85
+ def find_named_job(group, name)
86
+ group && name && jobs.detect { |j| j.group == group && j.name == name }
87
+ end
88
+
89
+ def has_properties(*args)
90
+ args.each do |a|
91
+ unless props.member?(a.to_s)
92
+ props << a.to_s
93
+ class_eval { attr_accessor(a.to_sym) }
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ has_properties :group, :name, :status, :start_at, :end_at
100
+ has_properties :trigger_strategy, :trigger_options, :last_fire_at, :next_fire_at
101
+ has_properties :job_class, :job_method, :data, :updated_at, :scheduled
102
+
103
+ def initialize(options = {})
104
+ options.each do |k,v|
105
+ if self.class.props.member?(k.to_s)
106
+ send("#{k}=", v)
107
+ end
108
+ end
109
+
110
+ self.updated_at = Time.now
111
+ self.status = STATE_WAITING unless self.status
112
+ self.next_fire_at = trigger.next_fire_at
113
+ end
114
+
115
+ def to_hash
116
+ [:job_class, :job_method, :data].inject({}) { |h,k| h[k] = send(k); h }
117
+ end
118
+
119
+ def save
120
+ self.class.jobs << self
121
+ self
122
+ end
123
+
124
+ def <=>(job)
125
+ self.next_fire_at.to_i <=> job.next_fire_at.to_i
126
+ end
127
+
128
+ def fired(fired_at=Time.now)
129
+ super
130
+ self.updated_at = Time.now
131
+ end
132
+
133
+ def release
134
+ super
135
+ self.updated_at = Time.now
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -1,9 +1,21 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  namespace :say_when do
4
- desc 'Start the SayWhen Scheduler'
5
- task start: :environment do
4
+ desc 'Start the SayWhen Scheduler with Rails environment'
5
+ task start_rails: :environment do
6
6
  require 'say_when'
7
- SayWhen::Scheduler.start
7
+ require 'say_when/poller/simple_poller'
8
+
9
+ SayWhen::Poller::SimplePoller.new.start
10
+ end
11
+
12
+ desc 'Start the SayWhen Scheduler standalone'
13
+ task :start do
14
+ require 'say_when'
15
+ require 'say_when/poller/simple_poller'
16
+
17
+ SayWhen.configure
18
+ puts "SayWhen starting with options: #{SayWhen.options.inspect}"
19
+ SayWhen::Poller::SimplePoller.new.start
8
20
  end
9
21
  end
@@ -3,14 +3,14 @@
3
3
  module SayWhen
4
4
  module Triggers
5
5
  module Base
6
-
7
6
  attr_accessor :job
8
7
 
9
- def initialize(options={})
8
+ def initialize(options = {})
10
9
  self.job = options.delete(:job)
10
+ raise ArgumentError.new("job must be provided to create a trigger") unless job
11
11
  end
12
12
 
13
- def next_fire_at(time=nil)
13
+ def next_fire_at(time = nil)
14
14
  raise NotImplementedError.new('You need to implement next_fire_at in your strategy')
15
15
  end
16
16
  end
@@ -6,17 +6,16 @@ require 'say_when/cron_expression'
6
6
  module SayWhen
7
7
  module Triggers
8
8
  class CronStrategy
9
-
10
9
  include SayWhen::Triggers::Base
11
10
 
12
11
  attr_accessor :cron_expression
13
12
 
14
- def initialize(options={})
13
+ def initialize(options = {})
15
14
  super
16
15
  self.cron_expression = SayWhen::CronExpression.new(options)
17
16
  end
18
17
 
19
- def next_fire_at(time=nil)
18
+ def next_fire_at(time = nil)
20
19
  cron_expression.next_fire_at(time || Time.now)
21
20
  end
22
21
  end
@@ -5,18 +5,17 @@ require 'say_when/triggers/base'
5
5
  module SayWhen
6
6
  module Triggers
7
7
  class InstanceStrategy
8
-
9
8
  include SayWhen::Triggers::Base
10
9
 
11
10
  attr_accessor :instance, :next_at_method
12
11
 
13
- def initialize(options={})
12
+ def initialize(options = {})
14
13
  super
15
- self.instance = job.scheduled
14
+ self.instance = job.scheduled
16
15
  self.next_at_method = options[:next_at_method] || 'next_fire_at'
17
16
  end
18
17
 
19
- def next_fire_at(time=Time.now)
18
+ def next_fire_at(time = Time.now)
20
19
  instance.send(next_at_method, time)
21
20
  end
22
21
  end
@@ -5,19 +5,18 @@ require 'say_when/triggers/base'
5
5
  module SayWhen
6
6
  module Triggers
7
7
  class OnceStrategy
8
-
9
8
  include SayWhen::Triggers::Base
10
9
 
11
10
  attr_accessor :once_at
12
11
 
13
- def initialize(options=nil)
12
+ def initialize(options = nil)
14
13
  super
15
14
  self.once_at = options[:at] || Time.now
16
15
  end
17
16
 
18
- def next_fire_at(time=nil)
17
+ def next_fire_at(time = nil)
19
18
  nfa = once_at if (!time || (time <= once_at))
20
- return nfa
19
+ nfa
21
20
  end
22
21
  end
23
22
  end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module SayWhen
4
+ module Utils
5
+ def load_strategy(strategy_type, strategy)
6
+ if strategy.is_a?(Symbol) || strategy.is_a?(String)
7
+ require "say_when/#{strategy_type}/#{strategy}_strategy"
8
+ class_name = "SayWhen::#{strategy_type.to_s.camelize}::#{strategy.to_s.camelize}Strategy"
9
+ strategy_class = class_name.constantize
10
+ strategy_class
11
+ else
12
+ strategy
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module SayWhen
4
- VERSION = '1.0.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/say_when.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- # -*- encoding: utf-8 -*-
1
+ # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'say_when/version'
@@ -20,12 +20,17 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency('activesupport')
22
22
 
23
- spec.add_development_dependency('activerecord')
24
- spec.add_development_dependency('shoryuken')
25
- spec.add_development_dependency('activemessaging')
26
- spec.add_development_dependency('bundler', '~> 1.3')
23
+ spec.add_development_dependency('bundler')
27
24
  spec.add_development_dependency('rake')
28
25
  spec.add_development_dependency('minitest')
26
+ spec.add_development_dependency('guard')
27
+ spec.add_development_dependency('guard-minitest')
29
28
  spec.add_development_dependency('simplecov')
29
+ spec.add_development_dependency('coveralls')
30
+
30
31
  spec.add_development_dependency('sqlite3')
32
+ spec.add_development_dependency('activerecord')
33
+ spec.add_development_dependency('celluloid')
34
+ spec.add_development_dependency('concurrent-ruby')
35
+ spec.add_development_dependency('activejob')
31
36
  end
@@ -1,5 +1,15 @@
1
1
  # encoding: utf-8
2
2
 
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+
5
+ require 'simplecov'
6
+ SimpleCov.start #'rails'
7
+
8
+ if ENV['TRAVIS']
9
+ require 'coveralls'
10
+ Coveralls.wear!
11
+ end
12
+
3
13
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
14
  require 'say_when'
5
15
 
@@ -9,35 +19,55 @@ require 'minitest/spec'
9
19
  require 'minitest/mock'
10
20
  require 'fileutils'
11
21
 
12
- Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
13
-
14
22
  require 'active_support'
23
+ require 'active_job'
24
+
25
+ require 'celluloid/test'
26
+
27
+ ActiveJob::Base.queue_adapter = :inline
28
+
29
+ Celluloid.boot
30
+
31
+ SayWhen.configure do |options|
32
+ options[:storage_strategy] = :memory
33
+ options[:processor_strategy] = :test
34
+ end
35
+
36
+ SayWhen.logger = Logger.new('/dev/null')
15
37
 
16
38
  module SayWhen
17
39
  module Test
18
-
19
40
  class TestTask
20
- def execute(data)
41
+ @@executed = false
42
+
43
+ def self.reset
44
+ @@executed = false
45
+ end
46
+
47
+ def self.execute(data)
48
+ @@executed = true
21
49
  data[:result] || 0
22
50
  end
51
+
52
+ def self.executed?
53
+ @@executed
54
+ end
23
55
  end
24
56
 
25
- class TestProcessor < SayWhen::Processor::Base
26
- attr_accessor :jobs
57
+ class TestActsAsScheduled
58
+ @@_has_many = false
27
59
 
28
- def initialize(scheduler)
29
- super(scheduler)
30
- reset
60
+ def self.has_many_called?
61
+ @@_has_many
31
62
  end
32
63
 
33
- def process(job)
34
- self.jobs << job
64
+ def self.has_many(*args)
65
+ @@_has_many = true
35
66
  end
36
67
 
37
- def reset
38
- self.jobs = []
39
- end
68
+ require 'say_when/storage/active_record_strategy'
69
+ include SayWhen::Storage::ActiveRecordStrategy::Acts
70
+ acts_as_scheduled
40
71
  end
41
-
42
72
  end
43
73
  end