action_logic 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb88bf14abd86df63ae10d679391a032ac66245f
4
- data.tar.gz: 9a98e3cf999ed6390c9f239960945c2f2efb9767
3
+ metadata.gz: b6d36552e9a95bd397f6841f5ea609b870885349
4
+ data.tar.gz: f2e121ac6007fcd73d09a88988053adc7892fe4a
5
5
  SHA512:
6
- metadata.gz: 3a6102dceee026431347f921772b4919584a8253bbb54a1fdd68b64adbc84f3c0aa970b686735c20871dfb14a56913d1e0a3c18ca0f7a06535402c9b30e111ce
7
- data.tar.gz: 50db9e8264312f9cd1a7dd7f38e1bf0ee711bcdfd895bce653a316e4d5e09a5d1b4346946033c59d186799b825ed6dad62a39b2f8c4dc0522a373f6f4206df4c
6
+ metadata.gz: 8ae1e86d81b1d5799d95089eb6319402610046880d7c1856ce420fba6865a6029191e606d5093c595e839179773b16e91f10fb53bc4e1489300fd9202474afb0
7
+ data.tar.gz: 869e66643e26a901d707f4887539649ebe13e160199fc361190099be4b46dfe0761eb3e75d40805e12f8db72faf2c92f9ae865d375ce2ca66238f3686a008488
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ coverage/
2
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,72 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ action_logic (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.0)
10
+ coveralls (0.8.3)
11
+ json (~> 1.8)
12
+ rest-client (>= 1.6.8, < 2)
13
+ simplecov (~> 0.10.0)
14
+ term-ansicolor (~> 1.3)
15
+ thor (~> 0.19.1)
16
+ diff-lcs (1.2.5)
17
+ docile (1.1.5)
18
+ domain_name (0.5.25)
19
+ unf (>= 0.0.5, < 1.0.0)
20
+ http-cookie (1.0.2)
21
+ domain_name (~> 0.5)
22
+ json (1.8.3)
23
+ method_source (0.8.2)
24
+ mime-types (2.6.2)
25
+ netrc (0.11.0)
26
+ pry (0.10.3)
27
+ coderay (~> 1.1.0)
28
+ method_source (~> 0.8.1)
29
+ slop (~> 3.4)
30
+ rest-client (1.8.0)
31
+ http-cookie (>= 1.0.2, < 2.0)
32
+ mime-types (>= 1.16, < 3.0)
33
+ netrc (~> 0.7)
34
+ rspec (3.3.0)
35
+ rspec-core (~> 3.3.0)
36
+ rspec-expectations (~> 3.3.0)
37
+ rspec-mocks (~> 3.3.0)
38
+ rspec-core (3.3.2)
39
+ rspec-support (~> 3.3.0)
40
+ rspec-expectations (3.3.1)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.3.0)
43
+ rspec-mocks (3.3.2)
44
+ diff-lcs (>= 1.2.0, < 2.0)
45
+ rspec-support (~> 3.3.0)
46
+ rspec-support (3.3.0)
47
+ simplecov (0.10.0)
48
+ docile (~> 1.1.0)
49
+ json (~> 1.8)
50
+ simplecov-html (~> 0.10.0)
51
+ simplecov-html (0.10.0)
52
+ slop (3.6.0)
53
+ term-ansicolor (1.3.2)
54
+ tins (~> 1.0)
55
+ thor (0.19.1)
56
+ tins (1.6.0)
57
+ unf (0.1.4)
58
+ unf_ext
59
+ unf_ext (0.0.7.1)
60
+
61
+ PLATFORMS
62
+ ruby
63
+
64
+ DEPENDENCIES
65
+ action_logic!
66
+ coveralls (~> 0.8.3)
67
+ pry (~> 0.10)
68
+ rspec (~> 3.3)
69
+ simplecov (~> 0.10.0)
70
+
71
+ BUNDLED WITH
72
+ 1.10.6
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # ActionLogic
2
+ [![Gem Version](https://badge.fury.io/rb/action_logic.svg)](https://badge.fury.io/rb/action_logic)
3
+ [![Code Climate](https://codeclimate.com/github/rewinfrey/action_logic/badges/gpa.svg)](https://codeclimate.com/github/rewinfrey/action_logic)
4
+ [![Coverage Status](https://coveralls.io/repos/rewinfrey/action_logic/badge.svg?branch=master&service=github)](https://coveralls.io/github/rewinfrey/action_logic?branch=master)
5
+
6
+ [![Codeship Status for rewinfrey/action_logic](https://codeship.com/projects/7737cf40-6808-0133-84a7-460d97cd31f0/status?branch=master)](https://codeship.com/projects/114179)
@@ -0,0 +1,24 @@
1
+ require File.expand_path('../lib/action_logic/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'action_logic'
5
+ s.summary = 'Business logic abstraction'
6
+ s.homepage = 'https://github.com/rewinfrey/action_logic'
7
+ s.license = 'MIT'
8
+
9
+ s.files = `git ls-files`.split($\)
10
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
12
+ s.require_paths = ["lib"]
13
+ s.version = ActionLogic::VERSION
14
+
15
+ s.authors = ["Rick Winfrey"]
16
+ s.email = 'rick.winfrey@gmail.com'
17
+ s.date = '2015-11-03'
18
+ s.description = 'Provides common interfaces for validating and abstracting business logic'
19
+
20
+ s.add_development_dependency("rspec", "~> 3.3")
21
+ s.add_development_dependency("pry", "~> 0.10")
22
+ s.add_development_dependency("simplecov", "~> 0.10.0")
23
+ s.add_development_dependency("coveralls", "~> 0.8.3")
24
+ end
@@ -0,0 +1,39 @@
1
+ require 'ostruct'
2
+
3
+ module ActionLogic
4
+ class ActionContext < OpenStruct
5
+ SUCCESS = :success
6
+ FAILURE = :failure
7
+ HALTED = :halted
8
+
9
+ def initialize(params = {})
10
+ params[:status] ||= SUCCESS
11
+ super(params)
12
+ end
13
+
14
+ def update!(status, message)
15
+ self.status = status
16
+ self.message = message
17
+ end
18
+
19
+ def fail!(message = "")
20
+ update!(FAILURE, message)
21
+ end
22
+
23
+ def halt!(message = "")
24
+ update!(HALTED, message)
25
+ end
26
+
27
+ def success?
28
+ self.status == SUCCESS
29
+ end
30
+
31
+ def failure?
32
+ self.status == FAILURE
33
+ end
34
+
35
+ def halted?
36
+ self.status == HALTED
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ require 'action_logic/action_core'
2
+ require 'action_logic/action_validation'
3
+
4
+ module ActionLogic
5
+ module ActionCoordinator
6
+ include ActionLogic::ActionValidation
7
+ include ActionLogic::ActionCore
8
+
9
+ def self.included(klass)
10
+ klass.extend ClassMethods
11
+ klass.extend ActionLogic::ActionCore::ClassMethods
12
+ klass.extend ActionLogic::ActionValidation::ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def execute(params = {})
17
+ around(params) do |execution_context|
18
+ execution_context.call
19
+
20
+ next_execution_context = execution_context.plan.keys.first
21
+
22
+ while (next_execution_context) do
23
+ execution_context.context = next_execution_context.execute(execution_context.context)
24
+ next_execution_context = execution_context.plan[next_execution_context][execution_context.context.status]
25
+
26
+ # From the perspective of the coordinator, the status of the context should be
27
+ # :success as long as the state transition plan defines the next execution context
28
+ # for a given current exection context and its resulting context state.
29
+ # However, because normally a context in a state of :halted or :failure would
30
+ # be considered a "breaking" state, the status of a context that is :halted or :failure
31
+ # has to be reset to the default :success status only within the execution context of
32
+ # the coordinator and only when the next execution context is defined within the
33
+ # state transition plan. Otherwise, the context is return as is, without mutating its :status.
34
+ execution_context.context.status = :success if next_execution_context
35
+ end
36
+
37
+ execution_context.context
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ module ActionLogic
2
+ module ActionCore
3
+ attr_accessor :context
4
+
5
+ def initialize(params)
6
+ self.context = make_context(params)
7
+ end
8
+
9
+ def make_context(params = {})
10
+ ActionContext.new(params)
11
+ end
12
+
13
+ def break?
14
+ context.status == :failure ||
15
+ context.status == :halted
16
+ end
17
+
18
+ module ClassMethods
19
+ def around(params, &block)
20
+ execution_context = self.new(params)
21
+
22
+ return execution_context.context if execution_context.break?
23
+
24
+ execution_context.set_validation_rules
25
+ execution_context.validations!(:before)
26
+ execution_context.validations!(:around)
27
+
28
+ begin
29
+ block.call(execution_context)
30
+ rescue => e
31
+ if execution_context.respond_to?(:error)
32
+ execution_context.error(e)
33
+ else
34
+ raise e
35
+ end
36
+ end
37
+
38
+ execution_context.validations!(:after)
39
+ execution_context.validations!(:around)
40
+
41
+ execution_context.context
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ require 'action_logic/action_core'
2
+ require 'action_logic/action_validation'
3
+
4
+ module ActionLogic
5
+ module ActionTask
6
+ include ActionLogic::ActionValidation
7
+ include ActionLogic::ActionCore
8
+
9
+ def self.included(klass)
10
+ klass.extend ClassMethods
11
+ klass.extend ActionLogic::ActionCore::ClassMethods
12
+ klass.extend ActionLogic::ActionValidation::ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def execute(params = {})
17
+ around(params) do |execution_context|
18
+ execution_context.call
19
+ execution_context.context
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require 'action_logic/action_core'
2
+ require 'action_logic/action_validation'
3
+
4
+ module ActionLogic
5
+ module ActionUseCase
6
+ include ActionLogic::ActionValidation
7
+ include ActionLogic::ActionCore
8
+
9
+ def self.included(klass)
10
+ klass.extend ClassMethods
11
+ klass.extend ActionLogic::ActionCore::ClassMethods
12
+ klass.extend ActionLogic::ActionValidation::ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def execute(params = {})
17
+ around(params) do |execution_context|
18
+ execution_context.call
19
+
20
+ execution_context.tasks.reduce(execution_context.context) do |context, task|
21
+ execution_context.context = task.execute(context)
22
+ execution_context.context
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,111 @@
1
+ require 'action_logic/errors'
2
+
3
+ module ActionLogic
4
+ module ActionValidation
5
+
6
+ module ClassMethods
7
+ def validates_before(args)
8
+ @validates_before = args
9
+ end
10
+
11
+ def validates_after(args)
12
+ @validates_after = args
13
+ end
14
+
15
+ def validates_around(args)
16
+ @validates_around = args
17
+ end
18
+
19
+ def get_validates_before
20
+ @validates_before
21
+ end
22
+
23
+ def get_validates_after
24
+ @validates_after
25
+ end
26
+
27
+ def get_validates_around
28
+ @validates_around
29
+ end
30
+ end
31
+
32
+ def validations
33
+ [:validate_attributes!,
34
+ :validate_types!,
35
+ :validate_presence!]
36
+ end
37
+
38
+ def validate!(validation, validation_rules)
39
+ return if validation_rules.empty?
40
+ send(validation, validation_rules)
41
+ end
42
+
43
+ def validations!(validation_order)
44
+ case validation_order
45
+ when :before then validations.each { |validation| validate!(validation, @before_validation_rules) }
46
+ when :after then validations.each { |validation| validate!(validation, @after_validation_rules) }
47
+ when :around then validations.each { |validation| validate!(validation, @around_validation_rules) }
48
+ end
49
+ end
50
+
51
+ def set_validation_rules
52
+ @before_validation_rules ||= self.class.get_validates_before || {}
53
+ @after_validation_rules ||= self.class.get_validates_after || {}
54
+ @around_validation_rules ||= self.class.get_validates_around || {}
55
+ end
56
+
57
+ def validate_attributes!(validations)
58
+ existing_attributes = context.to_h.keys
59
+ expected_attributes = validations.keys || []
60
+ missing_attributes = expected_attributes - existing_attributes
61
+
62
+ raise ActionLogic::MissingAttributeError.new(missing_attributes) if missing_attributes.any?
63
+ end
64
+
65
+ def validate_types!(validations)
66
+ return unless validations.values.find { |expected_validation| expected_validation[:type] }
67
+
68
+ type_errors = validations.reduce([]) do |collection, (expected_attribute, expected_validation)|
69
+ next unless expected_validation[:type]
70
+
71
+ if type_to_sym(context.to_h[expected_attribute]) != expected_validation[:type]
72
+ collection << "Attribute: #{expected_attribute} with value: #{context.to_h[expected_attribute]} was expected to be of type #{expected_validation[:type]} but is #{type_to_sym(context.to_h[expected_attribute])}"
73
+ end
74
+ collection
75
+ end
76
+
77
+ raise ActionLogic::AttributeTypeError.new(type_errors) if type_errors.any?
78
+ end
79
+
80
+ def validate_presence!(validations)
81
+ return unless validations.values.find { |expected_validation| expected_validation[:presence] }
82
+
83
+ presence_errors = validations.reduce([]) do |collection, (expected_attribute, expected_validation)|
84
+ next unless expected_validation[:presence]
85
+
86
+ if expected_validation[:presence] == true
87
+ collection << "Attribute: #{expected_attribute} is missing value in context but presence validation was specified" unless context[expected_attribute]
88
+ elsif expected_validation[:presence].class == Proc
89
+ collection << "Attribute: #{expected_attribute} is missing value in context but custom presence validation was specified" unless expected_validation[:presence].call(context[expected_attribute])
90
+ else
91
+ raise ActionLogic::UnrecognizablePresenceValidatorError.new("Presence validator: #{expected_validation[:presence]} is not a supported format")
92
+ end
93
+
94
+ collection
95
+ end || []
96
+
97
+ raise ActionLogic::PresenceError.new(presence_errors) if presence_errors.any?
98
+ end
99
+
100
+ def type_to_sym(value)
101
+ case value.class.name.downcase.to_sym
102
+ when :fixnum then :integer
103
+ when :falseclass then :boolean
104
+ when :trueclass then :boolean
105
+ when :nilclass then :nil
106
+ else
107
+ value.class.name.downcase.to_sym
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,6 @@
1
+ module ActionLogic
2
+ class MissingAttributeError < StandardError; end
3
+ class AttributeTypeError < StandardError; end
4
+ class PresenceError < StandardError; end
5
+ class UnrecognizablePresenceValidatorError < StandardError; end
6
+ end
@@ -0,0 +1,3 @@
1
+ module ActionLogic
2
+ VERSION = '0.0.4'
3
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ require 'action_logic'
3
+ require 'fixtures/constants'
4
+
5
+ module ActionLogic
6
+ describe ActionContext do
7
+ subject { ActionContext.new }
8
+
9
+ describe "initialization" do
10
+ it "sets a default success attribute on the context" do
11
+ expect(subject.status).to eq(described_class::SUCCESS)
12
+ end
13
+ end
14
+
15
+ describe "success?" do
16
+ it "returns true if the context is successful" do
17
+ expect(subject.success?).to be_truthy
18
+ end
19
+ end
20
+
21
+ describe "failing a context" do
22
+ it "sets the context status as failed" do
23
+ subject.fail!
24
+
25
+ expect(subject.status).to eq(:failure)
26
+ end
27
+
28
+ it "does not require a message" do
29
+ subject.fail!
30
+
31
+ expect(subject.message).to be_empty
32
+ end
33
+
34
+ it "allows a custom failure message to be defined" do
35
+ failure_message = Constants::FAILURE_MESSAGE
36
+ subject.fail!(failure_message)
37
+
38
+ expect(subject.message).to eq(failure_message)
39
+ end
40
+
41
+ it "responds to directly query" do
42
+ subject.fail!
43
+
44
+ expect(subject.failure?).to be_truthy
45
+ end
46
+ end
47
+
48
+ describe "halting a context" do
49
+ it "sets the context status as halted" do
50
+ subject.halt!
51
+
52
+ expect(subject.status).to eq(:halted)
53
+ end
54
+
55
+ it "does not require a message" do
56
+ subject.halt!
57
+
58
+ expect(subject.message).to be_empty
59
+ end
60
+
61
+ it "allows a custom halted message to be defined" do
62
+ halt_message = Constants::HALT_MESSAGE
63
+ subject.halt!(halt_message)
64
+
65
+ expect(subject.message).to eq(halt_message)
66
+ end
67
+
68
+ it "responds to direct query" do
69
+ subject.halt!
70
+
71
+ expect(subject.halted?).to be_truthy
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+ require 'action_logic'
3
+ require 'fixtures/coordinators'
4
+ require 'fixtures/custom_types'
5
+
6
+ module ActionLogic
7
+ describe ActionCoordinator do
8
+ context "no failures and no halts" do
9
+ it "evaluates all use cases defined by the state transition plan" do
10
+ result = TestCoordinator1.execute()
11
+
12
+ expect(result.test_coordinator1).to be_truthy
13
+ expect(result.test_use_case1).to be_truthy
14
+ expect(result.test_task1).to be_truthy
15
+ expect(result.test_use_case2).to be_truthy
16
+ expect(result.test_task2).to be_truthy
17
+ expect(result.test_use_case3).to be_truthy
18
+ expect(result.test_task3).to be_truthy
19
+ end
20
+ end
21
+
22
+ context "with halts" do
23
+ it "evaluates all use cases defined by the state transition plan" do
24
+ result = HaltedTestCoordinator1.execute()
25
+
26
+ expect(result.halted_test_coordinator1).to be_truthy
27
+ expect(result.halted_test_use_case1).to be_truthy
28
+ expect(result.halted_test_task1).to be_truthy
29
+ expect(result.test_use_case2).to be_truthy
30
+ expect(result.test_task2).to be_truthy
31
+ expect(result.test_use_case3).to be_truthy
32
+ expect(result.test_task3).to be_truthy
33
+ end
34
+ end
35
+
36
+ context "with failures" do
37
+ it "evaluates all use cases defined by the state transition plan" do
38
+ result = FailureTestCoordinator1.execute()
39
+
40
+ expect(result.failure_test_coordinator1).to be_truthy
41
+ expect(result.failure_test_use_case1).to be_truthy
42
+ expect(result.failure_test_task1).to be_truthy
43
+ expect(result.test_use_case2).to be_truthy
44
+ expect(result.test_task2).to be_truthy
45
+ expect(result.test_use_case3).to be_truthy
46
+ expect(result.test_task3).to be_truthy
47
+ end
48
+ end
49
+
50
+ describe "before validations" do
51
+ describe "required attributes and type validation" do
52
+ it "does not raise error if context has required keys and values are of the correct type" do
53
+ expect { ValidateBeforeTestCoordinator.execute(Constants::VALID_ATTRIBUTES) }.to_not raise_error
54
+ end
55
+
56
+ it "raises error if context is missing required keys" do
57
+ expect { ValidateBeforeTestCoordinator.execute() }.to\
58
+ raise_error(ActionLogic::MissingAttributeError)
59
+ end
60
+
61
+ it "raises error if context has required key but is not of correct type" do
62
+ expect { ValidateBeforeTestCoordinator.execute(Constants::INVALID_ATTRIBUTES) }.to\
63
+ raise_error(ActionLogic::AttributeTypeError)
64
+ end
65
+ end
66
+
67
+ describe "custom types" do
68
+ it "allows validation against custom defined types" do
69
+ expect { ValidateBeforeCustomTypeTestCoordinator.execute(Constants::CUSTOM_TYPE_ATTRIBUTES1) }.to_not raise_error
70
+ end
71
+
72
+ it "raises error if context has custom type attribute but value is not correct custom type" do
73
+ expect { ValidateBeforeCustomTypeTestCoordinator.execute(Constants::CUSTOM_TYPE_ATTRIBUTES2) }.to\
74
+ raise_error(ActionLogic::AttributeTypeError)
75
+ end
76
+ end
77
+
78
+ describe "presence" do
79
+ it "validates presence if presence is specified" do
80
+ expect { ValidateBeforePresenceTestCoordinator.execute(:integer_test => 1) }.to_not raise_error
81
+ end
82
+
83
+ it "raises error if context has required key but value is not defined when validation requires presence" do
84
+ expect { ValidateBeforePresenceTestCoordinator.execute(:integer_test => nil) }.to\
85
+ raise_error(ActionLogic::PresenceError)
86
+ end
87
+ end
88
+
89
+ describe "custom presence" do
90
+ it "allows custom presence validation if custom presence is defined" do
91
+ expect { ValidateBeforeCustomPresenceTestCoordinator.execute(:array_test => [1]) }.to_not raise_error
92
+ end
93
+
94
+ it "raises error if custom presence validation is not satisfied" do
95
+ expect { ValidateBeforeCustomPresenceTestCoordinator.execute(:array_test => []) }.to\
96
+ raise_error(ActionLogic::PresenceError)
97
+ end
98
+
99
+ it "raises error if custom presence validation is not supported" do
100
+ expect { ValidateBeforeUnrecognizablePresenceTestCoordinator.execute(:integer_test => 1) }.to\
101
+ raise_error(ActionLogic::UnrecognizablePresenceValidatorError)
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "after validations" do
107
+ describe "required attributes and type validation" do
108
+ it "does not raise error if the task sets all required keys and values are of the correct type" do
109
+ expect { ValidateAfterTestCoordinator.execute() }.to_not raise_error
110
+ end
111
+
112
+ it "raises error if task does not provide the necessary keys" do
113
+ expect { ValidateAfterMissingAttributesTestCoordinator.execute() }.to\
114
+ raise_error(ActionLogic::MissingAttributeError)
115
+ end
116
+
117
+ it "raises error if task has required key but is not of correct type" do
118
+ expect { ValidateAfterInvalidTypeTestCoordinator.execute() }.to\
119
+ raise_error(ActionLogic::AttributeTypeError)
120
+ end
121
+ end
122
+
123
+ describe "custom types" do
124
+ it "allows validation against custom defined types" do
125
+ expect { ValidateAfterCustomTypeTestCoordinator.execute() }.to_not raise_error
126
+ end
127
+
128
+ it "raises error if context has custom type attribute but value is not correct custom type" do
129
+ expect { ValidateAfterInvalidCustomTypeTestCoordinator.execute() }.to\
130
+ raise_error(ActionLogic::AttributeTypeError)
131
+ end
132
+ end
133
+
134
+ describe "presence" do
135
+ it "validates presence if presence is specified" do
136
+ expect { ValidateAfterPresenceTestCoordinator.execute() }.to_not raise_error
137
+ end
138
+
139
+ it "raises error if context has required key but value is not defined when validation requires presence" do
140
+ expect { ValidateAfterInvalidPresenceTestCoordinator.execute() }.to\
141
+ raise_error(ActionLogic::PresenceError)
142
+ end
143
+ end
144
+
145
+ describe "custom presence" do
146
+ it "allows custom presence validation if custom presence is defined" do
147
+ expect { ValidateAfterCustomPresenceTestCoordinator.execute() }.to_not raise_error
148
+ end
149
+
150
+ it "raises error if custom presence validation is not satisfied" do
151
+ expect { ValidateAfterInvalidCustomPresenceTestCoordinator.execute() }.to\
152
+ raise_error(ActionLogic::PresenceError)
153
+ end
154
+
155
+ it "raises error if custom presence validation is not supported" do
156
+ expect { ValidateAfterUnrecognizablePresenceTestCoordinator.execute() }.to\
157
+ raise_error(ActionLogic::UnrecognizablePresenceValidatorError)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end