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
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