command_service_object 0.6.2 → 1.1.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +22 -9
  3. data/README.md +41 -22
  4. data/command_service_object.gemspec +2 -1
  5. data/lib/command_service_object.rb +1 -0
  6. data/lib/command_service_object/helpers/check_helper.rb +9 -0
  7. data/lib/command_service_object/version.rb +1 -1
  8. data/lib/generators/.DS_Store +0 -0
  9. data/lib/generators/service/command/command_generator.rb +13 -2
  10. data/lib/generators/service/{test/templates/rspec/command.rb.erb → command/templates/command_spec.rb.erb} +0 -0
  11. data/lib/generators/service/entity/entity_generator.rb +30 -0
  12. data/lib/generators/service/entity/templates/entity.rb.erb +9 -0
  13. data/lib/generators/service/external/external_generator.rb +37 -0
  14. data/lib/generators/service/external/templates/external.rb.erb +16 -0
  15. data/lib/generators/service/external/templates/external_spec.rb.erb +12 -0
  16. data/lib/generators/service/external/templates/external_test.rb.erb +0 -0
  17. data/lib/generators/service/install/templates/initializer.rb +5 -0
  18. data/lib/generators/service/install/templates/services/application_service.rb +57 -16
  19. data/lib/generators/service/install/templates/services/case_base.rb +15 -1
  20. data/lib/generators/service/install/templates/services/command_base.rb +8 -3
  21. data/lib/generators/service/install/templates/services/listener_base.rb +7 -0
  22. data/lib/generators/service/install/templates/services/query_base.rb +55 -0
  23. data/lib/generators/service/listener/listener_generator.rb +30 -0
  24. data/lib/generators/service/listener/templates/listener.rb.erb +11 -0
  25. data/lib/generators/service/query/USAGE +8 -0
  26. data/lib/generators/service/query/query_generator.rb +30 -0
  27. data/lib/generators/service/query/templates/query.rb.erb +22 -0
  28. data/lib/generators/service/service_generator.rb +1 -7
  29. data/lib/generators/service/setup/setup_generator.rb +10 -0
  30. data/lib/generators/service/setup/templates/doc.md.erb +39 -0
  31. data/lib/generators/service/usecase/templates/usecase.rb.erb +17 -4
  32. data/lib/generators/service/{test/templates/rspec/usecase.rb.erb → usecase/templates/usecase_spec.rb.erb} +0 -0
  33. data/lib/generators/service/usecase/usecase_generator.rb +12 -2
  34. metadata +39 -13
  35. data/lib/generators/service/policies.rb +0 -5
  36. data/lib/generators/service/test/USAGE +0 -8
  37. data/lib/generators/service/test/templates/minitest/usecase.rb.erb +0 -1
  38. data/lib/generators/service/test/test_generator.rb +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7cefa6382d8d484a958b67ad037d24fda4b7f3fad92f4bbc0a4a180a7056c32
4
- data.tar.gz: 1a09682c9b6dd977b096a217d88c2ec5592dd9e185462059c0bfaae6ac5c9af7
3
+ metadata.gz: 378dc78b1fdeb1e415506b8edacb761ee0a0d78c90b5b1cf90c081a506cf6183
4
+ data.tar.gz: 26395c6559e2f8124583c5002a519f39f32b781dfee944218a894bd6570f1715
5
5
  SHA512:
6
- metadata.gz: f4ac178e8404280d2275871fe84b07857e54fd19f41f0130de0f2757b5970fb2a4a9931d15baaff969079bf2ded3f6144e14d46694d13e325798e3d254bf459e
7
- data.tar.gz: daecf44e8b7c62896c46ca91f60e0624065905c4f9b9742ff9fab34b5eeb942449e44c8903e11653ba3bc63767786e9c2e63dfcc4486b405ba43dba28349daf4
6
+ metadata.gz: 446882a4a2a56924868783acffa56fdf54b0f61eedb48e1c58846f3de7795f32d7afde0b74fb02425530590324bc240eaf9befedc8b0cb306f52f1cb75e1cde4
7
+ data.tar.gz: 49e3cb994a9304e5b34d88d5abef25e4c79cbb751c1f466b8d232735534205e6092bb74219f4b05e4a46ec8e8cc295730c29b8b656e3ebcef612b98a9a0996f5
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- command_service_object (0.6.2)
4
+ command_service_object (0.6.5)
5
+ hutch (= 1.0)
5
6
  virtus (~> 1.0, >= 1.0.5)
6
7
 
7
8
  GEM
@@ -48,27 +49,38 @@ GEM
48
49
  i18n (>= 0.7, < 2)
49
50
  minitest (~> 5.1)
50
51
  tzinfo (~> 1.1)
52
+ amq-protocol (2.3.2)
51
53
  arel (9.0.0)
52
54
  axiom-types (0.1.1)
53
55
  descendants_tracker (~> 0.0.4)
54
56
  ice_nine (~> 0.11.0)
55
57
  thread_safe (~> 0.3, >= 0.3.1)
