triggerable 0.1.7 → 0.1.8

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -4
  3. data/lib/triggerable.rb +9 -8
  4. data/lib/triggerable/actions/action.rb +18 -0
  5. data/lib/triggerable/actions/lambda_action.rb +28 -0
  6. data/lib/triggerable/conditions/condition.rb +33 -31
  7. data/lib/triggerable/conditions/field/exists.rb +14 -8
  8. data/lib/triggerable/conditions/field/field_condition.rb +27 -21
  9. data/lib/triggerable/conditions/field/in.rb +18 -12
  10. data/lib/triggerable/conditions/field/or_equal_to.rb +13 -11
  11. data/lib/triggerable/conditions/lambda_condition.rb +14 -8
  12. data/lib/triggerable/conditions/method_condition.rb +13 -7
  13. data/lib/triggerable/conditions/predicate/and.rb +10 -4
  14. data/lib/triggerable/conditions/predicate/or.rb +13 -4
  15. data/lib/triggerable/conditions/predicate/predicate_condition.rb +34 -32
  16. data/lib/triggerable/conditions/schedule/after.rb +17 -15
  17. data/lib/triggerable/conditions/schedule/before.rb +17 -15
  18. data/lib/triggerable/conditions/schedule/schedule_condition.rb +20 -18
  19. data/lib/triggerable/engine.rb +33 -20
  20. data/lib/triggerable/rules/automation.rb +31 -11
  21. data/lib/triggerable/rules/rule.rb +21 -9
  22. data/lib/triggerable/rules/trigger.rb +19 -9
  23. data/lib/triggerable/version.rb +1 -1
  24. data/spec/conditions_spec.rb +13 -13
  25. data/spec/integration/actions_spec.rb +9 -5
  26. data/spec/integration/automations_spec.rb +20 -20
  27. data/spec/integration/conditions_spec.rb +2 -2
  28. data/spec/integration/short_syntax_spec.rb +1 -1
  29. data/spec/scopes_spec.rb +10 -10
  30. data/triggerable.gemspec +1 -1
  31. metadata +4 -3
  32. data/lib/triggerable/actions.rb +0 -41
@@ -1,7 +1,16 @@
1
- module Conditions
2
- class Or < PredicateCondition
3
- def true_for? object
4
- true_conditions(object).any?
1
+ module Triggerable
2
+ module Conditions
3
+ class Or < PredicateCondition
4
+ def true_for? object
5
+ true_conditions(object).any?
6
+ end
7
+
8
+ def desc
9
+ @conditions.map do |c|
10
+ desc = c.try(:desc)
11
+ desc ? "(#{desc})" : c
12
+ end.join(' || ')
13
+ end
5
14
  end
6
15
  end
7
16
  end
@@ -1,45 +1,47 @@
1
- module Conditions
2
- class PredicateCondition < Condition
3
- attr_accessor :conditions
4
-
5
- def initialize conditions
6
- @conditions = conditions.map do |condition|
7
- unless condition.is_a?(Hash)
8
- condition
9
- else
10
- field = condition.keys.first
11
- statement = condition.values.first
12
-
13
- Condition.build({field => statement})
1
+ module Triggerable
2
+ module Conditions
3
+ class PredicateCondition < Condition
4
+ attr_accessor :conditions
5
+
6
+ def initialize conditions
7
+ @conditions = conditions.map do |condition|
8
+ unless condition.is_a?(Hash)
9
+ condition
10
+ else
11
+ field = condition.keys.first
12
+ statement = condition.values.first
13
+
14
+ Condition.build({field => statement})
15
+ end
14
16
  end
15
17
  end
16
- end
17
18
 
18
- def scope table
19
- predicate_scope = nil
19
+ def scope table
20
+ predicate_scope = nil
20
21
 
21
- @conditions.each_with_index do |condition, index|
22
- condition_scope = condition.scope(table)
22
+ @conditions.each_with_index do |condition, index|
23
+ condition_scope = condition.scope(table)
23
24
 
24
- predicate_scope = if index.zero?
25
- condition_scope
26
- else
27
- predicate_scope.send(predicate_name, condition_scope)
25
+ predicate_scope = if index.zero?
26
+ condition_scope
27
+ else
28
+ predicate_scope.send(predicate_name, condition_scope)
29
+ end
28
30
  end
29
- end
30
31
 
31
- predicate_scope
32
- end
32
+ predicate_scope
33
+ end
33
34
 
