command_service_object 0.6.2 → 1.1.0

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