56
58
  builder (3.2.3)
59
+ bunny (2.15.0)
60
+ amq-protocol (~> 2.3, >= 2.3.1)
57
61
  byebug (9.0.6)
62
+ carrot-top (0.0.7)
63
+ json
58
64
  coercible (1.0.0)
59
65
  descendants_tracker (~> 0.0.1)
60
66
  concurrent-ruby (1.1.5)
61
- crass (1.0.4)
67
+ crass (1.0.6)
62
68
  descendants_tracker (0.0.4)
63
69
  thread_safe (~> 0.3, >= 0.3.1)
64
70
  equalizer (0.0.11)
65
71
  erubi (1.8.0)
66
72
  globalid (0.4.2)
67
73
  activesupport (>= 4.2.0)
74
+ hutch (1.0.0)
75
+ activesupport (>= 4.2, < 7)
76
+ bunny (>= 2.15, < 2.16)
77
+ carrot-top (~> 0.0.7)
78
+ multi_json (~> 1.14)
68
79
  i18n (1.6.0)
69
80
  concurrent-ruby (~> 1.0)
70
81
  ice_nine (0.11.2)
71
- loofah (2.2.3)
82
+ json (2.3.1)
83
+ loofah (2.7.0)
72
84
  crass (~> 1.0.2)
73
85
  nokogiri (>= 1.5.9)
74
86
  mail (2.7.1)
@@ -80,10 +92,11 @@ GEM
80
92
  mini_mime (1.0.1)
81
93
  mini_portile2 (2.4.0)
82
94
  minitest (5.11.3)
95
+ multi_json (1.15.0)
83
96
  nio4r (2.3.1)
84
- nokogiri (1.10.4)
97
+ nokogiri (1.10.10)
85
98
  mini_portile2 (~> 2.4.0)
86
- rack (2.0.7)
99
+ rack (2.2.3)
87
100
  rack-test (1.1.0)
88
101
  rack (>= 1.0, < 3)
89
102
  rails (5.2.3)
@@ -110,7 +123,7 @@ GEM
110
123
  method_source
111
124
  rake (>= 0.8.7)
112
125
  thor (>= 0.19.0, < 2.0)
113
- rake (10.5.0)
126
+ rake (13.0.1)
114
127
  sprockets (3.7.2)
115
128
  concurrent-ruby (~> 1.0)
116
129
  rack (> 1, < 3)
@@ -129,7 +142,7 @@ GEM
129
142
  equalizer (~> 0.0, >= 0.0.9)
130
143
  websocket-driver (0.7.0)
131
144
  websocket-extensions (>= 0.1.0)
132
- websocket-extensions (0.1.3)
145
+ websocket-extensions (0.1.5)
133
146
 
134
147
  PLATFORMS
135
148
  ruby
@@ -140,8 +153,8 @@ DEPENDENCIES
140
153
  command_service_object!
141
154
  minitest (~> 5.11, >= 5.11.3)
142
155
  rails (~> 5.0)
143
- rake (~> 10.0)
156
+ rake (~> 13.0)
144
157
  thor (~> 0.20.3)
145
158
 
146
159
  BUNDLED WITH
147
- 2.0.2
160
+ 2.1.4
data/README.md CHANGED
@@ -8,19 +8,24 @@ Rails Generator for command service object.
8
8
 
9
9
  ### Implementation
10
10
 
11
- Service consists of several objects { `Command Object` `Usecase Object` And `Error Object` (business logic error) }.
11
+ Service consists of several objects { `Command Object` `Usecase Object` And `Error Object` (business logic error) }.
12
12
 
