light-service 0.14.0 → 0.15.0

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
  SHA256:
3
- metadata.gz: 10ae707b6938f2898615668928728eb8e562fb9ed2f0ecf17e50deff76226a44
4
- data.tar.gz: c870f719d663dd1aec96d895196f57b2caaae1cb9e61d073bd5fcef7ce33cff5
3
+ metadata.gz: f400829da41766f56e7f0cb9cfe95aa028ef8ab34bf0bc5794bfafcff3cdcfb1
4
+ data.tar.gz: 41dbd504ac39a0804eb445b4af086dbcf9986264077994277e17fde98b7bdc3d
5
5
  SHA512:
6
- metadata.gz: 3062b1c4d10519f2f60c527ecedee3647d13c85419e5ad38db7ec7046b0cba68b122d044a1045724bc034c64d6e3b4afc3482cfa9bb8a87ee77ef5f847e0e728
7
- data.tar.gz: c240aec3f4244f7ebb17f06c98e21c03495c1718d34dafcd862209fc95323bde27db6e9144e38ad285d0b83b2182713d5d1690dc43f28c95b591dd9d56aec7b7
6
+ metadata.gz: e9a1148b82b6e1a0813400b0d23b013ff6f06965f3204bb3fa95101adc8a749223dd334887f7543622b4a829fd17f190c21dd5f601df3aecde8d8451459a76af
7
+ data.tar.gz: f0aec690e94896dcc13d6caa774003e49ddfa6b74c5acf9272fb7b9d3710d28db4a29601e725e3729deba820013c62b740739d5c6052c4c5ae93fd885cec6fda
@@ -20,7 +20,6 @@ script:
20
20
  - bundle exec rubocop
21
21
 
22
22
  gemfile:
23
- - gemfiles/activesupport_3.gemfile
24
23
  - gemfiles/activesupport_4.gemfile
25
24
  - gemfiles/activesupport_5.gemfile
26
25
  - gemfiles/activesupport_6.gemfile
data/Appraisals CHANGED
@@ -1,7 +1,3 @@
1
- appraise "activesupport-3" do
2
- gem "activesupport", "~> 3.0"
3
- end
4
-
5
1
  appraise "activesupport-4" do
6
2
  gem "activesupport", "~> 4.0"
7
3
  end
data/Gemfile CHANGED
@@ -2,5 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in light_service.gemspec
4
4
  gemspec
