light-service 0.11.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/project-build.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +7 -11
- data/Appraisals +4 -4
- data/Gemfile +0 -2
- data/README.md +257 -42
- data/RELEASES.md +21 -0
- data/gemfiles/activesupport_5.gemfile +0 -1
- data/gemfiles/{activesupport_3.gemfile → activesupport_6.gemfile} +1 -2
- data/lib/generators/light_service/action_generator.rb +90 -0
- data/lib/generators/light_service/generator_utils.rb +45 -0
- data/lib/generators/light_service/organizer_generator.rb +66 -0
- data/lib/generators/light_service/templates/action_spec_template.erb +31 -0
- data/lib/generators/light_service/templates/action_template.erb +30 -0
- data/lib/generators/light_service/templates/organizer_spec_template.erb +20 -0
- data/lib/generators/light_service/templates/organizer_template.erb +22 -0
- data/lib/light-service.rb +1 -0
- data/lib/light-service/action.rb +3 -0
- data/lib/light-service/context.rb +8 -4
- data/lib/light-service/context/key_verifier.rb +18 -1
- data/lib/light-service/localization_adapter.rb +1 -1
- data/lib/light-service/organizer.rb +27 -0
- data/lib/light-service/organizer/with_reducer.rb +8 -1
- data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +10 -4
- data/spec/acceptance/after_actions_spec.rb +17 -0
- data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
- data/spec/acceptance/fail_spec.rb +42 -16
- data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +57 -0
- data/spec/acceptance/organizer/execute_spec.rb +1 -1
- data/spec/acceptance/organizer/execute_with_add_to_context_spec.rb +28 -0
- data/spec/acceptance/organizer/iterate_spec.rb +7 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
- data/spec/action_spec.rb +8 -0
- data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
- data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
- data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
- data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
- data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
- data/spec/organizer_spec.rb +21 -0
- data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -2
- data/spec/test_doubles.rb +47 -0
- metadata +111 -21
- data/gemfiles/activesupport_3.gemfile.lock +0 -76
- data/gemfiles/activesupport_4.gemfile +0 -8
- data/gemfiles/activesupport_4.gemfile.lock +0 -82
- data/gemfiles/activesupport_5.gemfile.lock +0 -82
- data/resources/orchestrators_deprecated.svg +0 -10
data/RELEASES.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
A brief list of new features and changes introduced with the specified version.
|
2
2
|
|
3
|
+
### 0.16.0
|
4
|
+
* [Drop Ruby 2.4 support](https://github.com/adomokos/light-service/pull/207)
|
5
|
+
* [Fix callback current action](https://github.com/adomokos/light-service/pull/209)
|
6
|
+
* [Add Context accessors](https://github.com/adomokos/light-service/pull/211)
|
7
|
+
* [Switched to GH Actions from Travis CI](https://github.com/adomokos/light-service/pull/212)
|
8
|
+
|
9
|
+
### 0.15.0
|
10
|
+
* [Add Rails Generators](https://github.com/adomokos/light-service/pull/194) - LightService actions and organizers can be generated with generators
|
11
|
+
* [Add CodeCov](https://github.com/adomokos/light-service/pull/195) - Upload code coverage report to codecov.io
|
12
|
+
* [Remove ActiveSupport 3 checks](https://github.com/adomokos/light-service/pull/197) - They are unsupported, no need to tests them any more.
|
13
|
+
|
14
|
+
### 0.14.0
|
15
|
+
* [Add 'organized_by' to context](https://github.com/adomokos/light-service/pull/192) - Context now have an #organized_by attribute
|
16
|
+
|
17
|
+
### 0.13.0
|
18
|
+
* [Add 'add_to_context' and 'add_aliases'](https://github.com/adomokos/light-service/pull/172) - Updating Ruby compatibility, minor fixes
|
19
|
+
|
20
|
+
### 0.12.0
|
21
|
+
* [Per organizer logger](https://github.com/adomokos/light-service/pull/162)
|
22
|
+
* [Fix 'fail_and_return!' not accepting 'error_code' option](https://github.com/adomokos/light-service/pull/168)
|
23
|
+
|
3
24
|
### 0.11.0
|
4
25
|
* [Switch to 'each_with_object' in WithReducer](https://github.com/adomokos/light-service/pull/149).
|
5
26
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative './generator_utils'
|
2
|
+
|
3
|
+
module LightService
|
4
|
+
module Generators
|
5
|
+
class ActionGenerator < Rails::Generators::Base
|
6
|
+
include GeneratorUtils
|
7
|
+
|
8
|
+
argument :name, :type => :string
|
9
|
+
argument :keys,
|
10
|
+
:type => :hash,
|
11
|
+
:default => { "expects" => '', "promises" => '' },
|
12
|
+
:banner => "expects:one,thing promises:something,else"
|
13
|
+
|
14
|
+
class_option :dir,
|
15
|
+
:type => :string,
|
16
|
+
:default => "actions",
|
17
|
+
:desc => "Path to write actions to"
|
18
|
+
|
19
|
+
class_option :tests,
|
20
|
+
:type => :boolean,
|
21
|
+
:default => true,
|
22
|
+
:desc => "Generate tests (currently only RSpec supported)"
|
23
|
+
|
24
|
+
class_option :roll_back,
|
25
|
+
:type => :boolean,
|
26
|
+
:default => true,
|
27
|
+
:desc => "Add a roll back block"
|
28
|
+
|
29
|
+
source_root File.expand_path('templates', __dir__)
|
30
|
+
|
31
|
+
desc <<~DESCRIPTION
|
32
|
+
Description:
|
33
|
+
Will create the boilerplate for an action. Pass it an action name, e.g.
|
34
|
+
foo_bar, or FooBar - will create FooBar in app/actions/foo_bar.rb
|
35
|
+
foo/bar, or Foo::Bar - will create Foo::Bar in app/actions/foo/bar.rb
|
36
|
+
|
37
|
+
Expects & Promises:
|
38
|
+
Specify a list of expected context keys by passing expects and a comma separated
|
39
|
+
list of keys. Adds keys to the `expects` list, creates convenience variables in
|
40
|
+
the action, and generates a stub context in generated specs.
|
41
|
+
|
42
|
+
expects:foo,bar,baz
|
43
|
+
|
44
|
+
Specify promised context keys in the same manner as 'expects' above. This adds
|
45
|
+
keys to the `promises` list, and creates stub expectations in generated specs.
|
46
|
+
|
47
|
+
promises:quux,quark
|
48
|
+
|
49
|
+
Options:
|
50
|
+
Skip rspec test creation with --no-tests
|
51
|
+
Skip ActionRollback creation with --no-roll-back
|
52
|
+
Write actions to a specified dir with --dir="services". Default is "actions" in app/actions
|
53
|
+
|
54
|
+
Full Example:
|
55
|
+
rails g light_service:action My::Awesome::Action expects:foo,bar promises:baz,qux
|
56
|
+
DESCRIPTION
|
57
|
+
|
58
|
+
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
59
|
+
def create_action
|
60
|
+
gen_vals = create_required_gen_vals_from(name)
|
61
|
+
|
62
|
+
@module_path = gen_vals[:module_path]
|
63
|
+
@class_name = gen_vals[:class_name]
|
64
|
+
@full_class_name = gen_vals[:full_class_name]
|
65
|
+
@expects = keys["expects"].to_s.downcase.split(',')
|
66
|
+
@promises = keys["promises"].to_s.downcase.split(',')
|
67
|
+
|
68
|
+
file_name = gen_vals[:file_name]
|
69
|
+
file_path = gen_vals[:file_path]
|
70
|
+
|
71
|
+
root_dir = options.dir.downcase
|
72
|
+
action_dir = File.join('app', root_dir, *file_path)
|
73
|
+
action_file = "#{action_dir}/#{file_name}"
|
74
|
+
|
75
|
+
make_nested_dir(action_dir)
|
76
|
+
template("action_template.erb", action_file)
|
77
|
+
|
78
|
+
return unless must_gen_tests?
|
79
|
+
|
80
|
+
spec_dir = File.join('spec', root_dir, *file_path)
|
81
|
+
spec_file_name = gen_vals[:spec_file_name]
|
82
|
+
spec_file = "#{spec_dir}/#{spec_file_name}"
|
83
|
+
|
84
|
+
make_nested_dir(spec_dir)
|
85
|
+
template("action_spec_template.erb", spec_file)
|
86
|
+
end
|
87
|
+
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module LightService
|
2
|
+
module Generators
|
3
|
+
module GeneratorUtils
|
4
|
+
def make_nested_dir(dir)
|
5
|
+
FileUtils.mkdir_p(dir)
|
6
|
+
end
|
7
|
+
|
8
|
+
def supported_test_frameworks
|
9
|
+
%i[rspec]
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_framework_supported?
|
13
|
+
supported_test_frameworks.include? test_framework
|
14
|
+
end
|
15
|
+
|
16
|
+
# Don't know a better way to get to this value, unfortunately.
|
17
|
+
def test_framework
|
18
|
+
# Rails.application.config.generators.options[:rails][:test_framework]
|
19
|
+
# When/if Minitest is supported, this will need to be updated to detect
|
20
|
+
# the selected test framework, and switch templates accordingly
|
21
|
+
:rspec
|
22
|
+
end
|
23
|
+
|
24
|
+
def must_gen_tests?
|
25
|
+
options.tests? && test_framework_supported?
|
26
|
+
end
|
27
|
+
|
28
|
+
# rubocop:disable Metrics/AbcSize
|
29
|
+
def create_required_gen_vals_from(name)
|
30
|
+
path_parts = name.underscore.split('/')
|
31
|
+
|
32
|
+
{
|
33
|
+
:path_parts => path_parts,
|
34
|
+
:file_path => path_parts.reverse.drop(1).reverse,
|
35
|
+
:module_path => path_parts.reverse.drop(1).reverse.join('/').classify,
|
36
|
+
:class_name => path_parts.last.classify,
|
37
|
+
:file_name => "#{path_parts.last}.rb",
|
38
|
+
:spec_file_name => "#{path_parts.last}_spec.rb",
|
39
|
+
:full_class_name => name.classify
|
40
|
+
}
|
41
|
+
end
|
42
|
+
# rubocop:enable Metrics/AbcSize
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative './generator_utils'
|
2
|
+
|
3
|
+
module LightService
|
4
|
+
module Generators
|
5
|
+
class OrganizerGenerator < Rails::Generators::Base
|
6
|
+
include GeneratorUtils
|
7
|
+
|
8
|
+
argument :name, :type => :string
|
9
|
+
|
10
|
+
class_option :dir,
|
11
|
+
:type => :string,
|
12
|
+
:default => "organizers",
|
13
|
+
:desc => "Path to write organizers to"
|
14
|
+
|
15
|
+
class_option :tests,
|
16
|
+
:type => :boolean,
|
17
|
+
:default => true,
|
18
|
+
:desc => "Generate tests (currently only RSpec supported)"
|
19
|
+
|
20
|
+
source_root File.expand_path('templates', __dir__)
|
21
|
+
|
22
|
+
desc <<~DESCRIPTION
|
23
|
+
Description:
|
24
|
+
Will create the boilerplate for an organizer. Pass it an organizer name, e.g.
|
25
|
+
thing_maker, or ThingMaker - will create ThingMaker in app/organizers/thing_maker.rb
|
26
|
+
thing/maker, or Thing::Maker - will create Thing::Maker in app/organizers/thing/maker.rb
|
27
|
+
|
28
|
+
Options:
|
29
|
+
Skip rspec test creation with --no-tests
|
30
|
+
Write organizers to a specified dir with --dir="workflows". Default is "organizers" in app/organizers
|
31
|
+
|
32
|
+
Full Example:
|
33
|
+
rails g light_service:organizer My::Awesome::Organizer
|
34
|
+
DESCRIPTION
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
37
|
+
def create_organizer
|
38
|
+
gen_vals = create_required_gen_vals_from(name)
|
39
|
+
|
40
|
+
@module_path = gen_vals[:module_path]
|
41
|
+
@class_name = gen_vals[:class_name]
|
42
|
+
@full_class_name = gen_vals[:full_class_name]
|
43
|
+
|
44
|
+
file_name = gen_vals[:file_name]
|
45
|
+
file_path = gen_vals[:file_path]
|
46
|
+
|
47
|
+
root_dir = options.dir.downcase
|
48
|
+
organizer_dir = File.join('app', root_dir, *file_path)
|
49
|
+
organizer_file = "#{organizer_dir}/#{file_name}"
|
50
|
+
|
51
|
+
make_nested_dir(organizer_dir)
|
52
|
+
template("organizer_template.erb", organizer_file)
|
53
|
+
|
54
|
+
return unless must_gen_tests?
|
55
|
+
|
56
|
+
spec_dir = File.join('spec', root_dir, *file_path)
|
57
|
+
spec_file_name = gen_vals[:spec_file_name]
|
58
|
+
spec_file = "#{spec_dir}/#{spec_file_name}"
|
59
|
+
|
60
|
+
make_nested_dir(spec_dir)
|
61
|
+
template("organizer_spec_template.erb", spec_file)
|
62
|
+
end
|
63
|
+
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe <%= @full_class_name %>, type: :action do
|
6
|
+
subject { described_class.execute(ctx) }
|
7
|
+
|
8
|
+
let(:ctx) do
|
9
|
+
{
|
10
|
+
<%- if @expects.any? -%>
|
11
|
+
<%- @expects.each do |key| -%>
|
12
|
+
<%= key %>: nil,
|
13
|
+
<%- end -%>
|
14
|
+
<%- end -%>
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when executed" do
|
19
|
+
xit "is expected to be successful" do
|
20
|
+
expect(subject).to be_a_success
|
21
|
+
end
|
22
|
+
<%- if @promises.any? -%>
|
23
|
+
<%- @promises.each do |key| -%>
|
24
|
+
|
25
|
+
xit "is expected to promise '<%= key %>'" do
|
26
|
+
expect(subject.<%= key %>).to eq Some<%= key.classify %>Class
|
27
|
+
end
|
28
|
+
<%- end -%>
|
29
|
+
<%- end -%>
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%- indent = !@module_path.empty? ? ' ' : '' -%>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
<%= "module #{@module_path}\n" unless @module_path.empty? -%>
|
5
|
+
<%= "#{indent}class #{@class_name}" %>
|
6
|
+
<%= indent %>extend ::LightService::Action
|
7
|
+
|
8
|
+
<%- if @expects.any? -%>
|
9
|
+
<%= indent %>expects <%= @expects.map { |k| ":#{k}" }.join(', ') %>
|
10
|
+
<%- end -%>
|
11
|
+
<%- if @promises.any? -%>
|
12
|
+
<%= indent %>promises <%= @promises.map { |k| ":#{k}" }.join(', ') %>
|
13
|
+
<%- end -%>
|
14
|
+
<%- if (@expects + @promises).any? -%>
|
15
|
+
|
16
|
+
<%- end -%>
|
17
|
+
<%= indent %>executed do |ctx|
|
18
|
+
<%- if @expects.any? -%>
|
19
|
+
<%- @expects.each do |key| -%>
|
20
|
+
<%= indent %><%= key %> = ctx.<%= key %>
|
21
|
+
<%- end -%>
|
22
|
+
<%- end -%>
|
23
|
+
<%= indent %>end
|
24
|
+
<%- if options.roll_back -%>
|
25
|
+
|
26
|
+
<%= indent %>rolled_back do |ctx|
|
27
|
+
<%= indent %>end
|
28
|
+
<%- end -%>
|
29
|
+
<%= indent %>end
|
30
|
+
<%= 'end' unless @module_path.empty? -%>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe <%= @full_class_name %>, type: :organizer do
|
6
|
+
subject { described_class.call(ctx) }
|
7
|
+
|
8
|
+
let(:ctx) do
|
9
|
+
{
|
10
|
+
#foo: 'something foo',
|
11
|
+
#bar: { baz: qux },
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when called" do
|
16
|
+
xit "is expected to be successful" do
|
17
|
+
expect(subject).to be_a_success
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<%- indent = !@module_path.empty? ? ' ' : '' -%>
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
<%= "module #{@module_path}\n" unless @module_path.empty? -%>
|
5
|
+
<%= "#{indent}class #{@class_name}" %>
|
6
|
+
<%= indent %>extend ::LightService::Organizer
|
7
|
+
|
8
|
+
<%= indent %>def self.call(params)
|
9
|
+
<%= indent %> with(
|
10
|
+
<%= indent %> #foo: params[:foo],
|
11
|
+
<%= indent %> #bar: params[:bar]
|
12
|
+
<%= indent %> ).reduce(actions)
|
13
|
+
<%= indent %>end
|
14
|
+
|
15
|
+
<%= indent %>def self.actions
|
16
|
+
<%= indent %> [
|
17
|
+
<%= indent %> #<%= "#{@module_path}::" if @module_path.present? %>OneAction,
|
18
|
+
<%= indent %> #<%= "#{@module_path}::" if @module_path.present? %>TwoAction,
|
19
|
+
<%= indent %> ]
|
20
|
+
<%= indent %>end
|
21
|
+
<%= indent %>end
|
22
|
+
<%= 'end' unless @module_path.empty? -%>
|
data/lib/light-service.rb
CHANGED
data/lib/light-service/action.rb
CHANGED
@@ -44,6 +44,9 @@ module LightService
|
|
44
44
|
catch(:jump_when_failed) do
|
45
45
|
call_before_action(action_context)
|
46
46
|
yield(action_context)
|
47
|
+
|
48
|
+
# Reset the stored action in case it was changed downstream
|
49
|
+
action_context.current_action = self
|
47
50
|
call_after_action(action_context)
|
48
51
|
end
|
49
52
|
end
|
@@ -8,7 +8,7 @@ module LightService
|
|
8
8
|
|
9
9
|
# rubocop:disable ClassLength
|
10
10
|
class Context < Hash
|
11
|
-
attr_accessor :message, :error_code, :current_action
|
11
|
+
attr_accessor :message, :error_code, :current_action, :organized_by
|
12
12
|
|
13
13
|
def initialize(context = {},
|
14
14
|
outcome = Outcomes::SUCCESS,
|
@@ -18,6 +18,7 @@ module LightService
|
|
18
18
|
@message = message
|
19
19
|
@error_code = error_code
|
20
20
|
@skip_remaining = false
|
21
|
+
|
21
22
|
context.to_hash.each { |k, v| self[k] = v }
|
22
23
|
end
|
23
24
|
|
@@ -88,7 +89,7 @@ module LightService
|
|
88
89
|
|
89
90
|
def fail_and_return!(*args)
|
90
91
|
fail!(*args)
|
91
|
-
throw(:jump_when_failed
|
92
|
+
throw(:jump_when_failed)
|
92
93
|
end
|
93
94
|
|
94
95
|
def fail_with_rollback!(message = nil, error_code = nil)
|
@@ -114,9 +115,11 @@ module LightService
|
|
114
115
|
end
|
115
116
|
|
116
117
|
def define_accessor_methods_for_keys(keys)
|
117
|
-
return if keys.
|
118
|
-
|
118
|
+
return if keys.blank?
|
119
|
+
|
120
|
+
Array(keys).each do |key|
|
119
121
|
next if respond_to?(key.to_sym)
|
122
|
+
|
120
123
|
define_singleton_method(key.to_s) { fetch(key) }
|
121
124
|
define_singleton_method("#{key}=") { |value| self[key] = value }
|
122
125
|
end
|
@@ -161,6 +164,7 @@ module LightService
|
|
161
164
|
|
162
165
|
def check_nil(value)
|
163
166
|
return 'nil' unless value
|
167
|
+
|
164
168
|
"'#{value}'"
|
165
169
|
end
|
166
170
|
end
|
@@ -111,7 +111,24 @@ module LightService
|
|
111
111
|
end
|
112
112
|
|
113
113
|
def reserved_keys
|
114
|
-
%i[message error_code current_action].freeze
|
114
|
+
%i[message error_code current_action organized_by].freeze
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class ReservedKeysViaOrganizerVerifier < ReservedKeysVerifier
|
119
|
+
def initialize(context_data)
|
120
|
+
@context = LightService::Context.make(context_data)
|
121
|
+
end
|
122
|
+
|
123
|
+
def violated_keys
|
124
|
+
context.keys.map(&:to_sym) & reserved_keys
|
125
|
+
end
|
126
|
+
|
127
|
+
def error_message
|
128
|
+
<<~ERR
|
129
|
+
reserved keys cannot be added to the context
|
130
|
+
reserved key: [#{format_keys(violated_keys)}]
|
131
|
+
ERR
|
115
132
|
end
|
116
133
|
end
|
117
134
|
end
|