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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01cc155d845bc2afb7c7eb308113e74819a00d4f
4
- data.tar.gz: 81074d512740a11417bc35480d4ffc1e3192dfef
3
+ metadata.gz: 4675b09599d91ea600d5b96c346746323a4c99b0
4
+ data.tar.gz: 057af4afa9a42e2fa9a09eb02d3bf728d115f6ec
5
5
  SHA512:
6
- metadata.gz: 01af1a6893ed82e2878e57ab66578c6b05f5dae25eb822faf06ba66faf5071af0329b0ab266a306b0ef2b2a4b39b7b0e75d0b40f7f8651009ae4de3efffe70ee
7
- data.tar.gz: e0d69053d213d38377dcaa496ded6d51cbc630933f2e623ba79a0d53b384037dc1db94a94b3c657d1d9566f4682b1d2b4ac7bf3dab085c5746d5fa4ac3978b00
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!(obj, trigger_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.
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 Conditions
2
- class Condition
3
- def self.build condition
4
- return Condition.new if condition.blank?
5
- return LambdaCondition.new(condition) if condition.is_a?(Proc)
6
- return MethodCondition.new(condition) if condition.is_a?(Symbol)
7
-
8
- key = condition.keys.first
9
- value = condition[key]
10
-
11
- if [:and, :or].include?(key)
12
- predicate_condition(key, value)
13
- else
14
- field_condition(key, value)
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
- def true_for?(object); true; end
19
+ def true_for?(object); true; end
19
20
 
20
- def scope; ''; end
21
+ def scope; ''; end
21
22
 
22
- private
23
+ private
23
24
 
24
- def self.predicate_condition class_name, value
25
- condition_class(class_name).new(value)
26
- end
25
+ def self.predicate_condition class_name, value
26
+ condition_class(class_name).new(value)
27
+ end
27
28
 
28
- def self.field_condition field, value
29
- if value.is_a?(Array)
30
- Conditions::In.new(field, value)
31
- elsif value.is_a?(Hash)
32
- condition_class(value.keys.first).new(field, value.values.first)
33
- else
34
- Conditions::Is.new(field, value)
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
- def self.condition_class sym
39
- "Conditions::#{sym.to_s.camelize}".constantize
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 Conditions
2
- class Exists < FieldCondition
3
- def true_for? object
4
- v = field_value(object)
5
- @value ? v.present? : v.blank?
6
- end
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
- def scope
9
- "#{@field} IS #{'NOT ' if @value}NULL"
13
+ def desc
14
+ "#{@field} exists"
15
+ end
10
16
  end
11
17
  end
12
18
  end
@@ -1,28 +1,34 @@
1
- module Conditions
2
- class FieldCondition < Condition
3
- def initialize field, value
4
- @field = field
5
- @value = value
6
- end
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
- def true_for? object
9
- field_value(object).send(@ruby_comparator, @value)
10
- end
9
+ def true_for? object
10
+ field_value(object).send(@ruby_comparator, @value)
11
+ end
11
12
 
12
- def scope table
13
- table[@field].send(@db_comparator, @value)
14
- end
13
+ def scope table
14
+ table[@field].send(@db_comparator, @value)
15
+ end
15
16
 
16
- private
17
- def field_value object
18
- object.send(@field)
19
- end
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
- def sanitized_value
22
- if @value.is_a?(Array)
23
- @value.map { |v| ActiveRecord::Base::sanitize(v) }
24
- else
25
- ActiveRecord::Base::sanitize(@value)
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 Conditions
2
- class In < FieldCondition
3
- def initialize field, condition
4
- super
5
- @db_comparator = 'in'
6
- end
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
- def true_for? object
9
- @value.include?(field_value(object))
10
- end
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
- private
13
- def sanitized_value
14
- "(#{super.join(',')})"
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 Conditions
2
- class OrEqualTo < FieldCondition
3
- def initialize field, value
4
- super
5
- @condition = Or.new [
6
- @additional_condition.constantize.new(field, value),
7
- Is.new(field, value)
8
- ]
9
- end
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
- def true_for? object
12
- @condition.true_for?(object)
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 Conditions
2
- class LambdaCondition < Condition
3
- def initialize block
4
- @block = block
5
- end
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
- def true_for? object
8
- proc = @block
9
- object.instance_eval { instance_exec(&proc) }
13
+ def desc
14
+ 'lambda'
15
+ end
10
16
  end
11
17
  end
12
18
  end
@@ -1,11 +1,17 @@
1
- module Conditions
2
- class MethodCondition < Condition
3
- def initialize method_name
4
- @method_name = method_name
5
- end
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
- def true_for? object
8
- object.send(@method_name)
12
+ def desc
13
+ @method_name
14
+ end
9
15
  end
10
16
  end
11
17
  end
@@ -1,7 +1,13 @@
1
- module Conditions
2
- class And < PredicateCondition
3
- def true_for? object
4
- true_conditions(object).count == @conditions.count
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