light-service 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|