use_cases 0.3.8 → 1.0.12

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.
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UseCases
4
+ module ModuleOptins
5
+ module Transactional
6
+ class TransactionHandlerUndefined < StandardError; end
7
+
8
+ class TransactionHandlerInvalid < StandardError; end
9
+
10
+ def self.included(base)
11
+ super
12
+ base.prepend DoCallPatch
13
+ end
14
+
15
+ module DoCallPatch
16
+ def do_call(*args)
17
+ unless respond_to?(:transaction_handler)
18
+ raise TransactionHandlerUndefined, "when using *transactional*, make sure to include a transaction handler in your dependencies."
19
+ end
20
+
21
+ raise TransactionHandlerInvalid, "Make sure your transaction_handler implements #transaction." unless transaction_handler.respond_to?(:transaction)
22
+
23
+ transaction_handler.transaction { super }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/validation/contract"
4
+
5
+ module UseCases
6
+ module ModuleOptins
7
+ module Validated
8
+ class NoValidationError < StandardError; end
9
+
10
+ def self.included(base)
11
+ super
12
+ base.class_eval do
13
+ extend DSL
14
+ extend ClassMethods
15
+ prepend CallPatch
16
+ end
17
+ end
18
+
19
+ module CallPatch
20
+ def call(*args)
21
+ unless stack.include_step?(:validate)
22
+ raise NoValidationError,
23
+ "Make sure to define params validations by using *params*" \
24
+ "*schema*, *json*, *rule* or *option* macros in your use case."
25
+ end
26
+
27
+ super
28
+ end
29
+ end
30
+
31
+ module DSL
32
+ def params(*args, &blk)
33
+ _setup_validation
34
+
35
+ _contract_class.params(*args, &blk)
36
+ end
37
+
38
+ def schema(*args, &blk)
39
+ _setup_validation
40
+
41
+ _contract_class.schema(*args, &blk)
42
+ end
43
+
44
+ def rule(*args, &blk)
45
+ _setup_validation
46
+
47
+ _contract_class.rule(*args, &blk)
48
+ end
49
+
50
+ def json(*args, &blk)
51
+ _setup_validation
52
+
53
+ _contract_class.json(*args, &blk)
54
+ end
55
+
56
+ def option(*args, &blk)
57
+ _contract_class.option(*args, &blk)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def validate(params, *_args)
64
+ return Failure([:validation_error, "*params* must be a hash."]) unless params.respond_to?(:merge)
65
+
66
+ validation = contract.call(params)
67
+
68
+ if validation.success?
69
+ params.merge!(validation.to_h)
70
+ Success(validation.to_h)
71
+ else
72
+ Failure([:validation_error, validation.errors.to_h])
73
+ end
74
+ end
75
+
76
+ def contract
77
+ return self.class._contract_class.new if self.class._contract_class_defined?
78
+ end
79
+
80
+ module ClassMethods
81
+ def _setup_validation
82
+ _define_contract_class unless _contract_class_defined?
83
+ _define_validation_step unless _validation_step_defined?
84
+ end
85
+
86
+ def _define_validation_step
87
+ step :validate
88
+ end
89
+
90
+ def _contract_class
91
+ self::Contract
92
+ end
93
+
94
+ def _define_contract_class
95
+ const_set(:Contract, Class.new(Dry::Validation::Contract))
96
+ end
97
+
98
+ def _contract_class_name
99
+ "#{name}::Contract"
100
+ end
101
+
102
+ def _contract_class_defined?
103
+ Object.const_defined? _contract_class_name
104
+ end
105
+
106
+ def _validation_step_defined?
107
+ __steps__.map(&:name).include?(:validate)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,39 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "use_cases/authorize"
4
- require "use_cases/prepare"
5
- require "use_cases/transaction"
6
- require "use_cases/validate"
3
+ require "use_cases/module_optins/prepared"
4
+ require "use_cases/module_optins/transactional"
5
+ require "use_cases/module_optins/validated"
6
+ require "use_cases/module_optins/publishing"
7
+ require "use_cases/module_optins/authorized"
7
8
 
