light-service 0.6.0 → 0.6.1
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/.rubocop.yml +34 -0
- data/.travis.yml +16 -2
- data/Appraisals +7 -0
- data/Gemfile +2 -0
- data/README.md +126 -16
- data/RELEASES.md +4 -0
- data/Rakefile +3 -0
- data/gemfiles/activesupport_3.gemfile +8 -0
- data/gemfiles/activesupport_3.gemfile.lock +63 -0
- data/gemfiles/activesupport_4.gemfile +8 -0
- data/gemfiles/activesupport_4.gemfile.lock +71 -0
- data/lib/light-service.rb +1 -1
- data/lib/light-service/action.rb +6 -8
- data/lib/light-service/configuration.rb +0 -2
- data/lib/light-service/context.rb +32 -22
- data/lib/light-service/context/key_verifier.rb +87 -83
- data/lib/light-service/localization_adapter.rb +10 -7
- data/lib/light-service/organizer.rb +6 -3
- data/lib/light-service/organizer/with_reducer.rb +53 -39
- data/lib/light-service/organizer/with_reducer_factory.rb +3 -4
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +81 -51
- data/lib/light-service/version.rb +2 -1
- data/light-service.gemspec +4 -4
- data/resources/fail_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/around_each_spec.rb +27 -0
- data/spec/acceptance/include_warning_spec.rb +6 -2
- data/spec/acceptance/log_from_organizer_spec.rb +39 -18
- data/spec/acceptance/message_localization_spec.rb +23 -23
- data/spec/acceptance/rollback_spec.rb +1 -3
- data/spec/action_expected_keys_spec.rb +32 -19
- data/spec/action_promised_keys_spec.rb +72 -54
- data/spec/action_spec.rb +23 -5
- data/spec/context_spec.rb +21 -17
- data/spec/localization_adapter_spec.rb +14 -10
- data/spec/organizer/with_reducer_spec.rb +19 -2
- data/spec/organizer_key_aliases_spec.rb +6 -5
- data/spec/organizer_spec.rb +32 -56
- data/spec/sample/calculates_tax_spec.rb +17 -9
- data/spec/sample/provides_free_shipping_action_spec.rb +3 -7
- data/spec/sample/tax/calculates_order_tax_action.rb +3 -2
- data/spec/sample/tax/calculates_tax.rb +3 -4
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +10 -8
- data/spec/sample/tax/provides_free_shipping_action.rb +2 -4
- data/spec/spec_helper.rb +0 -1
- data/spec/test_doubles.rb +38 -15
- metadata +38 -28
data/lib/light-service.rb
CHANGED
data/lib/light-service/action.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module LightService
|
2
2
|
module Action
|
3
|
-
|
4
3
|
def self.extended(base_class)
|
5
4
|
base_class.extend Macros
|
6
5
|
end
|
7
6
|
|
8
7
|
def self.included(base_class)
|
9
|
-
|
8
|
+
msg = "including LightService::Action is deprecated. " \
|
9
|
+
"Please use `extend LightService::Action` instead"
|
10
|
+
ActiveSupport::Deprecation.warn(msg)
|
10
11
|
base_class.extend Macros
|
11
12
|
end
|
12
13
|
|
@@ -46,22 +47,20 @@ module LightService
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def rolled_back
|
49
|
-
|
50
|
+
msg = "`rolled_back` macro can not be invoked again"
|
51
|
+
fail msg if respond_to?(:rollback)
|
50
52
|
|
51
53
|
define_singleton_method :rollback do |context = {}|
|
52
54
|
yield(context)
|
53
55
|
|
54
56
|
context
|
55
57
|
end
|
56
|
-
|
57
58
|
end
|
58
59
|
|
59
60
|
private
|
60
61
|
|
61
62
|
def create_action_context(context)
|
62
|
-
if context.is_a? LightService::Context
|
63
|
-
return context
|
64
|
-
end
|
63
|
+
return context if context.is_a? LightService::Context
|
65
64
|
|
66
65
|
LightService::Context.make(context)
|
67
66
|
end
|
@@ -70,6 +69,5 @@ module LightService
|
|
70
69
|
expected_keys + promised_keys
|
71
70
|
end
|
72
71
|
end
|
73
|
-
|
74
72
|
end
|
75
73
|
end
|
@@ -7,28 +7,32 @@ module LightService
|
|
7
7
|
class Context < Hash
|
8
8
|
attr_accessor :message, :error_code, :current_action
|
9
9
|
|
10
|
-
def initialize(context={},
|
11
|
-
|
10
|
+
def initialize(context = {},
|
11
|
+
outcome = Outcomes::SUCCESS,
|
12
|
+
message = '',
|
13
|
+
error_code = nil)
|
14
|
+
@outcome = outcome
|
15
|
+
@message = message
|
16
|
+
@error_code = error_code
|
12
17
|
@skip_all = false
|
13
|
-
context.to_hash.each {|k,v| self[k] = v}
|
18
|
+
context.to_hash.each { |k, v| self[k] = v }
|
14
19
|
self
|
15
20
|
end
|
16
21
|
|
17
|
-
def self.make(context={})
|
18
|
-
unless context.is_a?
|
19
|
-
|
22
|
+
def self.make(context = {})
|
23
|
+
unless context.is_a?(Hash) || context.is_a?(LightService::Context)
|
24
|
+
msg = 'Argument must be Hash or LightService::Context'
|
25
|
+
fail ArgumentError, msg
|
20
26
|
end
|
21
27
|
|
22
|
-
unless context.is_a?(Context)
|
23
|
-
context = self.new(context)
|
24
|
-
end
|
28
|
+
context = new(context) unless context.is_a?(Context)
|
25
29
|
|
26
|
-
context.
|
30
|
+
context.assign_aliases(context.delete(:_aliases)) if context[:_aliases]
|
27
31
|
context
|
28
32
|
end
|
29
33
|
|
30
34
|
def add_to_context(values)
|
31
|
-
|
35
|
+
merge! values
|
32
36
|
end
|
33
37
|
|
34
38
|
def success?
|
@@ -44,16 +48,20 @@ module LightService
|
|
44
48
|
end
|
45
49
|
|
46
50
|
def outcome
|
47
|
-
|
51
|
+
msg = '`Context#outcome` attribute reader is ' \
|
52
|
+
'DEPRECATED and will be removed'
|
53
|
+
ActiveSupport::Deprecation.warn(msg)
|
48
54
|
@outcome
|
49
55
|
end
|
50
56
|
|
51
|
-
def succeed!(message=nil, options={})
|
52
|
-
@message = Configuration.localization_adapter.success(message,
|
57
|
+
def succeed!(message = nil, options = {})
|
58
|
+
@message = Configuration.localization_adapter.success(message,
|
59
|
+
current_action,
|
60
|
+
options)
|
53
61
|
@outcome = Outcomes::SUCCESS
|
54
62
|
end
|
55
63
|
|
56
|
-
def fail!(message=nil, options_or_error_code={})
|
64
|
+
def fail!(message = nil, options_or_error_code = {})
|
57
65
|
options_or_error_code ||= {}
|
58
66
|
|
59
67
|
if options_or_error_code.is_a?(Hash)
|
@@ -64,17 +72,19 @@ module LightService
|
|
64
72
|
options = {}
|
65
73
|
end
|
66
74
|
|
67
|
-
@message = Configuration.localization_adapter.failure(message,
|
75
|
+
@message = Configuration.localization_adapter.failure(message,
|
76
|
+
current_action,
|
77
|
+
options)
|
68
78
|
@error_code = error_code
|
69
79
|
@outcome = Outcomes::FAILURE
|
70
80
|
end
|
71
81
|
|
72
|
-
def fail_with_rollback!(message=nil, error_code=nil)
|
82
|
+
def fail_with_rollback!(message = nil, error_code = nil)
|
73
83
|
fail!(message, error_code)
|
74
|
-
|
84
|
+
fail(FailWithRollbackError)
|
75
85
|
end
|
76
86
|
|
77
|
-
def skip_all!(message=nil)
|
87
|
+
def skip_all!(message = nil)
|
78
88
|
@message = message
|
79
89
|
@skip_all = true
|
80
90
|
end
|
@@ -86,13 +96,13 @@ module LightService
|
|
86
96
|
def define_accessor_methods_for_keys(keys)
|
87
97
|
return if keys.nil?
|
88
98
|
keys.each do |key|
|
89
|
-
next if
|
90
|
-
define_singleton_method(
|
99
|
+
next if respond_to?(key.to_sym)
|
100
|
+
define_singleton_method(key.to_s) { fetch(key) }
|
91
101
|
define_singleton_method("#{key}=") { |value| self[key] = value }
|
92
102
|
end
|
93
103
|
end
|
94
104
|
|
95
|
-
def
|
105
|
+
def assign_aliases(aliases)
|
96
106
|
@aliases = aliases
|
97
107
|
|
98
108
|
aliases.each_pair do |key, key_alias|
|
@@ -1,114 +1,118 @@
|
|
1
|
-
module LightService
|
2
|
-
class
|
3
|
-
|
1
|
+
module LightService
|
2
|
+
class Context
|
3
|
+
class KeyVerifier
|
4
|
+
attr_reader :context, :action
|
5
|
+
|
6
|
+
def initialize(context, action)
|
7
|
+
@context = context
|
8
|
+
@action = action
|
9
|
+
end
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
11
|
+
def are_all_keys_in_context?(keys)
|
12
|
+
not_found_keys = keys_not_found(keys)
|
13
|
+
!not_found_keys.any?
|
14
|
+
end
|
9
15
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
16
|
+
def keys_not_found(keys)
|
17
|
+
keys ||= context.keys
|
18
|
+
keys - context.keys
|
19
|
+
end
|
14
20
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
21
|
+
def format_keys(keys)
|
22
|
+
keys.map { |k| ":#{k}" }.join(', ')
|
23
|
+
end
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
def error_message
|
26
|
+
"#{type_name} #{format_keys(keys_not_found(keys))} " \
|
27
|
+
"to be in the context during #{action}"
|
28
|
+
end
|
23
29
|
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
def throw_error_predicate(_keys)
|
31
|
+
fail NotImplementedError, 'Sorry, you have to override length'
|
32
|
+
end
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
34
|
+
def verify
|
35
|
+
return context if context.failure?
|
31
36
|
|
32
|
-
|
33
|
-
|
37
|
+
if throw_error_predicate(keys)
|
38
|
+
Configuration.logger.error error_message
|
39
|
+
fail error_to_throw, error_message
|
40
|
+
end
|
34
41
|
|
35
|
-
|
36
|
-
Configuration.logger.error error_message
|
37
|
-
fail error_to_throw, error_message
|
42
|
+
context
|
38
43
|
end
|
39
44
|
|
40
|
-
context
|
41
|
-
|
42
|
-
|
43
|
-
def self.verify_keys(context, action, &block)
|
44
|
-
ReservedKeysVerifier.new(context, action).verify
|
45
|
-
ExpectedKeyVerifier.new(context, action).verify
|
45
|
+
def self.verify_keys(context, action, &block)
|
46
|
+
ReservedKeysVerifier.new(context, action).verify
|
47
|
+
ExpectedKeyVerifier.new(context, action).verify
|
46
48
|
|
47
|
-
|
49
|
+
block.call
|
48
50
|
|
49
|
-
|
51
|
+
PromisedKeyVerifier.new(context, action).verify
|
52
|
+
end
|
50
53
|
end
|
51
|
-
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
class ExpectedKeyVerifier < KeyVerifier
|
56
|
+
def type_name
|
57
|
+
"expected"
|
58
|
+
end
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
def keys
|
61
|
+
action.expected_keys
|
62
|
+
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
def error_to_throw
|
65
|
+
ExpectedKeysNotInContextError
|
66
|
+
end
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
+
def throw_error_predicate(keys)
|
69
|
+
!are_all_keys_in_context?(keys)
|
70
|
+
end
|
68
71
|
end
|
69
|
-
end
|
70
72
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
class PromisedKeyVerifier < KeyVerifier
|
74
|
+
def type_name
|
75
|
+
"promised"
|
76
|
+
end
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
78
|
+
def keys
|
79
|
+
action.promised_keys
|
80
|
+
end
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
-
|
82
|
+
def error_to_throw
|
83
|
+
PromisedKeysNotInContextError
|
84
|
+
end
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
+
def throw_error_predicate(keys)
|
87
|
+
!are_all_keys_in_context?(keys)
|
88
|
+
end
|
86
89
|
end
|
87
|
-
end
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
class ReservedKeysVerifier < KeyVerifier
|
92
|
+
def violated_keys
|
93
|
+
(action.promised_keys + action.expected_keys) & reserved_keys
|
94
|
+
end
|
93
95
|
|
94
|
-
|
95
|
-
|
96
|
-
|
96
|
+
def error_message
|
97
|
+
"promised or expected keys cannot be a " \
|
98
|
+
"reserved key: [#{format_keys(violated_keys)}]"
|
99
|
+
end
|
97
100
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
+
def keys
|
102
|
+
violated_keys
|
103
|
+
end
|
101
104
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
+
def error_to_throw
|
106
|
+
ReservedKeysInContextError
|
107
|
+
end
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
+
def throw_error_predicate(keys)
|
110
|
+
keys.any?
|
111
|
+
end
|
109
112
|
|
110
|
-
|
111
|
-
|
113
|
+
def reserved_keys
|
114
|
+
[:message, :error_code, :current_action].freeze
|
115
|
+
end
|
112
116
|
end
|
113
117
|
end
|
114
|
-
end
|
118
|
+
end
|
@@ -1,22 +1,25 @@
|
|
1
1
|
module LightService
|
2
2
|
class LocalizationAdapter
|
3
|
-
def failure(message_or_key, action_class, i18n_options={})
|
3
|
+
def failure(message_or_key, action_class, i18n_options = {})
|
4
4
|
find_translated_message(message_or_key,
|
5
5
|
action_class,
|
6
6
|
i18n_options,
|
7
|
-
|
7
|
+
:type => :failure)
|
8
8
|
end
|
9
9
|
|
10
|
-
def success(message_or_key, action_class, i18n_options={})
|
10
|
+
def success(message_or_key, action_class, i18n_options = {})
|
11
11
|
find_translated_message(message_or_key,
|
12
12
|
action_class,
|
13
13
|
i18n_options,
|
14
|
-
|
14
|
+
:type => :success)
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
def find_translated_message(message_or_key,
|
19
|
+
def find_translated_message(message_or_key,
|
20
|
+
action_class,
|
21
|
+
i18n_options,
|
22
|
+
type)
|
20
23
|
if message_or_key.is_a?(Symbol)
|
21
24
|
i18n_options.merge!(type)
|
22
25
|
translate(message_or_key, action_class, i18n_options)
|
@@ -25,11 +28,11 @@ module LightService
|
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
28
|
-
def translate(key, action_class, options={})
|
31
|
+
def translate(key, action_class, options = {})
|
29
32
|
type = options.delete(:type)
|
30
33
|
|
31
34
|
scope = i18n_scope_from_class(action_class, type)
|
32
|
-
options
|
35
|
+
options[:scope] = scope
|
33
36
|
|
34
37
|
I18n.t(key, options)
|
35
38
|
end
|
@@ -4,16 +4,19 @@ module LightService
|
|
4
4
|
base_class.extend ClassMethods
|
5
5
|
base_class.extend Macros
|
6
6
|
end
|
7
|
+
|
7
8
|
def self.included(base_class)
|
8
|
-
|
9
|
+
warning_msg = "including LightService::Organizer is deprecated. " \
|
10
|
+
"Please use `extend LightService::Organizer` instead"
|
11
|
+
ActiveSupport::Deprecation.warn(warning_msg)
|
9
12
|
base_class.extend ClassMethods
|
10
13
|
base_class.extend Macros
|
11
14
|
end
|
12
15
|
|
13
16
|
# In case this module is included
|
14
17
|
module ClassMethods
|
15
|
-
def with(data={})
|
16
|
-
data
|
18
|
+
def with(data = {})
|
19
|
+
data[:_aliases] = @aliases if @aliases
|
17
20
|
WithReducerFactory.make(self).with(data)
|
18
21
|
end
|
19
22
|
|