34
- protected
35
+ protected
35
36
 
36
- def predicate_name
37
- self.class.name.demodulize.downcase
38
- end
37
+ def predicate_name
38
+ self.class.name.demodulize.downcase
39
+ end
39
40
 
40
- def true_conditions object
41
- @conditions.select do |c|
42
- c.is_a?(Symbol) ? object.send(c) : c.true_for?(object)
41
+ def true_conditions object
42
+ @conditions.select do |c|
43
+ c.is_a?(Symbol) ? object.send(c) : c.true_for?(object)
44
+ end
43
45
  end
44
46
  end
45
47
  end
@@ -1,20 +1,22 @@
1
- module Conditions
2
- class After < ScheduleCondition
3
- def from
4
- automation_time - @value - Engine.interval
5
- end
1
+ module Triggerable
2
+ module Conditions
3
+ class After < ScheduleCondition
4
+ def from
5
+ automation_time - @value - Triggerable::Engine.interval
6
+ end
6
7
 
7
- def to
8
- automation_time - @value
9
- end
8
+ def to
9
+ automation_time - @value
10
+ end
10
11
 
11
- private
12
+ private
12
13
 
13
- def condition
14
- And.new [
15
- GreaterThan.new(@field, from),
16
- LessThanOrEqualTo.new(@field, to)
17
- ]
14
+ def condition
15
+ And.new [
16
+ GreaterThan.new(@field, from),
17
+ LessThanOrEqualTo.new(@field, to)
18
+ ]
19
+ end
18
20
  end
19
21
  end
20
- end
22
+ end
@@ -1,20 +1,22 @@
1
- module Conditions
2
- class Before < ScheduleCondition
3
- def from
4
- automation_time + @value
5
- end
1
+ module Triggerable
2
+ module Conditions
3
+ class Before < ScheduleCondition
4
+ def from
5
+ automation_time + @value
6
+ end
6
7
 
7
- def to
8
- automation_time + @value + Engine.interval
9
- end
8
+ def to
9
+ automation_time + @value + Triggerable::Engine.interval
10
+ end
10
11
 
11
- private
12
+ private
12
13
 
13
- def condition
14
- And.new [
15
- GreaterThanOrEqualTo.new(@field, from),
16
- LessThan.new(@field, to)
17
- ]
14
+ def condition
15
+ And.new [
16
+ GreaterThanOrEqualTo.new(@field, from),
17
+ LessThan.new(@field, to)
18
+ ]
19
+ end
18
20
  end
19
21
  end
20
- end
22
+ end
@@ -1,24 +1,26 @@
1
- module Conditions
2
- class ScheduleCondition < FieldCondition
3
- def initialize field, value
4
- @value = value.values.first if value.is_a?(Hash)
5
- super
6
- end
1
+ module Triggerable
2
+ module Conditions
3
+ class ScheduleCondition < FieldCondition
4
+ def initialize field, value
5
+ @value = value.values.first if value.is_a?(Hash)
6
+ super
7
+ end
7
8
 
8
- def true_for? object
9
- condition.true_for?(object)
10
- end
9
+ def true_for? object
10
+ condition.true_for?(object)
11
+ end
11
12
 
12
- def scope table
13
- condition.scope(table)
14
- end
13
+ def scope table
14
+ condition.scope(table)
15
+ end
15
16
 
16
- protected
17
+ protected
17
18
 
18
- # automation_time is Time.now rounded by Engine.interval
19
- def automation_time
20
- i = Engine.interval
21
- Time.at((Time.now.to_i / i) * i).utc
19
+ # automation_time is Time.now rounded by Engine.interval
20
+ def automation_time
21
+ i = Triggerable::Engine.interval
22
+ Time.at((Time.now.to_i / i) * i).utc
23
+ end
22
24
  end
23
25
  end
24
- end
26
+ end
@@ -1,28 +1,41 @@
1
- class Engine
2
- cattr_accessor :triggers, :automations, :interval
1
+ require 'logger'
3
2
 
4
- self.triggers = []
5
- self.automations = []
3
+ module Triggerable
4
+ class Engine
5
+ cattr_accessor :triggers,
6
+ :automations,
7
+ :interval,
8
+ :logger,
9
+ :debug
6
10
 
7
- def self.trigger model, options, block
8
- self.triggers << Rules::Trigger.new(model, options, block)
9
- end
11
+ self.triggers = []
12
+ self.automations = []
10
13
 