8
9
  module UseCases
9
10
  module ModuleOptins
10
11
  attr_accessor :options
11
12
 
13
+ OPTINS = {
14
+ authorized: Authorized,
15
+ transactional: Transactional,
16
+ validated: Validated,
17
+ prepared: Prepared,
18
+ publishing: Publishing
19
+ }
20
+
12
21
  def [](*options)
13
- @modules = []
14
- @modules << UseCases::Authorize if options.include?(:authorized)
15
- @modules << UseCases::Transaction if options.include?(:transactional)
16
- @modules << UseCases::Validate if options.include?(:validated)
17
- @modules << UseCases::Prepare if options.include?(:prepared)
18
- self
19
- end
22
+ modules = [self]
20
23
 
21
- def included(base)
22
- super
23
- @modules ||= []
24
- return if @modules.empty?
24
+ OPTINS.each do |key, module_constant|
25
+ modules << module_constant if options.include?(key)
26
+ end
25
27
 
26
- base.include(*@modules)
27
- @modules = nil
28
- end
28
+ supermodule = Module.new
29
29
 
30
- def inherited(base)
31
- super
32
- @modules ||= []
33
- return if @modules.empty?
30
+ supermodule.define_singleton_method(:included) do |base|
31
+ base.include(*modules)
32
+ end
34
33
 
35
- base.include(*@modules)
36
- @modules = nil
34
+ supermodule
37
35
  end
38
36
 
39
37
  def descendants
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UseCases
4
- class StepResult < Dry::Monads::Result
4
+ class Result < Dry::Monads::Result
5
5
  attr_reader :step, :result
6
6
 
7
7
  def initialize(step, result)
@@ -14,7 +14,7 @@ module UseCases
14
14
  return result if result_not_monad?
15
15
  return nil if result_empty?
16
16
 
17
- result.success? ? result.value! : result
17
+ result.success? ? result.value! : result.failure
18
18
  end
19
19
  alias value! value
20
20
 
@@ -23,25 +23,23 @@ module UseCases
23
23
  end
24
24
 
25
25
  def failure?
26
- value.is_a?(Dry::Monads::Result::Failure)
27
- end
28
-
29
- def failure
30
- failure? && value.failure
26
+ result.is_a?(Dry::Monads::Result::Failure)
31
27
  end
32
28
 
33
29
  def success
34
30
  success? && value
35
31
  end
36
32
 
33
+ def failure
34
+ failure? && value
35
+ end
36
+
37
37
  def nil?
38
38
  value.nil?
39
39
  end
40
40
 
41
41
  def to_result
42
- value.to_result if failure?
43
-
44
- result
42
+ self
45
43
  end
46
44
 
47
45
  def result_empty?
@@ -51,5 +49,13 @@ module UseCases
51
49
  def result_not_monad?
52
50
  !result.is_a?(Dry::Monads::Result)
53
51
  end
52
+
53
+ def inspect
54
+ if failure?
55
+ "UseCases::Result.Failure(#{value.inspect})"
56
+ else
57
+ "UseCases::Result.Success(#{value.inspect})"
58
+ end
59
+ end
54
60
  end
55
61
  end
@@ -38,13 +38,15 @@ RSpec::Matchers.define(:be_failure_with) do |*expected_failure|
38
38
  expect(test_subject.failure).to eql expected_failure
39
39
  end
40
40
 
41
+ expected_result, expected_code = expected_failure
42
+
41
43
  failure_message do |test_subject|
42
44
  if test_subject.failure?
43
- "the use case was expected to fail with #{expected_result.inspect} but it returned #{test_subject.failure.inspect}"
45
+ "the use case was expected to fail with #{expected_code} and #{expected_result.inspect} but it returned #{test_subject.failure.inspect}"
44
46
  else
45
47
  "the use case was expected to fail but it succeeded with #{test_subject.success.inspect}"
46
48
  end
47
- end
49
+ end
48
50
  end
49
51
 
50
52
  RSpec::Matchers.define(:be_successful_with) do |expected_result|
