triggerable 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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]