command_service_object 0.6.5 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) 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 +3 -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/install/templates/services/value_object_base.rb +4 -0
  24. data/lib/generators/service/listener/listener_generator.rb +39 -0
  25. data/lib/generators/service/listener/templates/listener.rb.erb +11 -0
  26. data/lib/generators/service/micro/micro_generator.rb +11 -2
  27. data/lib/generators/service/query/USAGE +8 -0
  28. data/lib/generators/service/query/query_generator.rb +39 -0
  29. data/lib/generators/service/query/templates/query.rb.erb +22 -0
  30. data/lib/generators/service/service_generator.rb +12 -13
  31. data/lib/generators/service/setup/setup_generator.rb +20 -6
  32. data/lib/generators/service/setup/templates/doc.md.erb +39 -0
  33. data/lib/generators/service/usecase/templates/usecase.rb.erb +17 -3
  34. data/lib/generators/service/{test/templates/rspec/usecase.rb.erb → usecase/templates/usecase_spec.rb.erb} +0 -0
  35. data/lib/generators/service/usecase/usecase_generator.rb +22 -3
  36. data/lib/generators/service/value_object/templates/value_object.rb.erb +17 -0
  37. data/lib/generators/service/value_object/value_object_generator.rb +40 -0
  38. metadata +43 -15
  39. data/lib/generators/service/getter/getter_generator.rb +0 -30
  40. data/lib/generators/service/getter/templates/getter.rb.erb +0 -8
  41. data/lib/generators/service/policies.rb +0 -5
  42. data/lib/generators/service/test/USAGE +0 -8
  43. data/lib/generators/service/test/templates/minitest/usecase.rb.erb +0 -1
  44. 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,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'hutch'
4
+
3
5
  class CaseBase
4
6
  include CommandServiceObject::Hooks
5
7
  include CommandServiceObject::FailureHelper
8
+ include CommandServiceObject::CheckHelper
6
9
 
7
10
  attr_reader :cmd, :issuer, :right_name
8
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,4 @@
1
+ class ValueObjectBase
2
+ include ActiveModel::Validations
3
+ include Virtus.value_object
4
+ 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