triggerable 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +14 -4
- data/lib/triggerable.rb +9 -8
- data/lib/triggerable/actions/action.rb +18 -0
- data/lib/triggerable/actions/lambda_action.rb +28 -0
- data/lib/triggerable/conditions/condition.rb +33 -31
- data/lib/triggerable/conditions/field/exists.rb +14 -8
- data/lib/triggerable/conditions/field/field_condition.rb +27 -21
- data/lib/triggerable/conditions/field/in.rb +18 -12
- data/lib/triggerable/conditions/field/or_equal_to.rb +13 -11
- data/lib/triggerable/conditions/lambda_condition.rb +14 -8
- data/lib/triggerable/conditions/method_condition.rb +13 -7
- data/lib/triggerable/conditions/predicate/and.rb +10 -4
- data/lib/triggerable/conditions/predicate/or.rb +13 -4
- data/lib/triggerable/conditions/predicate/predicate_condition.rb +34 -32
- data/lib/triggerable/conditions/schedule/after.rb +17 -15
- data/lib/triggerable/conditions/schedule/before.rb +17 -15
- data/lib/triggerable/conditions/schedule/schedule_condition.rb +20 -18
- data/lib/triggerable/engine.rb +33 -20
- data/lib/triggerable/rules/automation.rb +31 -11
- data/lib/triggerable/rules/rule.rb +21 -9
- data/lib/triggerable/rules/trigger.rb +19 -9
- data/lib/triggerable/version.rb +1 -1
- data/spec/conditions_spec.rb +13 -13
- data/spec/integration/actions_spec.rb +9 -5
- data/spec/integration/automations_spec.rb +20 -20
- data/spec/integration/conditions_spec.rb +2 -2
- data/spec/integration/short_syntax_spec.rb +1 -1
- data/spec/scopes_spec.rb +10 -10
- data/triggerable.gemspec +1 -1
- metadata +4 -3
- data/lib/triggerable/actions.rb +0 -41
@@ -1,7 +1,16 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
condition
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
19
|
-
|
19
|
+
def scope table
|
20
|
+
predicate_scope = nil
|
20
21
|
|
21
|
-
|
22
|
-
|
22
|
+
@conditions.each_with_index do |condition, index|
|
23
|
+
condition_scope = condition.scope(table)
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
32
|
-
|
32
|
+
predicate_scope
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
+
protected
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
def predicate_name
|
38
|
+
self.class.name.demodulize.downcase
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def to
|
9
|
+
automation_time - @value
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
+
private
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class Before < ScheduleCondition
|
4
|
+
def from
|
5
|
+
automation_time + @value
|
6
|
+
end
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def to
|
9
|
+
automation_time + @value + Triggerable::Engine.interval
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
+
private
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def true_for? object
|
10
|
+
condition.true_for?(object)
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def scope table
|
14
|
+
condition.scope(table)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
+
protected
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
data/lib/triggerable/engine.rb
CHANGED
@@ -1,28 +1,41 @@
|
|
1
|
-
|
2
|
-
cattr_accessor :triggers, :automations, :interval
|
1
|
+
require 'logger'
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
module Triggerable
|
4
|
+
class Engine
|
5
|
+
cattr_accessor :triggers,
|
6
|
+
:automations,
|
7
|
+
:interval,
|
8
|
+
:logger,
|
9
|
+
:debug
|
6
10
|
|
7
|
-
|
8
|
-
self.
|
9
|
-
end
|
11
|
+
self.triggers = []
|
12
|
+
self.automations = []
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
def self.trigger model, options, block
|
15
|
+
self.triggers << Rules::Trigger.new(model, options, block)
|
16
|
+
end
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
def self.automation model, options, block
|
19
|
+
self.automations << Rules::Automation.new(model, options, block)
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
2
|
-
|
3
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Rules
|
3
|
+
class Trigger < Rule
|
4
|
+
attr_accessor :callback
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
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
|
data/lib/triggerable/version.rb
CHANGED
data/spec/conditions_spec.rb
CHANGED
@@ -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]
|