@@ -59,5 +61,5 @@ RSpec::Matchers.define(:be_successful_with) do |expected_result|
59
61
  else
60
62
  "the use case was expected to succeed but it failed with #{test_subject.failure.inspect}"
61
63
  end
62
- end
64
+ end
63
65
  end
@@ -8,9 +8,7 @@ module UseCases
8
8
  @stack = stack
9
9
  end
10
10
 
11
- def call(*args, &around_block)
12
- return around_block.call { do_call(*args) } if around_block
13
-
11
+ def call(*args)
14
12
  do_call(*args)
15
13
  end
16
14
 
@@ -19,7 +17,6 @@ module UseCases
19
17
  def do_call(*args)
20
18
  stack.call do
21
19
  result = _run_step(stack, args)
22
-
23
20
  return result if result.failure?
24
21
 
25
22
  result
@@ -28,29 +25,21 @@ module UseCases
28
25
 
29
26
  def _run_step(stack, args)
30
27
  step = stack.current_step
31
- expected_args_count = step.args_count
32
- step_args = _assert_step_arguments_with_count(stack, args)
28
+ step_args = _assert_step_arguments(stack, args)
33
29
 
34
30
  raise MissingStepError, "Missing ##{step.name} implementation." if step.missing?
35
31
 
36
- if expected_args_count != step_args.count
37
- raise StepArgumentError,
38
- "##{step.name} expects #{expected_args_count} arguments it only received #{step_args.count}, make sure your previous step Success() statement has a payload."
39
- end
40
-
41
32
  step.call(*step_args)
42
33
  end
43
34
 
44
- def _assert_step_arguments_with_count(stack, args)
45
- step_args_count = stack.current_step.args_count
46
-
35
+ def _assert_step_arguments(stack, args)
47
36
  if _should_prepend_previous_step_result_to_args?(stack)
48
37
  prev_step_result_value = stack.previous_step_value
49
38
 
50
39
  args = [prev_step_result_value] + args
51
40
  end
52
41
 
53
- args.first(step_args_count)
42
+ args
54
43
  end
55
44
 
56
45
  def _should_prepend_previous_step_result_to_args?(stack)
@@ -16,7 +16,7 @@ module UseCases
16
16
  def deserialize_step_arguments(args)
17
17
  args.map do |arg|
18
18
  if arg.is_a?(Hash) && arg.delete("_serialized_by_use_case")
19
- arg.delete('_class').constantize.new(arg)
19
+ arg.delete("_class").constantize.new(arg)
20
20
  else
21
21
  arg
22
22
  end
@@ -6,16 +6,6 @@ module UseCases
6
6
  module StepAdapters
7
7
  class Abstract
8
8
  include Dry::Monads
9
-
10
- # include Dry::Events::Publisher[name || object_id]
11
-
12
- # def self.inherited(subclass)
13
- # super
14
- # subclass.register_event(:step)
15
- # subclass.register_event(:step_succeeded)
16
- # subclass.register_event(:step_failed)
17
- # end
18
-
19
9
  include Dry::Monads[:result]
20
10
 
21
11
  attr_reader :name, :object, :failure, :options
@@ -31,22 +21,11 @@ module UseCases
31
21
  end
32
22
 
33
23
  def call(*args)
34
- around_call(name, args: args) do
35
- before_call(name, args: args)
36
-
37
- result = StepResult.new(self, do_call(*args))
38
-
39
- if result.success?
40
- after_call_success(name, args: args, value: result.value)
41
- else
42
- after_call_failure(name, args: args, value: result.value)
43
- end
44
-
45
- result
46
- end
24
+ UseCases::Result.new(self, do_call(*args))
47
25
  end
48
26
 
49
- def do_call(*args)
27
+ def do_call(*initial_args)
28
+ args = callable_args(initial_args)
50
29
  callable_proc.call(*args)
51
30
  end
52
31
 
@@ -54,45 +33,71 @@ module UseCases
54
33
  self.class.new(name, object, options)
55
34
  end
56
35
 
