command_service_object 0.6.4 → 1.2.1

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +74 -61
  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 +23 -3
  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 +39 -0
  12. data/lib/generators/service/entity/templates/entity.rb.erb +9 -0
  13. data/lib/generators/service/external/external_generator.rb +46 -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/helper.rb +23 -0
  18. data/lib/generators/service/install/templates/initializer.rb +5 -0
  19. data/lib/generators/service/install/templates/services/application_service.rb +57 -16
  20. data/lib/generators/service/install/templates/services/case_base.rb +4 -0
  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 +39 -0
  24. data/lib/generators/service/listener/templates/listener.rb.erb +11 -0
  25. data/lib/generators/service/micro/micro_generator.rb +11 -2
  26. data/lib/generators/service/query/USAGE +8 -0
  27. data/lib/generators/service/query/query_generator.rb +39 -0
  28. data/lib/generators/service/query/templates/query.rb.erb +22 -0
  29. data/lib/generators/service/service_generator.rb +13 -4
  30. data/lib/generators/service/setup/setup_generator.rb +20 -6
  31. data/lib/generators/service/setup/templates/doc.md.erb +39 -0
  32. data/lib/generators/service/usecase/templates/usecase.rb.erb +17 -3
  33. data/lib/generators/service/{test/templates/rspec/usecase.rb.erb → usecase/templates/usecase_spec.rb.erb} +0 -0
  34. data/lib/generators/service/usecase/usecase_generator.rb +22 -3
  35. metadata +40 -15
  36. data/lib/generators/service/getter/getter_generator.rb +0 -30
  37. data/lib/generators/service/getter/templates/getter.rb.erb +0 -8
  38. data/lib/generators/service/policies.rb +0 -5
  39. data/lib/generators/service/test/USAGE +0 -8
  40. data/lib/generators/service/test/templates/minitest/usecase.rb.erb +0 -1
  41. data/lib/generators/service/test/test_generator.rb +0 -72
@@ -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,46 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+ require_relative '../helper'
3
+
4
+ module Service
5
+ module Generators
6
+ class ExternalGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ argument :externals, type: :array, default: [], banner: 'external external'
10
+
11
+ def create_externals
12
+ invoke Service::Generators::SetupGenerator, [name]
13
+
14
+ externals.each do |m|
15
+ @external = m.classify
16
+ create_main(m)
17
+ create_test(m)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def create_main(m)
24
+ path = "#{service_path}/externals/#{m.underscore}.rb"
25
+ template 'external.rb.erb', path
26
+ end
27
+
28
+ def create_test(m)
29
+ path = "#{spec_path}/externals/#{m.underscore}_spec.rb"
30
+ template 'external_spec.rb.erb', path
31
+ end
32
+
33
+ def service_name
34
+ Service::Helper.service_name(name)
35
+ end
36
+
37
+ def service_path
38
+ Service::Helper.service_path(name)
39
+ end
40
+
41
+ def spec_path
42
+ Service::Helper.spec_path(name)
43
+ end
44
+ end
45
+ end
46
+ 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
@@ -0,0 +1,23 @@
1
+ module Service
2
+ module Helper
3
+ extend self
4
+
5
+ def service_name(arg)
6
+ if arg.include? "/"
7
+ root = arg.split("/").first
8
+ sub_domain = arg.split("/").last
9
+ "#{root.underscore}_service/#{sub_domain}"
10
+ else
11
+ "#{arg.underscore}_service"
12
+ end
13
+ end
14
+
15
+ def service_path(arg)
16
+ "app/services/#{service_name(arg)}"
17
+ end
18
+
19
+ def spec_path(arg)
20
+ "spec/services/#{service_name(arg)}"
21
+ end
22
+ end
23
+ 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,7 +1,11 @@
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
10
  attr_reader :cmd, :issuer, :right_name
7
11
  alias_attribute :payload, :cmd
@@ -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,39 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+ require_relative '../helper'
3
+
4
+ module Service
5
+ module Generators
6
+ class ListenerGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ argument :listeners, type: :array, default: [], banner: 'Listener Listener'
10
+
11
+ def setup
12
+ invoke Service::Generators::SetupGenerator, [name]
13
+ end
14
+
15
+ def create_micros
16
+ listeners.each do |m|
17
+ @listener = m.classify
18
+
19
+ path = "#{service_path}/listeners/#{m.underscore}.rb"
20
+ template 'listener.rb.erb', path
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def service_name
27
+ Service::Helper.service_name(name)
28
+ end
29
+
30
+ def service_path
31
+ Service::Helper.service_path(name)
32
+ end
33
+
34
+ def spec_path
35
+ Service::Helper.spec_path(name)
36
+ end
37
+ end
38
+ end
39
+ 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
@@ -1,4 +1,5 @@
1
1
  require_relative '../setup/setup_generator.rb'
2
+ require_relative '../helper'
2
3
 
3
4
  module Service
4
5
  module Generators
@@ -15,7 +16,7 @@ module Service
15
16
  micros.each do |m|
16
17
  @micro = m.classify
17
18
 
18
- path = "app/services/#{service_name}/usecases/micros/#{m.underscore}.rb"
19
+ path = "#{service_path}/usecases/micros/#{m.underscore}.rb"
19
20
  template 'micro.rb.erb', path
20
21
  end
21
22
  end
@@ -23,7 +24,15 @@ module Service
23
24
  private
24
25
 
25
26
  def service_name
26
- "#{name.underscore}_service"
27
+ Service::Helper.service_name(name)
28
+ end
29
+
30
+ def service_path
31
+ Service::Helper.service_path(name)
32
+ end
33
+
34
+ def spec_path
35
+ Service::Helper.spec_path(name)
27
36
  end
28
37
  end
29
38
  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,39 @@
1
+ require_relative '../setup/setup_generator.rb'
2
+ require_relative '../helper'
3
+
4
+ module Service
5
+ module Generators
6
+ class QueryGenerator < Rails::Generators::NamedBase
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ argument :queries, type: :array, default: [], banner: 'query query'
10
+
11
+ def setup
12
+ invoke Service::Generators::SetupGenerator, [name]
13
+ end
14
+
15
+ def create_queries
16
+ queries.each do |m|
17
+ @query = m.classify
18
+
19
+ path = "#{service_path}/queries/#{m.underscore}.rb"
20
+ template 'query.rb.erb', path
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def service_name
27
+ Service::Helper.service_name(name)
28
+ end
29
+
30
+ def service_path
31
+ Service::Helper.service_path(name)
32
+ end
33
+
34
+ def spec_path
35
+ Service::Helper.spec_path(name)
36
+ end
37
+ end
38
+ end
39
+ 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