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.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4675b09599d91ea600d5b96c346746323a4c99b0
|
4
|
+
data.tar.gz: 057af4afa9a42e2fa9a09eb02d3bf728d115f6ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3df5712b4a6e059cfbd2a7e5c5f90d7cbbbe4941f2ee761d08c84a58b0bc6ac0dba60b2089c54cb724f24a0d180fc6a0c3f5f00b99b72e4fb1f21dc5979cd136
|
7
|
+
data.tar.gz: 88c14b4067cba8a5816c1d93c2987b101631c7bc4869f9bba42e8ea757593adce708fedeb316e203b2bb2ae9c391eea449c5bc23b658e978ec1ed451bec2161c
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ class User < ActiveRecord::Base
|
|
28
28
|
user.send_welcome_sms
|
29
29
|
end
|
30
30
|
|
31
|
-
automation if: { created_at: { after: 24 }, confirmed: false } do
|
31
|
+
automation if: { created_at: { after: 24.hours }, confirmed: false } do
|
32
32
|
send_confirmation_email
|
33
33
|
end
|
34
34
|
end
|
@@ -56,7 +56,7 @@ trigger on: :after_create, if: { and: [{ field1: '1' }, { field2: 1 }] }, ...
|
|
56
56
|
trigger on: :after_create, if: { or: [{ field1: '1' }, { field2: 1 }] }, ...
|
57
57
|
```
|
58
58
|
|
59
|
-
Triggerable does not run automations by itself, you should call `Engine.run_automations(interval)` using any scheduling script. Interval is a time difference between calling the method (e.g. `1.hour`). *You should avoid situations when your interval is less then the time your automations need to complete!*
|
59
|
+
Triggerable does not run automations by itself, you should call `Triggerable::Engine.run_automations(interval)` using any scheduling script. Interval is a time difference between calling the method (e.g. `1.hour`). *You should avoid situations when your interval is less then the time your automations need to complete!*
|
60
60
|
|
61
61
|
If you have more complex condition or need to check associations (not supported in DSL now), you should use a lambda condition form:
|
62
62
|
|
@@ -66,10 +66,10 @@ trigger on: :after_update, if: -> { orders.any? } do
|
|
66
66
|
end
|
67
67
|
```
|
68
68
|
|
69
|
-
If you need to share logic between triggers/automations bodies you can move it into separate class. It should be inherited from `Triggerable::Action` and implement a single method `run_for!(
|
69
|
+
If you need to share logic between triggers/automations bodies you can move it into separate class. It should be inherited from `Triggerable::Actions::Action` and implement a single method `run_for!(object, rule_name)` where trigger_name is a string passed to rule in :name option and obj is a triggered object. Then you can pass a name of your action class instead of do block.
|
70
70
|
|
71
71
|
```ruby
|
72
|
-
class SendWelcomeSms < Triggerable::Action
|
72
|
+
class SendWelcomeSms < Triggerable::Actions::Action
|
73
73
|
def run_for! object, trigger_name
|
74
74
|
SmsGateway.send_to object.phone, welcome_text
|
75
75
|
end
|
@@ -80,6 +80,16 @@ class User
|
|
80
80
|
end
|
81
81
|
```
|
82
82
|
|
83
|
+
## Logging and debugging
|
84
|
+
|
85
|
+
You can easily turn on logging and debugging (using `puts`):
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
Triggerable::Engine.logger = Logger.new(File.join(Rails.root, 'log', 'triggers.log'))
|
89
|
+
Triggerable::Engine.debug = true
|
90
|
+
```
|
91
|
+
|
92
|
+
|
83
93
|
## Contributing
|
84
94
|
|
85
95
|
1. Fork it
|
data/lib/triggerable.rb
CHANGED
@@ -23,7 +23,8 @@ require "triggerable/conditions/schedule/schedule_condition"
|
|
23
23
|
require "triggerable/conditions/schedule/before"
|
24
24
|
require "triggerable/conditions/schedule/after"
|
25
25
|
|
26
|
-
require "triggerable/actions"
|
26
|
+
require "triggerable/actions/action"
|
27
|
+
require "triggerable/actions/lambda_action"
|
27
28
|
|
28
29
|
module Triggerable
|
29
30
|
extend ActiveSupport::Concern
|
@@ -55,13 +56,13 @@ module Triggerable
|
|
55
56
|
end
|
56
57
|
|
57
58
|
COMPARSIONS = [
|
58
|
-
{ name: 'Is', ancestor: Conditions::FieldCondition, args: { ruby_comparator: '==', db_comparator: 'eq' } },
|
59
|
-
{ name: 'GreaterThan', ancestor: Conditions::FieldCondition, args: { ruby_comparator: '>', db_comparator: 'gt' } },
|
60
|
-
{ name: 'LessThan', ancestor: Conditions::FieldCondition, args: { ruby_comparator: '<', db_comparator: 'lt' } },
|
61
|
-
{ name: 'IsNot', ancestor: Conditions::FieldCondition, args: { ruby_comparator: '!=', db_comparator: 'not_eq'} },
|
59
|
+
{ name: 'Is', ancestor: Triggerable::Conditions::FieldCondition, args: { ruby_comparator: '==', db_comparator: 'eq' } },
|
60
|
+
{ name: 'GreaterThan', ancestor: Triggerable::Conditions::FieldCondition, args: { ruby_comparator: '>', db_comparator: 'gt' } },
|
61
|
+
{ name: 'LessThan', ancestor: Triggerable::Conditions::FieldCondition, args: { ruby_comparator: '<', db_comparator: 'lt' } },
|
62
|
+
{ name: 'IsNot', ancestor: Triggerable::Conditions::FieldCondition, args: { ruby_comparator: '!=', db_comparator: 'not_eq'} },
|
62
63
|
|
63
|
-
{ name: 'GreaterThanOrEqualTo', ancestor: Conditions::OrEqualTo, args: { db_comparator: 'gteq', additional_condition: 'Conditions::GreaterThan' } },
|
64
|
-
{ name: 'LessThanOrEqualTo', ancestor: Conditions::OrEqualTo, args: { db_comparator: 'lteq', additional_condition: 'Conditions::LessThan' } }
|
64
|
+
{ name: 'GreaterThanOrEqualTo', ancestor: Triggerable::Conditions::OrEqualTo, args: { db_comparator: 'gteq', additional_condition: 'Triggerable::Conditions::GreaterThan' } },
|
65
|
+
{ name: 'LessThanOrEqualTo', ancestor: Triggerable::Conditions::OrEqualTo, args: { db_comparator: 'lteq', additional_condition: 'Triggerable::Conditions::LessThan' } }
|
65
66
|
]
|
66
67
|
|
67
68
|
COMPARSIONS.each do |desc|
|
@@ -72,7 +73,7 @@ COMPARSIONS.each do |desc|
|
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
|
-
Conditions.const_set(desc[:name], klass)
|
76
|
+
Triggerable::Conditions.const_set(desc[:name], klass)
|
76
77
|
end
|
77
78
|
|
78
79
|
ActiveSupport.on_load(:active_record) { include Triggerable }
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Triggerable
|
2
|
+
module Actions
|
3
|
+
class Action
|
4
|
+
def self.build source
|
5
|
+
if source.is_a?(Proc)
|
6
|
+
[LambdaAction.new(source)]
|
7
|
+
else
|
8
|
+
Array(source).map do |source|
|
9
|
+
descendant = descendants.find { |d| d == source.to_s.camelize.constantize }
|
10
|
+
descendant.new if descendant.present?
|
11
|
+
end.compact
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_for!(object, rule_name); end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Triggerable
|
2
|
+
module Actions
|
3
|
+
class LambdaAction < Action
|
4
|
+
def initialize block
|
5
|
+
@block = block
|
6
|
+
end
|
7
|
+
|
8
|
+
def run_for! object, trigger_name
|
9
|
+
proc = @block
|
10
|
+
object.instance_eval do
|
11
|
+
change_whodunnit = trigger_name.present? && defined?(PaperTrail)
|
12
|
+
old_whodunnit = nil
|
13
|
+
|
14
|
+
if change_whodunnit
|
15
|
+
old_whodunnit = PaperTrail.whodunnit
|
16
|
+
PaperTrail.whodunnit = trigger_name
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
instance_exec(&proc)
|
21
|
+
ensure
|
22
|
+
PaperTrail.whodunnit = old_whodunnit if change_whodunnit
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,42 +1,44 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class Condition
|
4
|
+
def self.build condition
|
5
|
+
return Condition.new if condition.blank?
|
6
|
+
return LambdaCondition.new(condition) if condition.is_a?(Proc)
|
7
|
+
return MethodCondition.new(condition) if condition.is_a?(Symbol)
|
8
|
+
|
9
|
+
key = condition.keys.first
|
10
|
+
value = condition[key]
|
11
|
+
|
12
|
+
if [:and, :or].include?(key)
|
13
|
+
predicate_condition(key, value)
|
14
|
+
else
|
15
|
+
field_condition(key, value)
|
16
|
+
end
|
15
17
|
end
|
16
|
-
end
|
17
18
|
|
18
|
-
|
19
|
+
def true_for?(object); true; end
|
19
20
|
|
20
|
-
|
21
|
+
def scope; ''; end
|
21
22
|
|
22
|
-
|
23
|
+
private
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def self.predicate_condition class_name, value
|
26
|
+
condition_class(class_name).new(value)
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
def self.field_condition field, value
|
30
|
+
if value.is_a?(Array)
|
31
|
+
In.new(field, value)
|
32
|
+
elsif value.is_a?(Hash)
|
33
|
+
condition_class(value.keys.first).new(field, value.values.first)
|
34
|
+
else
|
35
|
+
Is.new(field, value)
|
36
|
+
end
|
35
37
|
end
|
36
|
-
end
|
37
38
|
|
38
|
-
|
39
|
-
|
39
|
+
def self.condition_class sym
|
40
|
+
"Triggerable::Conditions::#{sym.to_s.camelize}".constantize
|
41
|
+
end
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
@@ -1,12 +1,18 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class Exists < FieldCondition
|
4
|
+
def true_for? object
|
5
|
+
v = field_value(object)
|
6
|
+
@value ? v.present? : v.blank?
|
7
|
+
end
|
8
|
+
|
9
|
+
def scope
|
10
|
+
"#{@field} IS #{'NOT ' if @value}NULL"
|
11
|
+
end
|
7
12
|
|
8
|
-
|
9
|
-
|
13
|
+
def desc
|
14
|
+
"#{@field} exists"
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
12
18
|
end
|
@@ -1,28 +1,34 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class FieldCondition < Condition
|
4
|
+
def initialize field, value
|
5
|
+
@field = field
|
6
|
+
@value = value
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def true_for? object
|
10
|
+
field_value(object).send(@ruby_comparator, @value)
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def scope table
|
14
|
+
table[@field].send(@db_comparator, @value)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def desc
|
18
|
+
"#{@field} #{@ruby_comparator} #{@value}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def field_value object
|
23
|
+
object.send(@field)
|
24
|
+
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
def sanitized_value
|
27
|
+
if @value.is_a?(Array)
|
28
|
+
@value.map { |v| ActiveRecord::Base::sanitize(v) }
|
29
|
+
else
|
30
|
+
ActiveRecord::Base::sanitize(@value)
|
31
|
+
end
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -1,17 +1,23 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class In < FieldCondition
|
4
|
+
def initialize field, condition
|
5
|
+
super
|
6
|
+
@db_comparator = 'in'
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def true_for? object
|
10
|
+
@value.include?(field_value(object))
|
11
|
+
end
|
12
|
+
|
13
|
+
def desc
|
14
|
+
"#{@field} #{@db_comparator} #{@value}"
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
private
|
18
|
+
def sanitized_value
|
19
|
+
"(#{super.join(',')})"
|
20
|
+
end
|
15
21
|
end
|
16
22
|
end
|
17
23
|
end
|
@@ -1,15 +1,17 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
@
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class OrEqualTo < FieldCondition
|
4
|
+
def initialize field, value
|
5
|
+
super
|
6
|
+
@condition = Or.new [
|
7
|
+
@additional_condition.constantize.new(field, value),
|
8
|
+
Is.new(field, value)
|
9
|
+
]
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
def true_for? object
|
13
|
+
@condition.true_for?(object)
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -1,12 +1,18 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class LambdaCondition < Condition
|
4
|
+
def initialize block
|
5
|
+
@block = block
|
6
|
+
end
|
7
|
+
|
8
|
+
def true_for? object
|
9
|
+
proc = @block
|
10
|
+
object.instance_eval { instance_exec(&proc) }
|
11
|
+
end
|
6
12
|
|
7
|
-
|
8
|
-
|
9
|
-
|
13
|
+
def desc
|
14
|
+
'lambda'
|
15
|
+
end
|
10
16
|
end
|
11
17
|
end
|
12
18
|
end
|
@@ -1,11 +1,17 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class MethodCondition < Condition
|
4
|
+
def initialize method_name
|
5
|
+
@method_name = method_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def true_for? object
|
9
|
+
object.send(@method_name)
|
10
|
+
end
|
6
11
|
|
7
|
-
|
8
|
-
|
12
|
+
def desc
|
13
|
+
@method_name
|
14
|
+
end
|
9
15
|
end
|
10
16
|
end
|
11
17
|
end
|
@@ -1,7 +1,13 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module Triggerable
|
2
|
+
module Conditions
|
3
|
+
class And < PredicateCondition
|
4
|
+
def true_for? object
|
5
|
+
true_conditions(object).count == @conditions.count
|
6
|
+
end
|
7
|
+
|
8
|
+
def desc
|
9
|
+
@conditions.map { |c| c.try(:desc) || c }.join(' && ')
|
10
|
+
end
|
5
11
|
end
|
6
12
|
end
|
7
13
|
end
|