36
+ def callable_args(args)
37
+ if callable_args_count > args.count
38
+ raise StepArgumentError,
39
+ "##{step.name} expects #{expected_args_count} arguments it only received #{step_args.count}, make sure your previous step Success() statement has a payload."
40
+ end
41
+
42
+ return args.first(callable_args_count) unless external? && selects_external_args?
43
+
44
+ hashed_args(args).values
45
+ end
46
+
47
+ def hashed_args(args)
48
+ {
49
+ params: args.first,
50
+ current_user: args.last,
51
+ previous_step_result: args.last
52
+ }.slice(pass_option)
53
+ end
54
+
57
55
  def callable_proc
58
56
  callable_object.method(callable_method).to_proc
59
57
  end
60
58
 
61
59
  def callable_object
62
- case options[:with]
60
+ case with_option
63
61
  when NilClass, FalseClass then object
64
- when String, Symbol then object.send(options[:with])
65
- else options[:with]
62
+ when Symbol then object.send(with_option)
63
+ when String then UseCases.config.container[with_option]
64
+ else with_option
66
65
  end
67
66
  end
68
67
 
69
68
  def callable_method
70
- case options[:with]
69
+ case with_option
71
70
  when NilClass, FalseClass then name
72
71
  else :call
73
72
  end
74
73
  end
75
74
 
76
- def external?
77
- options[:with].present?
75
+ def with_option
76
+ options[:with]
78
77
  end
79
78
 
80
- def args_count
81
- callable_proc.parameters.count
79
+ def pass_option
80
+ options[:pass]
82
81
  end
83
82
 
84
- def missing?
85
- !callable_object.respond_to?(callable_method, true)
83
+ def previous_input_param_name
84
+ options[:merge_input_as] || :input
86
85
  end
87
86
 
88
- def before_call(*args); end
87
+ def external?
88
+ with_option.present?
89
+ end
89
90
 
90
- def after_call_success(*args); end
91
+ def selects_external_args?
92
+ pass_option.present?
93
+ end
91
94
 
92
- def after_call_failure(*args); end
95
+ def callable_args_count
96
+ callable_proc.parameters.count
97
+ end
93
98
 
94
- def around_call(*_args, &blk)
95
- blk.call
99
+ def missing?
100
+ !callable_object.respond_to?(callable_method, true)
96
101
  end
97
102
  end
98
103
  end
@@ -11,6 +11,7 @@ require "use_cases/step_active_job_adapter"
11
11
  module UseCases
12
12
  module StepAdapters
13
13
  def self.included(base)
14
+ super
14
15
  base.class_eval do
15
16
  register_adapter StepAdapters::Step
16
17
  register_adapter StepAdapters::Tee
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UseCases
4
- VERSION = "0.3.8"
4
+ VERSION = "1.0.12"
5
5
  end
data/lib/use_cases.rb CHANGED
@@ -1,11 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dry/configurable"
4
+
3
5
  require_relative "use_case"
4
6
  require_relative "use_cases/version"
5
- require_relative "use_cases/base"
6
7
 
7
8
  module UseCases
8
- def self.register_type(type_name, klass)
9
+ class StepArgumentError < ArgumentError; end
10
+
11
+ class MissingStepError < NoMethodError; end
12
+
13
+ class PreviousStepInvalidReturn < StandardError; end
14
+
15
+ extend Dry::Configurable
9
16
 
10
- end
17
+ setting :container, reader: true
18
+ setting :publisher, default: ::UseCases::Events::Publisher, reader: true
19
+ setting :subscribers, default: [], reader: true
11
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: use_cases
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ring Twice
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-06 00:00:00.000000000 Z
11
+ date: 2022-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -145,30 +145,30 @@ files:
145
145
  - bin/setup
146
146
  - lib/use_case.rb
147
147
  - lib/use_cases.rb
148
- - lib/use_cases/authorize.rb
149
- - lib/use_cases/base.rb
150
148
  - lib/use_cases/dsl.rb