5
-
6
- gem 'appraisal', '~> 2.0'
data/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/light-service.svg)](https://rubygems.org/gems/light-service)
4
4
  [![Build Status](https://secure.travis-ci.org/adomokos/light-service.svg)](http://travis-ci.org/adomokos/light-service)
5
+ [![codecov](https://codecov.io/gh/adomokos/light-service/branch/master/graph/badge.svg)](https://codecov.io/gh/adomokos/light-service)
5
6
  [![Code Climate](https://codeclimate.com/github/adomokos/light-service.svg)](https://codeclimate.com/github/adomokos/light-service)
6
7
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
7
8
  [![Download Count](https://ruby-gem-downloads-badge.herokuapp.com/light-service?type=total)](https://rubygems.org/gems/light-service)
8
9
 
9
- <br><br>
10
+ <br>
10
11
 
11
12
  ![Orchestrators-Deprecated](resources/orchestrators_deprecated.svg)
12
13
  <br>Version 0.9.0 deprecates Orchestrators and moves all their functionalities into Organizers. Please check out [this PR](https://github.com/adomokos/light-service/pull/132) to see the changes.
@@ -15,6 +16,11 @@
15
16
 
16
17
  ## Table of Content
17
18
  * [Why LightService?](#why-lightservice)
19
+ * [Getting Started](#getting-started)
20
+ * [Requirements](#requirements)
21
+ * [Installation](#installation)
22
+ * [Your first action](#your-first-action)
23
+ * [Your first organizer](#your-first-organizer)
18
24
  * [Stopping the Series of Actions](#stopping-the-series-of-actions)
19
25
  * [Failing the Context](#failing-the-context)
20
26
  * [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
@@ -27,7 +33,7 @@
27
33
  * [Localizing Messages](#localizing-messages)
28
34
  * [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
29
35
  * [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
30
-
36
+ * [Rails support](#rails-support)
31
37
 
32
38
  ## Why LightService?
33
39
 
@@ -175,7 +181,143 @@ end
175
181
  I gave a [talk at RailsConf 2013](http://www.adomokos.com/2013/06/simple-and-elegant-rails-code-with.html) on
176
182
  simple and elegant Rails code where I told the story of how LightService was extracted from the projects I had worked on.
177
183
 
184
+ ## Getting started
185
+
186
+ ### Requirements
187
+
188
+ This gem requires ruby 2.x. Use of [generators](#rails-support) requires Rails 5+ (tested on Rails 5.x & 6.x only. Will probably work on
189
+ Rails versions as old as 3.2)
190
+
191
+ ### Installation
192
+
193
+ In your Gemfile:
194
+
195
+ ```ruby
196
+ gem 'light-service'
197
+ ```
198
+
199
+ And then
200
+
201
+ ```shell
202
+ bundle install
203
+ ```
204
+
205
+ Or install it yourself as:
206
+
207
+ ```shell
208
+ gem install light-service
209
+ ```
210
+
211
+ ### Your first action
212
+
213
+ LightService's building blocks are actions that are normally composed within an organizer, but can be run independently.
214
+ Let's make a simple greeter action. Each action can take an optional list of expected inputs and promised outputs. If
215
+ these are specified and missing at action start and stop respectively, an exception will be thrown.
216
+
217
+ ```ruby
218
+ class GreetsPerson
219
+ extend ::LightService::Action
220
+
221
+ expects :name
222
+ promises :greeting
223
+
224
+ executed do |context|
225
+ context.greeting = "Hey there, #{name}. You enjoying LightService so far?"
226
+ end
227
+ end
228
+ ```
178
229
 
230
+ When an action is run, you have access to its returned context, and the status of the action. You can invoke an
231
+ action by calling `.execute` on its class with `key: value` arguments, and inspect its status and context like so:
232
+
233
+ ```ruby
234
+ outcome = GreetsPerson.execute(name: "Han")
235
+
236
+ if outcome.success?
237
+ puts outcome.greeting # which was a promised context value
238
+ elsif outcome.failure?
239
+ puts "Rats... I can't say hello to you"
240
+ end
241
+ ```
242
+
243
+ You will notice that actions are set up to promote simplicity, i.e. they either succeed or fail, and they have
244
+ very clear inputs and outputs. Ideally, they should do [exactly one thing](https://en.wikipedia.org/wiki/Single-responsibility_principle). This makes them as easy to test as unit tests.
245
+
246
+ ### Your first organizer
247
+
248
+ LightService provides a facility to compose actions using organizers. This is great when you have a business process
249
+ to execute that has multiple steps. By composing actions that do exactly one thing, you can sequence simple
250
+ actions together to perform complex multi-step business processes in a clear manner that is very easy
251
+ to reason about.
252
+
253
+ There are advanced ways to sequence actions that can be found later in the README, but we'll keep this simple for now.
254
+ First, let's add a second action that we can sequence to run after the `GreetsPerson` action from above:
255
+
256
+ ```ruby
257
+ class RandomlyAwardsPrize
258
+ extend ::LightService::Action
259
+
260
+ expects :name, :greeting
261
+ promises :did_i_win
262
+
263
+ executed do |context|
264
+ prize_num = "#{context.name}__#{context.greeting}".length
265
+ prizes = ["jelly beans", "ice cream", "pie"]
266
+ did_i_win = rand((1..prize_num)) % 7 == 0
267
+ did_i_lose = rand((1..prize_num)) % 13 == 0
268
+
269
+ if did_i_lose
270
+ # When failing, send a message as an argument, readable from the return context
271
+ context.fail!("you are exceptionally unlucky")
272
+ else
273
+ # You can specify 'optional' context items by treating context like a hash.
274
+ # Useful for when you may or may not be returning extra data. Ideally, selecting
275
+ # a prize should be a separate action that is only run if you win.
276
+ context[:prize] = "lifetime supply of #{prizes.sample}" if did_i_win
277
+ context.did_i_win = did_i_win
278
+ end
279
+ end
280
+ end
281
+ ```
282
+
283
+ And here's the organizer that ties the two together. You implement a `call` class method that takes some arguments and
284
+ from there sends them to `with` in `key: value` format which forms the initial state of the context. From there, chain
285
+ `reduce` to `with` and send it a list of action class names in sequence. The organizer will call each action, one
286
+ after the other, and build up the context as it goes along.
287
+
288
+ ```ruby
289
+ class WelcomeAPotentiallyLuckyPerson
290
+ extend LightService::Organizer
291
+
292
+ def self.call(name)
293
+ with(:name => name).reduce(GreetsPerson, RandomlyAwardsPrize)
294
+ end
295
+ end
296
+ ```
297
+
298
+ When an organizer is run, you have access to the context as it passed through all actions, and the overall status
299
+ of the organized execution. You can invoke an organizer by calling `.call` on the class with the expected arguments,
300
+ and inspect its status and context just like you would an action:
301
+
302
+ ```ruby
303
+ outcome = WelcomeAPotentiallyLuckyPerson.call("Han")
304
+
305
+ if outcome.success?
306
+ puts outcome.greeting # which was a promised context value
307
+
308
+ if outcome.did_i_win
309
+ puts "And you've won a prize! Lucky you. Please see the front desk for your #{outcome.prize}."
310
+ end
311
+ else # outcome.failure? is true, and we can pull the failure message out of the context for feedback to the user.
312
+ puts "Rats... I can't say hello to you, because #{outcome.message}."
313
+ end
314
+ ```
315
+
316
+ Because organizers generally run through complex business logic, and every action has the potential to cause a failure,
317
+ testing an organizer is functionally equivalent to an integration test.
318
+
319
+ For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki) and review
320
+ the ["Why LightService" section](#why-lightservice) above.
179
321
 
180
322
  ## Stopping the Series of Actions
181
323
  When nothing unexpected happens during the organizer's call, the returned `context` will be successful. Here is how you can check for this:
@@ -857,28 +999,52 @@ This context then can be passed to the action under test, freeing you up from th
857
999
 
858
1000
  In case your organizer has more logic in its `call` method, you could create your own test organizer in your specs like you can see it in this [acceptance test](spec/acceptance/testing/context_factory_spec.rb#L4-L11). This is reusable in all your action tests.
859
1001
 
860
- ## Requirements
1002
+ ## Rails support
1003
+
1004
+ LightService includes Rails generators for creating both Organizers and Actions along with corresponding tests. Currently only RSpec is
1005
+ supported ([PR's for supporting MiniTest are welcome](https://github.com/adomokos/light-service/pulls))
861
1006
 
862
- This gem requires ruby 2.x
1007
+ Note: Generators are namespaced to `light_service` not `light-service` due to Rake name constraints.
863
1008
 
864
- ## Installation
865
- Add this line to your application's Gemfile:
1009
+ ### Organizer generation
866
1010
 
867
- gem 'light-service'
1011
+ ```shell
1012
+ rails generate light_service:organizer My::SuperFancy::Organizer
1013
+ # -- or
1014
+ rails generate light_service:organizer my/super_fancy/organizer
1015
+ ```
868
1016
 
869
- And then execute:
1017
+ Options for this generator are:
870
1018
 
871
- $ bundle
1019
+ * `--dir=<SOME_DIR>`. `<SOME_DIR>` defaults to `organizers`. Will write organizers to `/app/organizers`, and specs to `/spec/organizers`
1020
+ * `--no-tests`. Default is `--tests`. Will generate a test file matching the namespace you've supplied.
872
1021
 
873
- Or install it yourself as:
1022
+ ### Action generation
1023
+
1024
+ ```shell
1025
+ rails generate light_service:action My::SuperFancy::Action
1026
+ # -- or
1027
+ rails generate light_service:action my/super_fancy/action
1028
+ ```
1029
+
1030
+ Options for this generator are:
874
1031
 
875
- $ gem install light-service
1032
+ * `--dir=<SOME_DIR>`. `<SOME_DIR>` defaults to `actions`. Will write actions to `/app/actions`, and specs to `/spec/actions`
1033
+ * `--no-tests`. Defaults is `--tests`. Will generate a test file matching the namespace you've supplied.
1034
+ * `--no-roll-back`. Default is `--roll-back`. Will generate a `rolled_back` block for you to implement with [roll back functionality](#action-rollback).
1035
+
1036
+ ### Advanced action generation
1037
+
1038
+ You are able to optionally specify `expects` and/or `promises` keys during generation
1039
+
1040
+ ```shell
1041
+ rails generate light_service:action CrankWidget expects:one_fish,two_fish promises:red_fish,blue_fish
1042
+ ```
876
1043
 
877
- ## Usage
878
- Based on the refactoring example above, just create an organizer object that calls the
879
- actions in order and write code for the actions. That's it.
1044
+ When specifying `expects`, convenience variables will be initialized in the `executed` block so that you don't have to call
1045
+ them through the context. A stub context will be created in the test file using these keys too.
880
1046
 
881
- For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki).
1047
+ When specifying `promises`, specs will be created testing for their existence after executing the action.
882
1048
 
883
1049
  ## Contributing
884
1050
  1. Fork it
@@ -1,5 +1,11 @@
1
1
  A brief list of new features and changes introduced with the specified version.
2
2
 
3
+ ### 0.15.0
4
+ * [Add Rails Generators](https://github.com/adomokos/light-service/pull/194) - LightService actions and organizers can be generated with generators
5
+ * [Add CodeCov](https://github.com/adomokos/light-service/pull/195) - Upload code coverage report to codecov.io
6
+ * [Remove ActiveSupport 3 checks](https://github.com/adomokos/light-service/pull/197) - They are unsupported, no need to tests them any more.
7
+
8
+
3
9
  ### 0.14.0
4
10
  * [Add 'organized_by' to context](https://github.com/adomokos/light-service/pull/192) - Context now have an #organized_by attribute
5
11
 
@@ -3,6 +3,5 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activesupport", "~> 4.0"
6
- gem "appraisal", "~> 2.0"
7
6
 
8
7
  gemspec :path => "../"
@@ -3,6 +3,5 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activesupport", "~> 5.0"
6
- gem "appraisal", "~> 2.0"
7
6
 
8
7
  gemspec :path => "../"
@@ -3,6 +3,5 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activesupport", "~> 6.0"
6
- gem "appraisal", "~> 2.0"
7
6
 
8
7
  gemspec :path => "../"
@@ -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? -%>
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.14.0".freeze
2
+ VERSION = "0.15.0".freeze
3
3
  end
@@ -18,8 +18,12 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.add_runtime_dependency("activesupport", ">= 3.0.0")
20
20
 
21
+ gem.add_development_dependency("generator_spec", "~> 0.9.4")
22
+ gem.add_development_dependency("test-unit", "~> 3.0") # Needed for generator specs.
23
+ gem.add_development_dependency("appraisal", "~> 2.3")
21
24
  gem.add_development_dependency("rspec", "~> 3.0")
22
25
  gem.add_development_dependency("simplecov", "~> 0.17")
26
+ gem.add_development_dependency("codecov", "~> 0.1")
23
27
  gem.add_development_dependency("rubocop", "~> 0.68.0")
24
28
  gem.add_development_dependency("rubocop-performance", "~> 1.2.0")
25
29
  gem.add_development_dependency("pry", "~> 0.12.2")
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../lib/generators/light_service/action_generator.rb'
4
+ require_relative './full_generator_test_blobs'
5
+
6
+ describe LightService::Generators::ActionGenerator, :type => :generator do
7
+ destination File.expand_path('tmp', __dir__)
8
+
9
+ context "when generating an advanced action" do
10
+ before(:all) do
11
+ prepare_destination
12
+ run_generator
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf destination_root
17
+ end
18
+
19
+ arguments %w[
20
+ my/fancy/action
21
+ expects:foo,bar
22
+ promises:baz,qux
23
+ --no-roll-back
24
+ --dir=services
25
+ ]
26
+
27
+ specify do
28
+ expect(destination_root).to(have_structure do
29
+ directory "app/services/my/fancy" do
30
+ file "action.rb" do
31
+ contains FullGeneratorTestBlobs.advanced_action_blob
32
+ end
33
+ end
34
+
35
+ directory "spec/services/my/fancy" do
36
+ file "action_spec.rb" do
37
+ contains FullGeneratorTestBlobs.advanced_action_spec_blob
38
+ end
39
+ end
40
+ end)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../lib/generators/light_service/action_generator.rb'
4
+ require_relative './full_generator_test_blobs'
5
+
6
+ describe LightService::Generators::ActionGenerator, :type => :generator do
7
+ destination File.expand_path('tmp', __dir__)
8
+
9
+ context "when generating a simple action" do
10
+ before(:all) do
11
+ prepare_destination
12
+ run_generator
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf destination_root
17
+ end
18
+
19
+ arguments %w[my_action]
20
+
21
+ specify do
22
+ expect(destination_root).to(have_structure do
23
+ directory "app/actions" do
24
+ file "my_action.rb" do
25
+ contains FullGeneratorTestBlobs.simple_action_blob
26
+ end
27
+ end
28
+
29
+ directory "spec/actions" do
30
+ file "my_action_spec.rb" do
31
+ contains FullGeneratorTestBlobs.simple_action_spec_blob
32
+ end
33
+ end
34
+ end)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,193 @@
1
+ # rubocop:disable Metrics/ClassLength, Metrics/MethodLength
2
+ class FullGeneratorTestBlobs
3
+ def self.simple_action_blob
4
+ <<~BLOB
5
+ # frozen_string_literal: true
6
+
7
+ class MyAction
8
+ extend ::LightService::Action
9
+
10
+ executed do |ctx|
11
+ end
12
+
13
+ rolled_back do |ctx|
14
+ end
15
+ end
16
+ BLOB
17
+ end
18
+
19
+ def self.simple_action_spec_blob
20
+ <<~BLOB
21
+ # frozen_string_literal: true
22
+
23
+ require 'rails_helper'
24
+
25
+ RSpec.describe MyAction, type: :action do
26
+ subject { described_class.execute(ctx) }
27
+
28
+ let(:ctx) do
29
+ {
30
+ }
31
+ end
32
+
33
+ context "when executed" do
34
+ xit "is expected to be successful" do
35
+ expect(subject).to be_a_success
36
+ end
37
+ end
38
+ end
39
+ BLOB
40
+ end
41
+
42
+ # There's some weird whitespace issue which prevents
43
+ # using HEREDOCS :(
44
+ def self.advanced_action_blob
45
+ "# frozen_string_literal: true\n" \
46
+ "\n" \
47
+ "module My::Fancy\n" \
48
+ " class Action\n" \
49
+ " extend ::LightService::Action\n" \
50
+ "\n" \
51
+ " expects :foo, :bar\n" \
52
+ " promises :baz, :qux\n" \
53
+ "\n" \
54
+ " executed do |ctx|\n" \
55
+ " foo = ctx.foo\n" \
56
+ " bar = ctx.bar\n" \
57
+ " end\n" \
58
+ " end\n" \
59
+ "end"
60
+ end
61
+
62
+ def self.advanced_action_spec_blob
63
+ <<~BLOB
64
+ # frozen_string_literal: true
65
+
66
+ require 'rails_helper'
67
+
68
+ RSpec.describe My::Fancy::Action, type: :action do
69
+ subject { described_class.execute(ctx) }
70
+
71
+ let(:ctx) do
72
+ {
73
+ foo: nil,
74
+ bar: nil,
75
+ }
76
+ end
77
+
78
+ context "when executed" do
79
+ xit "is expected to be successful" do
80
+ expect(subject).to be_a_success
81
+ end
82
+
83
+ xit "is expected to promise 'baz'" do
84
+ expect(subject.baz).to eq SomeBazClass
85
+ end
86
+
87
+ xit "is expected to promise 'qux'" do
88
+ expect(subject.qux).to eq SomeQuxClass
89
+ end
90
+ end
91
+ end
92
+ BLOB
93
+ end
94
+
95
+ def self.simple_organizer_blob
96
+ <<~BLOB
97
+ # frozen_string_literal: true
98
+
99
+ class MyOrganizer
100
+ extend ::LightService::Organizer
101
+
102
+ def self.call(params)
103
+ with(
104
+ #foo: params[:foo],
105
+ #bar: params[:bar]
106
+ ).reduce(actions)
107
+ end
108
+
109
+ def self.actions
110
+ [
111
+ #OneAction,
112
+ #TwoAction,
113
+ ]
114
+ end
115
+ end
116
+ BLOB
117
+ end
118
+
119
+ def self.simple_organizer_spec_blob
120
+ <<~BLOB
121
+ # frozen_string_literal: true
122
+
123
+ require 'rails_helper'
124
+
125
+ RSpec.describe MyOrganizer, type: :organizer do
126
+ subject { described_class.call(ctx) }
127
+
128
+ let(:ctx) do
129
+ {
130
+ #foo: 'something foo',
131
+ #bar: { baz: qux },
132
+ }
133
+ end
134
+
135
+ context "when called" do
136
+ xit "is expected to be successful" do
137
+ expect(subject).to be_a_success
138
+ end
139
+ end
140
+ end
141
+ BLOB
142
+ end
143
+
144
+ def self.advanced_organizer_blob
145
+ "# frozen_string_literal: true\n" \
146
+ "\n" \
147
+ "module My::Fancy\n" \
148
+ " class Organizer\n" \
149
+ " extend ::LightService::Organizer\n" \
150
+ "\n" \
151
+ " def self.call(params)\n" \
152
+ " with(\n" \
153
+ " #foo: params[:foo],\n" \
154
+ " #bar: params[:bar]\n" \
155
+ " ).reduce(actions)\n" \
156
+ " end\n" \
157
+ "\n" \
158
+ " def self.actions\n" \
159
+ " [\n" \
160
+ " #My::Fancy::OneAction,\n" \
161
+ " #My::Fancy::TwoAction,\n" \
162
+ " ]\n" \
163
+ " end\n" \
164
+ " end\n" \
165
+ "end"
166
+ end
167
+
168
+ def self.advanced_organizer_spec_blob
169
+ <<~BLOB
170
+ # frozen_string_literal: true
171
+
172
+ require 'rails_helper'
173
+
174
+ RSpec.describe My::Fancy::Organizer, type: :organizer do
175
+ subject { described_class.call(ctx) }
176
+
177
+ let(:ctx) do
178
+ {
179
+ #foo: 'something foo',
180
+ #bar: { baz: qux },
181
+ }
182
+ end
183
+
184
+ context "when called" do
185
+ xit "is expected to be successful" do
186
+ expect(subject).to be_a_success
187
+ end
188
+ end
189
+ end
190
+ BLOB
191
+ end
192
+ end
193
+ # rubocop:enable Metrics/ClassLength, Metrics/MethodLength
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../lib/generators/light_service/organizer_generator.rb'
4
+ require_relative './full_generator_test_blobs'
5
+
6
+ describe LightService::Generators::OrganizerGenerator, :type => :generator do
7
+ destination File.expand_path('tmp', __dir__)
8
+
9
+ context "when generating an advanced organizer" do
10
+ before(:all) do
11
+ prepare_destination
12
+ run_generator
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf destination_root
17
+ end
18
+
19
+ arguments %w[my/fancy/organizer --dir=processes]
20
+
21
+ specify do
22
+ expect(destination_root).to(have_structure do
23
+ directory "app/processes/my/fancy" do
24
+ file "organizer.rb" do
25
+ contains FullGeneratorTestBlobs.advanced_organizer_blob
26
+ end
27
+ end
28
+
29
+ directory "spec/processes/my/fancy" do
30
+ file "organizer_spec.rb" do
31
+ contains FullGeneratorTestBlobs.advanced_organizer_spec_blob
32
+ end
33
+ end
34
+ end)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../lib/generators/light_service/organizer_generator.rb'
4
+ require_relative './full_generator_test_blobs'
5
+
6
+ describe LightService::Generators::OrganizerGenerator, :type => :generator do
7
+ destination File.expand_path('tmp', __dir__)
8
+
9
+ context "when generating a simple organizer" do
10
+ before(:all) do
11
+ prepare_destination
12
+ run_generator
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf destination_root
17
+ end
18
+
19
+ arguments %w[my_organizer]
20
+
21
+ specify do
22
+ expect(destination_root).to(have_structure do
23
+ directory "app/organizers" do
24
+ file "my_organizer.rb" do
25
+ contains FullGeneratorTestBlobs.simple_organizer_blob
26
+ end
27
+ end
28
+
29
+ directory "spec/organizers" do
30
+ file "my_organizer_spec.rb" do
31
+ contains FullGeneratorTestBlobs.simple_organizer_spec_blob
32
+ end
33
+ end
34
+ end)
35
+ end
36
+ end
37
+ end
@@ -8,8 +8,10 @@ if ENV['RUN_COVERAGE_REPORT']
8
8
  add_filter 'vendor/'
9
9
  add_filter %r{^/spec/}
10
10
  end
11
-
12
11
  SimpleCov.minimum_coverage_by_file 90
12
+
13
+ require 'codecov'
14
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
13
15
  end
14
16
 
15
17
  require 'light-service'
@@ -19,5 +21,7 @@ require 'pry'
19
21
  require 'support'
20
22
  require 'test_doubles'
21
23
  require 'stringio'
24
+ require 'fileutils'
25
+ require 'generator_spec'
22
26
 
23
27
  I18n.enforce_available_locales = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: light-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Attila Domokos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-02 00:00:00.000000000 Z
11
+ date: 2020-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: generator_spec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.4
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: appraisal
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.3'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: rspec
29
71
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +94,20 @@ dependencies:
52
94
  - - "~>"
53
95
  - !ruby/object:Gem::Version
54
96
  version: '0.17'
97
+ - !ruby/object:Gem::Dependency
98
+ name: codecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.1'
55
111
  - !ruby/object:Gem::Dependency
56
112
  name: rubocop
57
113
  requirement: !ruby/object:Gem::Requirement
@@ -112,10 +168,16 @@ files:
112
168
  - README.md
113
169
  - RELEASES.md
114
170
  - Rakefile
115
- - gemfiles/activesupport_3.gemfile
116
171
  - gemfiles/activesupport_4.gemfile
117
172
  - gemfiles/activesupport_5.gemfile
118
173
  - gemfiles/activesupport_6.gemfile
174
+ - lib/generators/light_service/action_generator.rb
175
+ - lib/generators/light_service/generator_utils.rb
176
+ - lib/generators/light_service/organizer_generator.rb
177
+ - lib/generators/light_service/templates/action_spec_template.erb
178
+ - lib/generators/light_service/templates/action_template.erb
179
+ - lib/generators/light_service/templates/organizer_spec_template.erb
180
+ - lib/generators/light_service/templates/organizer_template.erb
119
181
  - lib/light-service.rb
120
182
  - lib/light-service/action.rb
121
183
  - lib/light-service/configuration.rb
@@ -179,6 +241,11 @@ files:
179
241
  - spec/action_spec.rb
180
242
  - spec/context/inspect_spec.rb
181
243
  - spec/context_spec.rb
244
+ - spec/lib/generators/action_generator_advanced_spec.rb
245
+ - spec/lib/generators/action_generator_simple_spec.rb
246
+ - spec/lib/generators/full_generator_test_blobs.rb
247
+ - spec/lib/generators/organizer_generator_advanced_spec.rb
248
+ - spec/lib/generators/organizer_generator_simple_spec.rb
182
249
  - spec/localization_adapter_spec.rb
183
250
  - spec/organizer/with_reducer_spec.rb
184
251
  - spec/organizer_key_aliases_spec.rb
@@ -258,6 +325,11 @@ test_files:
258
325
  - spec/action_spec.rb
259
326
  - spec/context/inspect_spec.rb
260
327
  - spec/context_spec.rb
328
+ - spec/lib/generators/action_generator_advanced_spec.rb
329
+ - spec/lib/generators/action_generator_simple_spec.rb
330
+ - spec/lib/generators/full_generator_test_blobs.rb
331
+ - spec/lib/generators/organizer_generator_advanced_spec.rb
332
+ - spec/lib/generators/organizer_generator_simple_spec.rb
261
333
  - spec/localization_adapter_spec.rb
262
334
  - spec/organizer/with_reducer_spec.rb
263
335
  - spec/organizer_key_aliases_spec.rb
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 3.0"
6
- gem "appraisal", "~> 2.0"
7
-
8
- gemspec :path => "../"