11
- def self.automation model, options, block
12
- self.automations << Rules::Automation.new(model, options, block)
13
- end
14
+ def self.trigger model, options, block
15
+ self.triggers << Rules::Trigger.new(model, options, block)
16
+ end
14
17
 
15
- def self.triggers_for obj, callback
16
- triggers.select { |t| obj.class.name == t.model.name && t.callback == callback }
17
- end
18
+ def self.automation model, options, block
19
+ self.automations << Rules::Automation.new(model, options, block)
20
+ end
18
21
 
19
- def self.run_automations interval
20
- self.interval = interval
21
- automations.each(&:execute!)
22
- end
22
+ def self.triggers_for obj, callback
23
+ triggers.select { |t| obj.class.name == t.model.name && t.callback == callback }
24
+ end
23
25
 
24
- def self.clear
25
- self.triggers = []
26
- self.automations = []
26
+ def self.run_automations interval
27
+ self.interval = interval
28
+ automations.each(&:execute!)
29
+ end
30
+
31
+ def self.clear
32
+ self.triggers = []
33
+ self.automations = []
34
+ end
35
+
36
+ def self.log level, message
37
+ puts message if debug
38
+ logger.send(level, "#{Time.now.strftime('%FT%T%z')}: #{message}") if logger.present?
39
+ end
27
40
  end
28
41
  end
@@ -1,13 +1,33 @@
1
- module Rules
2
- class Automation < Rule
3
- def execute!
4
- table = Arel::Table.new(model.table_name)
5
- scope = @condition.scope(table)
6
- query = table.where(scope).project(Arel.sql('id')).to_sql
7
- ids = ActiveRecord::Base.connection.execute(query).map { |r| r['id'] }
8
- models = model.where(id: ids)
9
-
10
- models.each {|object| actions.each {|a| a.run_for!(object, name)} }
1
+ module Triggerable
2
+ module Rules
3
+ class Automation < Rule
4
+ def execute!
5
+ ids = ActiveRecord::Base.connection.execute(build_query).map { |r| r['id'] }
6
+ models = model.where(id: ids)
7
+
8
+ Triggerable::Engine.log(:debug, "#{desc}: processing #{models.count} object(s)")
9
+
10
+ models.each do |object|
11
+ begin
12
+ actions.each {|a| a.run_for!(object, name)}
13
+ rescue Exception => ex
14
+ Triggerable::Engine.log(:error, "#{desc} failed with exception #{ex}")
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def build_query
22
+ table = Arel::Table.new(model.table_name)
23
+ query = table.where(@condition.scope(table))
24
+ .project(Arel.sql('id'))
25
+ .to_sql
26
+
27
+ Triggerable::Engine.log(:debug, "#{desc}: #{query}")
28
+
29
+ query
30
+ end
11
31
  end
12
32
  end
13
- end
33
+ end
@@ -1,12 +1,24 @@
1
- module Rules
2
- class Rule
3
- attr_accessor :name, :model, :condition, :actions
4
-
5
- def initialize model, options, block
6
- @model = model
7
- @condition = Conditions::Condition.build(options[:if])
8
- @name = options[:name]
9
- @actions = Triggerable::Action.build(block || options[:do])
1
+ module Triggerable
2
+ module Rules
3
+ class Rule
4
+ attr_accessor :name, :model, :condition, :actions
5
+
6
+ def initialize model, options, block
7
+ @model = model
8
+ @condition = Conditions::Condition.build(options[:if])
9
+ @name = options[:name]
10
+ @actions = Triggerable::Actions::Action.build(block || options[:do])
11
+ end
12
+
13
+ protected
14
+
15
+ def desc
16
+ "#{self.class.name} #{name || self}(#{model})"
17
+ end
18
+
19
+ def debug?
20
+ Triggerable::Engine.debug
21
+ end
10
22
  end
11
23
  end
12
24
  end
@@ -1,14 +1,24 @@
1
- module Rules
2
- class Trigger < Rule
3
- attr_accessor :callback
1
+ module Triggerable
2
+ module Rules
3
+ class Trigger < Rule
4
+ attr_accessor :callback
4
5
 
5
- def initialize model, options, block
6
- super
7
- @callback = options[:on]
8
- end
6
+ def initialize model, options, block
7
+ super
8
+ @callback = options[:on]
9
+ end
10
+
11
+ def execute! object
12
+ return unless condition.true_for?(object)
9
13
 