13
- - **Command Object:** the object that responsible for containing `Client` requests and run input validations it's implemented using [Virtus](https://github.com/solnic/virtus) gem and can use `activerecord` for validations and it's existed under `commands` dir.
14
- - **Usecase Object:** this object responsible for executing the business logic, Every `usecase` should execute one command type only so that command name should be the same as usecase object name, usecase object existed under 'usecases` dir.
15
- - **Micros:** small reusable logic under the same service.
13
+ - **[Command](https://en.wikipedia.org/wiki/Command_pattern):** the object that responsible for containing `Client` requests and run input validations it's implemented using [Virtus](https://github.com/solnic/virtus) gem and can use `activerecord` for validations and it's existed under `commands` dir.
14
+ - **Usecase:** this object responsible for executing the business logic, Every `usecase` should execute one command type only so that command name should be the same as usecase object name, usecase object existed under 'usecases` dir.
15
+ - **Micros:** Small reusable logic under the same service.
16
+ - **Externals:** Simple ruby module works as a service interface whenever you wanna call any external service or even service that lives under the same project you should use it.
17
+ - **Queries:** This dir is the only entry point for you to get any data form a service.
18
+ - **Listeners:** An event listener that waits for an event outside the service to occur.
19
+ - **Entities:** Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity.
16
20
 
17
- #### Result Object
21
+ ### Result Object
18
22
 
19
23
  In case of successful or failure `ApplicationService` the responsible object for all services will return `service_result` object this object contain `value!` method containing successful call result, and `errors` method containing failure `errors` objects.
20
24
 
21
- #### Business Logic Failures
25
+ ### Helpers:
22
26
 
23
- To raise bussiness logic failures you can use `fail!` helper method with `message: String, extra_data: Hash` arguments.
27
+ - Fail: You can use `fail!` helper to raise business logic failures, ex: `fail!('user should not have any active cards')`.
28
+ - Check: To do business logic validations you can use `check!` helper ex: `check!('user should not have any active cards') { user.active_cards.empty? }`, if the given block returns false then it will raise fail! with the given message.
24
29
 
25
30
  > You can check if the result successful or not by using `ok?` method.
26
31
 
@@ -56,8 +61,8 @@ output
56
61
  ```bash
57
62
  app/services/
58
63
  ├── application_service.rb
59
- ├── external/
60
64
  ├── auth_service
65
+ │ ├ external/
61
66
  │   ├── commands
62
67
  │   │   └── login.rb
63
68
  │   └── usecases
@@ -99,11 +104,9 @@ then you can edit command params
99
104
  # frozen_string_literal: true
100
105
 
101
106
  module AuthService::Commands
102
- class Login
107
+ class Login < CommandBase
103
108
  # You can read Virtus gem doc for more info.
104
109
  # https://github.com/solnic/virtus
105
- include Virtus.model
106
- include ActiveModel::Validations
107
110
 
108
111
  # Attributes
109
112
  # attribute :REPLACE_ME, String
@@ -129,13 +132,29 @@ module AuthService::Usecases
129
132
  # methods for Business logic.
130
133
  #
131
134
  def call
132
- token = generate_jwt_token_for(user)
135
+ token = generate_jwt_token_for(cmd.user)
136
+ replace_me
137
+
138
+ output
139
+ end
140
+
141
+ def output
142
+ # return entity object
133
143
  end
134
144
 
135
145
  # This method will run if call method raise error
136
146
  def rollback
137
147
  # rollback logic
138
148
  end
149
+
150
+ def allowed?
151
+ # policies loginc for issuer
152
+ # ex:
153
+ #
154
+ # return false if issuer.role != :admin
155
+
156
+ true
157
+ end
139
158
 
140
159
  private
141
160
 
@@ -154,16 +173,16 @@ You can wrap external apis or services under `external/` dir
154
173
 
155
174
  ```ruby
156
175
  module External
157
- class StripeService
158
- class << self
159
- def charge(customer:, amount:, currency:, description: nil)
160
- Stripe::Charge.create(
161
- customer: customer.id,
162
- amount: (round_up(amount, currency) * 100).to_i,
163
- description: description || customer.email,
164
- currency: currency
165
- )
166
- end
176
+ module StripeService
177
+ extend self
178
+
179
+ def charge(customer:, amount:, currency:, description: nil)
180
+ Stripe::Charge.create(
181
+ customer: customer.id,
182
+ amount: (round_up(amount, currency) * 100).to_i,
183
+ description: description || customer.email,
184
+ currency: currency
185
+ )
167
186
  end
168
187
  end
169
188
  end
@@ -36,8 +36,9 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency 'byebug', '~> 9.0.6'
37
37
  spec.add_development_dependency 'minitest', '~> 5.11', '>= 5.11.3'
38
38
  spec.add_development_dependency 'rails', '~> 5.0'
39
- spec.add_development_dependency 'rake', '~> 10.0'
39
+ spec.add_development_dependency 'rake', '~> 13.0'
40
40
  spec.add_development_dependency 'thor', '~> 0.20.3'
41
41
 
42
42
  spec.add_dependency 'virtus', '~> 1.0', '>= 1.0.5'
43
+ spec.add_dependency 'hutch', '1.0'
43
44
  end
@@ -4,6 +4,7 @@ require 'command_service_object/failure'
4
4
  require 'command_service_object/helpers/model_helper'
5
5
  require 'command_service_object/helpers/controller_helper'
6
6
  require 'command_service_object/helpers/failure_helper'
7
+ require 'command_service_object/helpers/check_helper'
7
8
  require 'command_service_object/hooks'
8
9
  require 'virtus'
9
10
 
@@ -0,0 +1,9 @@
1
+ module CommandServiceObject
2
+ module CheckHelper
3
+ def check!(message, &block)
4
+ raise "No block given" unless block_given?
5
+
6
+ fail!(message) unless block.call
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module CommandServiceObject
2
- VERSION = '0.6.2'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
Binary file
@@ -12,15 +12,26 @@ module Service
12
12
  def call
13
13
  invoke Service::Generators::SetupGenerator, [name]
14
14
  @model_attributes = model_attributes
15
+
15
16
  commands.each do |c|
16
17
  @command = c.classify
17
- path = "app/services/#{service_name}/commands/#{c.underscore}.rb"
18
- template 'command.rb.erb', path unless options.skip_command?
18
+ create_main(c)
19
+ create_test(c)
19
20
  end
20
21
  end
21
22
 
22
23
  private
23
24
 
25
+ def create_main(m)
26
+ path = "app/services/#{service_name}/commands/#{m.underscore}.rb"
27
+ template 'command.rb.erb', path unless options.skip_command?
28
+ end
29
+
30
+ def create_test(m)
31
+ path = "spec/services/#{service_name}/commands/#{m.underscore}_spec.rb"
32
+ template 'command_spec.rb.erb', path
33
+ end
34
+
24
35
  def service_name
25
36
  "#{name.underscore}_service"
26
37
  end
@@ -0,0 +1,30 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+
3
+ module Service
4
+ module Generators
5
+ class EntityGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ argument :entities, type: :array, default: [], banner: 'entities entities'
9
+
10
+ def setup
11
+ invoke Service::Generators::SetupGenerator, [name]
12
+ end
13
+
14
+ def create_micros
15
+ entities.each do |m|
16
+ @entity = m.classify
17
+
18
+ path = "app/services/#{service_name}/entities/#{m.underscore}.rb"
19
+ template 'entity.rb.erb', path
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def service_name
26
+ "#{name.underscore}_service"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= service_name.classify %>::Entities
4
+ class <%= @entity %>
5
+ include Virtus.model(nullify_blank: true)
6
+
7
+ # attribute :name, String
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+
3
+ module Service
4
+ module Generators
5
+ class ExternalGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ argument :externals, type: :array, default: [], banner: 'external external'
9
+
10
+ def create_externals
11
+ invoke Service::Generators::SetupGenerator, [name]
12
+
13
+ externals.each do |m|
14
+ @external = m.classify
15
+ create_main(m)
16
+ create_test(m)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def create_main(m)
23
+ path = "app/services/#{service_name}/externals/#{m.underscore}.rb"
24
+ template 'external.rb.erb', path
25
+ end
26
+
27
+ def create_test(m)
28
+ path = "spec/services/#{service_name}/externals/#{m.underscore}_spec.rb"
29
+ template 'external_spec.rb.erb', path
30
+ end
31
+
32
+ def service_name
33
+ "#{name.underscore}_service"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= service_name.classify %>::Externals
4
+ module <%= @external %>
5
+ extend self
6
+
7
+ # Example
8
+ # def balance(id)
9
+ # res = http.get("user/:id")
10
+ # res_as_json = JSON.parse(res.body)
11
+ #
12
+ # The return value should only be OpenStruct or Hash Object
13
+ # OpenStruct.new(user_id: res_as_json[:id], balance: res_as_json[:balance])
14
+ # end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe <%= service_name.classify %>::Externals::<%= @external %> do
6
+ # Example
7
+ # context "when success" do
8
+ # it 'should be okay?' do
9
+ # expect(subject.[method_name]).to eql([result])
10
+ # end
11
+ # end
12
+ end
@@ -1,3 +1,8 @@
1
+ require 'hutch'
2
+
3
+ Hutch::Logging.logger = Rails.logger
4
+ Hutch.connect
5
+
1
6
  CommandServiceObject.configure do |config|
2
7
  # By setting the append_controller_helper to false you will need to
3
8
  # manually include the CommandServiceObject::ServiceControllerHelper
@@ -2,38 +2,79 @@
2
2
 
3
3
  class ApplicationService
4
4
  class << self
5
+ attr_reader :cmd, :usecase, :bm, :result
6
+
5
7
  def call(cmd)
6
- raise Errors::InvalidCommand, cmd.class if cmd.invalid?
8
+ @cmd = cmd
9
+ @bm = Benchmark.measure do
10
+ raise Errors::InvalidCommand, cmd.class if cmd.invalid?
11
+
12
+ @usecase = usecase_class.new(cmd)
13
+ raise Errors::NotAuthorizedError, cmd.class unless usecase.allowed?
7
14
 
8
- usecase = usecase_for(cmd).new(cmd)
9
- raise Errors::NotAuthorizedError, cmd.class unless usecase.allowed?
15
+ @result = ServiceResult.new { usecase.call }
10
16
 
11
- result = ServiceResult.new { usecase.call }
17
+ rollback if result.error.present?
18
+ end
12
19
 
13
- rollback(usecase, result, cmd) if result.error.present?
20
+ log_command
14
21
  result
15
22
  rescue StandardError => e
16
- log_errors(e, cmd)
17
- ServiceResult.new { raise e }
23
+ ServiceResult.new { raise e }
18
24
  end
19
25
 
20
- def rollback(usecase, result, cmd)
26
+ def rollback
21
27
  usecase.rollback_micros
22
28
  usecase.rollback
23
- log_errors(result.error, cmd)
29
+ log_errors(result.error)
24
30
  end
25
31
 
26
- def log_errors(err, _cmd)
27
- return if err.class.is_a?(CommandServiceObject::Failure)
28
- # Add your logging logic
29
- # ex:
30
- # Rollbar.error(err)
32
+ private
33
+
34
+ def log_command
35
+ service_logger = ActiveSupport::Logger.new(Rails.root.join('log', 'services.log').to_s)
36
+ service_logger.formatter = proc do |severity, datetime, progname, msg|
37
+ "[#{msg['usecase']}] [#{msg['status']}] [#{datetime.to_s(:db)} ##{Process.pid}] -- #{msg['body']}\n"
38
+ end
39
+ log_body = result.ok? ? success_log : failure_log
40
+
41
+ service_logger.info(log_body)
31
42
  end
32
43
 
33
- private
44
+ def success_log
45
+ {
46
+ usecase: "#{service_name}::#{usecase_name}",
47
+ status: 'success',
48
+ body: {
49
+ cmd: cmd.as_json,
50
+ result: result.value!.as_json,
51
+ benchmark: bm.as_json
52
+ }
53
+ }.as_json
54
+ end
34
55
 
35
- def usecase_for(cmd)
56
+ def failure_log
57
+ {
58
+ usecase: "#{service_name}::#{usecase_name}",
59
+ status: 'faild',
60
+ body: {
61
+ cmd: cmd.as_json,
62
+ error: result.error.to_s,
63
+ benchmark: bm.as_json
64
+ }
65
+ }.as_json
66
+ end
67
+
68
+ def usecase_class
36
69
  cmd.class.name.gsub('Commands', 'Usecases').constantize
37
70
  end
71
+
72
+ def service_name
73
+ cmd.class.name.split('::').first
74
+ end
75
+
76
+ def usecase_name
77
+ cmd.class.name.split('::').last
78
+ end
38
79
  end
39
80
  end
@@ -1,13 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'hutch'
4
+
3
5
  class CaseBase
6
+ include CommandServiceObject::Hooks
4
7
  include CommandServiceObject::FailureHelper
8
+ include CommandServiceObject::CheckHelper
5
9
 
6
- attr_reader :cmd
10
+ attr_reader :cmd, :issuer, :right_name
7
11
  alias_attribute :payload, :cmd
8
12
 
9
13
  def initialize(cmd)
10
14
  @cmd = cmd
15
+ @issuer = cmd.try(:issuer)
16
+ @right_name = "#{service_name}.#{case_name}"
17
+ end
18
+
19
+ def case_name
20
+ self.class.name.split('::').last.downcase
21
+ end
22
+
23
+ def service_name
24
+ self.class.name.split('::').first.remove('Service').downcase
11
25
  end
12
26
 
13
27
  def allowed?
@@ -1,10 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class CommandBase
4
- include Virtus.model
5
4
  include ActiveModel::Validations
5
+ include Virtus.model(nullify_blank: true)
6
6
 
7
7
  # if you need to enable policies add issuer attribute
8
- # attribute :issuer, User
9
- # validates :issuer, presence: true
8
+ attribute :issuer, Object, default: :default_issuer
9
+
10
+ private
11
+
12
+ def default_issuer
13
+ nil
14
+ end
10
15
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hutch'
4
+
5
+ class ListenerBase < SimpleDelegator
6
+ include Hutch::Consumer
7
+ end
@@ -0,0 +1,55 @@
1
+ require "active_record"
2
+
3
+ class QueryBase
4
+ RelationRequired = Class.new(StandardError)
5
+
6
+ def initialize(*args)
7
+ @options = args.extract_options!
8
+ @relation = args.first || base_relation
9
+
10
+ if relation.nil?
11
+ raise(
12
+ RelationRequired,
13
+ "Queries require a base relation defined. Use .queries method to define relation."
14
+ )
15
+ elsif !relation.is_a?(ActiveRecord::Relation)
16
+ raise(
17
+ RelationRequired,
18
+ "Queries accept only ActiveRecord::Relation as input"
19
+ )
20
+ end
21
+ end
22
+
23
+ def self.call(*args)
24
+ new(*args).query
25
+ end
26
+
27
+ def self.queries(subject)
28
+ self.base_relation = subject
29
+ end
30
+
31
+ def base_relation
32
+ return nil if self.class.base_relation.nil?
33
+
34
+ if self.class.base_relation.is_a?(ActiveRecord::Relation)
35
+ self.class.base_relation
36
+ elsif self.class.base_relation < ActiveRecord::Base
37
+ self.class.base_relation.all
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ class << self
44
+ attr_accessor :base_relation
45
+ end
46
+
47
+ attr_reader :relation, :options
48
+
49
+ def query
50
+ raise(
51
+ NotImplementedError,
52
+ "You need to implement #query method which returns ActiveRecord::Relation object"
53
+ )
54
+ end
55
+ end
@@ -0,0 +1,30 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+
3
+ module Service
4
+ module Generators
5
+ class ListenerGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ argument :listeners, type: :array, default: [], banner: 'Listener Listener'
9
+
10
+ def setup
11
+ invoke Service::Generators::SetupGenerator, [name]
12
+ end
13
+
14
+ def create_micros
15
+ listeners.each do |m|
16
+ @listener = m.classify
17
+
18
+ path = "app/services/#{service_name}/listeners/#{m.underscore}.rb"
19
+ template 'listener.rb.erb', path
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def service_name
26
+ "#{name.underscore}_service"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= service_name.classify %>::Listeners
4
+ class <%= @listener %> < ListenerBase
5
+ consume 'channel name'
6
+
7
+ def process(message)
8
+ # Logic
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ This will generatet query_object under [service_name]/queries
3
+
4
+ Example:
5
+ rails generate service:query [service name] [query name]
6
+
7
+ This will create:
8
+ services/[service_name]/queries/[query_name].rb
@@ -0,0 +1,30 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+
3
+ module Service
4
+ module Generators
5
+ class QueryGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ argument :queries, type: :array, default: [], banner: 'query query'
9
+
10
+ def setup
11
+ invoke Service::Generators::SetupGenerator, [name]
12
+ end
13
+
14
+ def create_queries
15
+ queries.each do |m|
16
+ @query = m.classify
17
+
18
+ path = "app/services/#{service_name}/queries/#{m.underscore}.rb"
19
+ template 'query.rb.erb', path
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def service_name
26
+ "#{name.underscore}_service"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= service_name.classify %>::Queries
4
+ class <%= @query %> < QueryBase
5
+ # queries UserService::Models::User
6
+
7
+ # Example
8
+ # def query
9
+ # map_rows(relation.where(phone: options.fetch(:phone)))
10
+ # end
11
+
12
+ private
13
+
14
+ # def map_rows(rows)
15
+ # rows.map { |r| UserService::Views::User.new(r.as_json).to_hash }
16
+ # end
17
+
18
+ # def map_row(r)
19
+ # UserService::Views::User.new(r.as_json).to_hash
20
+ # end
21
+ end
22
+ end
@@ -4,7 +4,6 @@ require 'rubygems'
4
4
  require_relative './setup/setup_generator.rb'
5
5
  require_relative './usecase/usecase_generator.rb'
6
6
  require_relative './command/command_generator.rb'
7
- require_relative './test/test_generator.rb'
8
7
  require 'rails/generators'
9
8
  require 'rails/generators/model_helpers'
10
9
 
@@ -16,6 +15,7 @@ module Service
16
15
  # Skiping options
17
16
  class_option :skip_usecase, type: :boolean, default: false, aliases: '-U'
18
17
  class_option :skip_command, type: :boolean, default: false, aliases: '-C'
18
+ class_option :skip_entity, type: :boolean, default: false, aliases: '-E'
19
19
  class_option :skip_test, type: :boolean, default: false, aliases: '-T'
20
20
 
21
21
  def install_if_not
@@ -39,12 +39,6 @@ module Service
39
39
 
40
40
  invoke Service::Generators::CommandGenerator, [name, usecases]
41
41
  end
42
-
43
- def generate_tests
44
- return if options.skip_test?
45
-
46
- invoke Service::Generators::TestGenerator, [name, usecases]
47
- end
48
42
  end
49
43
  end
50
44
  end
@@ -1,13 +1,23 @@
1
1
  module Service
2
2
  module Generators
3
3
  class SetupGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('templates', __dir__)
5
+
4
6
  def setup
5
7
  return if File.exist?("app/services/#{service_name}")
6
8
 
7
9
  empty_directory("app/services/#{service_name}")
10
+ empty_directory("app/services/#{service_name}/listeners")
11
+ empty_directory("app/services/#{service_name}/jobs")
12
+ empty_directory("app/services/#{service_name}/externals")
13
+ empty_directory("app/services/#{service_name}/queries")
8
14
  empty_directory("app/services/#{service_name}/usecases")
9
15
  empty_directory("app/services/#{service_name}/commands")
16
+ empty_directory("app/services/#{service_name}/entities")
10
17
  empty_directory("app/services/#{service_name}/usecases/micros")
18
+
19
+ path = "app/services/#{service_name}/doc.md"
20
+ template 'doc.md.erb', path unless File.exist?(path)
11
21
  end
12
22
 
13
23
  private
@@ -0,0 +1,39 @@
1
+ ## <%= service_name.classify %>
2
+ The authorization system is responsible for identifying a particular user.
3
+
4
+ ### Database Tables
5
+ - users
6
+ - profiles
7
+
8
+ ### Entities:
9
+ ###### *Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity.*
10
+ - User
11
+ - name: string
12
+ - email: string
13
+ - phone: string
14
+ - Profile:
15
+ - address: string
16
+ - foo: boolean
17
+ - bar: int
18
+
19
+ ### Usecases (Processes):
20
+ ###### *Use Cases focus on Users, Actions, and Processes.*
21
+ - **[Login](login-uml-and-full-doc)** - the process for authorization, we write out to the user an authentication key for a month. Only a user with a verified phone and email address can authenticate.
22
+ - **[Logout](logout-uml-and-full-doc)** - After logging out, the user will need to log in again to access the system.
23
+
24
+ ### Queries:
25
+ - FindByEmail(email: string)
26
+ - FindByName(name: string)
27
+
28
+ ### Listeners:
29
+ ###### *An event listener that waits for an event outside the service to occur.*
30
+ - order_status
31
+
32
+ ### Externals:
33
+ ###### *Wrapper for any external interactions.*
34
+ - **UserService**
35
+ - user_info(id: id)
36
+ - **Stripe** - payment gateway wrapper.
37
+ - charge(pid, amount, currency)
38
+ - refund(charge_id)
39
+ - create_customer(email, phone, name)
@@ -2,13 +2,22 @@
2
2
 
3
3
  module <%= service_name.classify %>::Usecases
4
4
  class <%= @usecase %> < CaseBase
5
- include CommandServiceObject::Hooks
6
5
  #
7
6
  # Your business logic goes here, keep [call] method clean by using private
8
7
  # methods for Business logic.
9
8
  #
10
9
  def call
10
+ # Use check! for business logic validation,
11
+ # If the given block returns false then it will raise a business logic failure with the given message.
12
+ # Ex: check!("user should not have any active cards") { cmd.issuer.active_cards.empty? }
13
+
11
14
  replace_me
15
+
16
+ output
17
+ end
18
+
19
+ def output
20
+ # return entity object
12
21
  end
13
22
 
14
23
  # This method will run if call method raise error
@@ -25,10 +34,14 @@ module <%= service_name.classify %>::Usecases
25
34
  true
26
35
  end
27
36
 
37
+ def broadcast
38
+ Hutch.publish('<%= service_name %>', action: '<%= "#{@usecase.underscore}" %>', subject: {})
39
+ end
40
+
28
41
  private
29
42
 
30
- def replace_me
31
- # [business logic]
32
- end
43
+ def replace_me
44
+ # [business logic]
45
+ end
33
46
  end
34
47
  end
@@ -12,13 +12,23 @@ module Service
12
12
 
13
13
  usecases.each do |u|
14
14
  @usecase = u.classify
15
- path = "app/services/#{service_name}/usecases/#{u.underscore}.rb"
16
- template 'usecase.rb.erb', path
15
+ create_main(u)
16
+ create_test(u)
17
17
  end
18
18
  end
19
19
 
20
20
  private
21
21
 
22
+ def create_main(m)
23
+ path = "app/services/#{service_name}/usecases/#{m.underscore}.rb"
24
+ template 'usecase.rb.erb', path unless options.skip_command?
25
+ end
26
+
27
+ def create_test(m)
28
+ path = "spec/services/#{service_name}/usecases/#{m.underscore}_spec.rb"
29
+ template 'usecase_spec.rb.erb', path
30
+ end
31
+
22
32
  def service_name
23
33
  "#{name.underscore}_service"
24
34
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: command_service_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adham EL-Deeb
8
8
  - Mohamed Diaa
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-10-18 00:00:00.000000000 Z
12
+ date: 2020-10-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -79,14 +79,14 @@ dependencies:
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '10.0'
82
+ version: '13.0'
83
83
  type: :development
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '10.0'
89
+ version: '13.0'
90
90
  - !ruby/object:Gem::Dependency
91
91
  name: thor
92
92
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +121,20 @@ dependencies:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
123
  version: 1.0.5
124
+ - !ruby/object:Gem::Dependency
125
+ name: hutch
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '='
129
+ - !ruby/object:Gem::Version
130
+ version: '1.0'
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - '='
136
+ - !ruby/object:Gem::Version
137
+ version: '1.0'
124
138
  description: command_service_object gem helps you to generate service and command
125
139
  objects using rails generator.
126
140
  email:
@@ -148,16 +162,25 @@ files:
148
162
  - lib/command_service_object.rb
149
163
  - lib/command_service_object/configuration.rb
150
164
  - lib/command_service_object/failure.rb
165
+ - lib/command_service_object/helpers/check_helper.rb
151
166
  - lib/command_service_object/helpers/controller_helper.rb
152
167
  - lib/command_service_object/helpers/failure_helper.rb
153
168
  - lib/command_service_object/helpers/model_helper.rb
154
169
  - lib/command_service_object/hooks.rb
155
170
  - lib/command_service_object/railtie.rb
156
171
  - lib/command_service_object/version.rb
172
+ - lib/generators/.DS_Store
157
173
  - lib/generators/service/USAGE
158
174
  - lib/generators/service/command/USAGE
159
175
  - lib/generators/service/command/command_generator.rb
160
176
  - lib/generators/service/command/templates/command.rb.erb
177
+ - lib/generators/service/command/templates/command_spec.rb.erb
178
+ - lib/generators/service/entity/entity_generator.rb
179
+ - lib/generators/service/entity/templates/entity.rb.erb
180
+ - lib/generators/service/external/external_generator.rb
181
+ - lib/generators/service/external/templates/external.rb.erb
182
+ - lib/generators/service/external/templates/external_spec.rb.erb
183
+ - lib/generators/service/external/templates/external_test.rb.erb
161
184
  - lib/generators/service/getter/getter_generator.rb
162
185
  - lib/generators/service/getter/templates/getter.rb.erb
163
186
  - lib/generators/service/install/install_generator.rb
@@ -165,19 +188,22 @@ files:
165
188
  - lib/generators/service/install/templates/services/application_service.rb
166
189
  - lib/generators/service/install/templates/services/case_base.rb
167
190
  - lib/generators/service/install/templates/services/command_base.rb
191
+ - lib/generators/service/install/templates/services/listener_base.rb
192
+ - lib/generators/service/install/templates/services/query_base.rb
168
193
  - lib/generators/service/install/templates/services/service_result.rb
194
+ - lib/generators/service/listener/listener_generator.rb
195
+ - lib/generators/service/listener/templates/listener.rb.erb
169
196
  - lib/generators/service/micro/micro_generator.rb
170
197
  - lib/generators/service/micro/templates/micro.rb.erb
171
- - lib/generators/service/policies.rb
198
+ - lib/generators/service/query/USAGE
199
+ - lib/generators/service/query/query_generator.rb
200
+ - lib/generators/service/query/templates/query.rb.erb
172
201
  - lib/generators/service/service_generator.rb
173
202
  - lib/generators/service/setup/setup_generator.rb
174
- - lib/generators/service/test/USAGE
175
- - lib/generators/service/test/templates/minitest/usecase.rb.erb
176
- - lib/generators/service/test/templates/rspec/command.rb.erb
177
- - lib/generators/service/test/templates/rspec/usecase.rb.erb
178
- - lib/generators/service/test/test_generator.rb
203
+ - lib/generators/service/setup/templates/doc.md.erb
179
204
  - lib/generators/service/usecase/USAGE
180
205
  - lib/generators/service/usecase/templates/usecase.rb.erb
206
+ - lib/generators/service/usecase/templates/usecase_spec.rb.erb
181
207
  - lib/generators/service/usecase/usecase_generator.rb
182
208
  homepage: https://github.com/adham90/command_service_object
183
209
  licenses:
@@ -202,8 +228,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
202
228
  - !ruby/object:Gem::Version
203
229
  version: '0'
204
230
  requirements: []
205
- rubygems_version: 3.0.3
206
- signing_key:
231
+ rubygems_version: 3.1.2
232
+ signing_key:
207
233
  specification_version: 4
208
234
  summary: Rails Generator for command service object.
209
235
  test_files: []
@@ -1,5 +0,0 @@
1
- class
2
- def initialize(*args)
3
-
4
- end
5
- end
@@ -1,8 +0,0 @@
1
- Description:
2
- Explain the generator
3
-
4
- Example:
5
- rails generate service Thing
6
-
7
- This will create:
8
- what/will/it/create
@@ -1 +0,0 @@
1
- # frozen_string_literal: true
@@ -1,72 +0,0 @@
1
- require_relative '../setup/setup_generator.rb'
2
-
3
- module Service
4
- module Generators
5
- class TestGenerator < Rails::Generators::NamedBase
6
- source_root File.expand_path('templates', __dir__)
7
- argument :tests, type: :array, default: [], banner: 'usecase usecase'
8
-
9
- def generate_test
10
- return if options.skip_test?
11
-
12
- if defined?(Minitest)
13
- minitest_test
14
- elsif defined?(RSpec)
15
- rspec_test
16
- end
17
- end
18
-
19
- private
20
-
21
- def minitest_test
22
- dir = create_test_dir('test')
23
-
24
- tests.each do |t|
25
- @test = t.classify
26
- path = "#{dir}/usecases/#{t.underscore}_test.rb"
27
- template 'minitest/usecase.rb.erb', path
28
- end
29
- end
30
-
31
- def rspec_test
32
- dir = create_test_dir('spec')
33
- create_usecase_test(dir)
34
- create_command_test(dir)
35
- end
36
-
37
- def create_usecase_test(dir)
38
- tests.each do |t|
39
- @test = t.classify
40
- path = "#{dir}/usecases/#{t.underscore}_spec.rb"
41
- template 'rspec/usecase.rb.erb', path
42
- end
43
- end
44
-
45
- def create_command_test(dir)
46
- tests.each do |t|
47
- @test = t.classify
48
- path = "#{dir}/commands/#{t.underscore}_spec.rb"
49
- template 'rspec/command.rb.erb', path
50
- end
51
- end
52
-
53
- def create_test_dir(path)
54
- test_path = "#{path}/services"
55
- service_test = "#{test_path}/#{service_name}"
56
- usecases_path = "#{test_path}/#{service_name}/usecases"
57
- commands_path = "#{test_path}/#{service_name}/commands"
58
-
59
- empty_directory(test_path) unless File.exist?(test_path)
60
- empty_directory(service_test) unless File.exist?(service_test)
61
- empty_directory(usecases_path) unless File.exist?(usecases_path)
62
- empty_directory(commands_path) unless File.exist?(commands_path)
63
-
64
- service_test
65
- end
66
-
67
- def service_name
68
- "#{name.underscore}_service"
69
- end
70
- end
71
- end
72
- end