151
- - lib/use_cases/errors.rb
149
+ - lib/use_cases/events/publish_job.rb
150
+ - lib/use_cases/events/publisher.rb
151
+ - lib/use_cases/events/subscriber.rb
152
152
  - lib/use_cases/module_optins.rb
153
- - lib/use_cases/notifications.rb
153
+ - lib/use_cases/module_optins/authorized.rb
154
+ - lib/use_cases/module_optins/prepared.rb
155
+ - lib/use_cases/module_optins/publishing.rb
156
+ - lib/use_cases/module_optins/transactional.rb
157
+ - lib/use_cases/module_optins/validated.rb
154
158
  - lib/use_cases/params.rb
155
- - lib/use_cases/prepare.rb
159
+ - lib/use_cases/result.rb
156
160
  - lib/use_cases/rspec/matchers.rb
157
161
  - lib/use_cases/stack.rb
158
162
  - lib/use_cases/stack_runner.rb
159
163
  - lib/use_cases/step_active_job_adapter.rb
160
164
  - lib/use_cases/step_adapters.rb
161
165
  - lib/use_cases/step_adapters/abstract.rb
162
- - lib/use_cases/step_adapters/authorize.rb
163
166
  - lib/use_cases/step_adapters/check.rb
164
167
  - lib/use_cases/step_adapters/enqueue.rb
165
168
  - lib/use_cases/step_adapters/map.rb
166
169
  - lib/use_cases/step_adapters/step.rb
167
170
  - lib/use_cases/step_adapters/tee.rb
168
171
  - lib/use_cases/step_adapters/try.rb
169
- - lib/use_cases/step_result.rb
170
- - lib/use_cases/transaction.rb
171
- - lib/use_cases/validate.rb
172
172
  - lib/use_cases/version.rb
173
173
  - use_cases.gemspec
174
174
  homepage: https://github.com/listminut/use_cases
@@ -193,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
195
  requirements: []
196
- rubygems_version: 3.0.3.1
196
+ rubygems_version: 3.1.6
197
197
  signing_key:
198
198
  specification_version: 4
199
199
  summary: Use Cases
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module UseCases
4
- module Authorize
5
- class NoAuthorizationError < StandardError; end
6
-
7
- def self.included(base)
8
- base.class_eval do
9
- extend DSL
10
- end
11
- end
12
-
13
- module DSL
14
- def authorize(step_name, options = {})
15
- options[:failure] = :unauthorized
16
- check step_name, **options
17
- end
18
- end
19
- end
20
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "use_case"
4
- module UseCases
5
- class Base
6
- include UseCase
7
- end
8
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module UseCases
4
- class StepArgumentError < ArgumentError; end
5
-
6
- class MissingStepError < NoMethodError; end
7
-
8
- class PreviousStepInvalidReturn < StandardError; end
9
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module UseCases
4
- module Notifications
5
- def self.included(base)
6
- base.extend DSL
7
- end
8
-
9
- module DSL
10
- def subscribe_to_step(event_id, listener)
11
- step_subscriptions << StepSubscription.new(event_id, listener)
12
- end
13
-
14
- def bind_step_subscriptions
15
- step_subscriptions.each { |subscription| subscription.bind(__steps__) }
16
- end
17
-
18
- def step_subscriptions
19
- @step_subscriptions ||= []
20
- end
21
- end
22
-
23
- class StepSubscription
24
- attr_reader :event_id, :listener, :steps
25
-
26
- def initialize(event_id, listener)
27
- @event_id = event_id
28
- @listener = listener
29
- end
30
-
31
- def bind(steps)
32
- @steps = steps
33
-
34
- step = steps.find { |s| s.name == step_name }
35
- step.subscribe(event_predicate, listener)
36
- end
37
-
38
- def event_predicate
39
- predicate = event_id.to_s.gsub(step_name.to_s, "")
40
-
41
- "step#{predicate}".to_sym
42
- end
43
-
44
- def step_name
45
- steps.map(&:name).find { |step_name| step_name.start_with?(event_id.to_s) }
46
- end
47
- end
48
- end
49
- end