10
- def execute! object
11
- actions.each {|a| a.run_for!(object, name)} if condition.true_for?(object)
14
+ actions.each do |action|
15
+ begin
16
+ action.run_for!(object, name)
17
+ rescue Exception => ex
18
+ Triggerable::Engine.log(:error, "#{desc} failed with exception #{ex}")
19
+ end
20
+ end
21
+ end
12
22
  end
13
23
  end
14
24
  end
@@ -1,3 +1,3 @@
1
1
  module Triggerable
2
- VERSION = '0.1.7'
2
+ VERSION = '0.1.8'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Conditions do
3
+ describe Triggerable::Conditions do
4
4
  before(:each) do
5
5
  class Sample; attr_accessor(:field); end
6
6
  @obj = Sample.new
@@ -9,11 +9,11 @@ describe Conditions do
9
9
  context 'is' do
10
10
 
11
11
  def check_value value
12
- Conditions::Is.new(:field, value).true_for?(@obj)
12
+ Triggerable::Conditions::Is.new(:field, value).true_for?(@obj)
13
13
  end
14
14
 
15
15
  def scope value
16
- Conditions::Is.new(:field, value).scope
16
+ Triggerable::Conditions::Is.new(:field, value).scope
17
17
  end
18
18
 
19
19
  it 'integer' do
@@ -36,11 +36,11 @@ describe Conditions do
36
36
  context 'is_not' do
37
37
 
38
38
  def check_value value
39
- Conditions::IsNot.new(:field, value).true_for?(@obj)
39
+ Triggerable::Conditions::IsNot.new(:field, value).true_for?(@obj)
40
40
  end
41
41
 
42
42
  def scope value
43
- Conditions::IsNot.new(:field, value).scope
43
+ Triggerable::Conditions::IsNot.new(:field, value).scope
44
44
  end
45
45
 
46
46
  it 'integer' do
@@ -62,11 +62,11 @@ describe Conditions do
62
62
 
63
63
  context 'greater_than' do
64
64
  def check_value value
65
- Conditions::GreaterThan.new(:field, value).true_for?(@obj)
65
+ Triggerable::Conditions::GreaterThan.new(:field, value).true_for?(@obj)
66
66
  end
67
67
 
68
68
  def scope value
69
- Conditions::GreaterThan.new(:field, value).scope
69
+ Triggerable::Conditions::GreaterThan.new(:field, value).scope
70
70
  end
71
71
 
72
72
  it 'integer' do
@@ -90,11 +90,11 @@ describe Conditions do
90
90
 
91
91
  context 'less_than' do
92
92
  def check_value value
93
- Conditions::LessThan.new(:field, value).true_for?(@obj)
93
+ Triggerable::Conditions::LessThan.new(:field, value).true_for?(@obj)
94
94
  end
95
95
 
96
96
  def scope value
97
- Conditions::LessThan.new(:field, value).scope
97
+ Triggerable::Conditions::LessThan.new(:field, value).scope
98
98
  end
99
99
 
100
100
  it 'integer' do
@@ -118,11 +118,11 @@ describe Conditions do
118
118
 
119
119
  context 'in' do
120
120
  def check_value value
121
- Conditions::In.new(:field, value).true_for?(@obj)
121
+ Triggerable::Conditions::In.new(:field, value).true_for?(@obj)
122
122
  end
123
123
 
124
124
  def scope value
125
- Conditions::In.new(:field, value).scope
125
+ Triggerable::Conditions::In.new(:field, value).scope
126
126
  end
127
127
 
128
128
  it 'integer' do
@@ -160,7 +160,7 @@ describe Conditions do
160
160
  end
161
161
 
162
162
  context 'and' do
163
- before(:each) { @and_condition = Conditions::And.new([]) }
163
+ before(:each) { @and_condition = Triggerable::Conditions::And.new([]) }
164
164
 
165
165
  it ('true + true') do
166
166
  @and_condition.conditions = [TrueCondition.new, TrueCondition.new]
@@ -179,7 +179,7 @@ describe Conditions do
179
179
  end
180
180
 
181
181
  context 'or' do
182
- before(:each) { @or_condition = Conditions::Or.new([]) }
182
+ before(:each) { @or_condition = Triggerable::Conditions::Or.new([]) }
183
183
 
184
184
  it ('true + true') do
185
185
  @or_condition.conditions = [TrueCondition.new, TrueCondition.new]