servactory 1.4.4 → 1.4.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54a713971d52bebd38a97f473eb942be19f5c4029604aa26f0729cb1dc40f24a
4
- data.tar.gz: 1b368e874cdf3c502328fe3dbf71758a3c0821140c1b0e9a595f6622b41ec1bc
3
+ metadata.gz: 8878b890cc7b43a45e8ea3e982a27e5873ec4d1aeff1ac91f6ce9ea77cc98e38
4
+ data.tar.gz: 4f2947b852d2f0e53965b79b88ce013ae442575dee4f6539b1a2684e242211a5
5
5
  SHA512:
6
- metadata.gz: c2e412f4e06a1a21aa70a43a0f898f6d5fc0adcca1eaf7922edb80aa719313b9c2325d83ef41e34eda9c8cacc988e60b076938e23b1ff1e709d50aee9ce0f0b1
7
- data.tar.gz: d871cef64e8249b89572f6b97e9fdd9410632d20a393ea77d59f36481acc7f0207bfa26e6c79749a2031962927b965955986f257ce298d554d8289f2f85bfaf4
6
+ metadata.gz: 437c627bef5b24d7f5a5e7b3f4c3b75e207af58d37900d6a8c6cf8b73c69e6704feb575f01233362f1ce2c40f7c640d2900d3d58e416ea12ed682c9f23fd058b
7
+ data.tar.gz: fc9a5f1a97cf8db5483df7be46f805a6c8f5598b559237a89c2409ee761d61204b79da375d3d0228133b88f4e60fe124c22519c6a314d39bb33dbf585cd5422a
data/README.md CHANGED
@@ -14,10 +14,13 @@ A set of tools for building reliable services of any complexity.
14
14
  - [Preparation](#preparation)
15
15
  - [Usage](#usage)
16
16
  - [Minimal example](#minimal-example)
17
+ - [Call](#call)
18
+ - [Result](#result)
17
19
  - [Input attributes](#input-attributes)
18
20
  - [Isolated usage](#isolated-usage)
19
21
  - [As an internal argument](#isolated-usage)
20
22
  - [Optional inputs](#optional-inputs)
23
+ - [As (internal name)](#as-internal-name)
21
24
  - [An array of specific values](#an-array-of-specific-values)
22
25
  - [Inclusion](#inclusion)
23
26
  - [Must](#must)
@@ -25,7 +28,6 @@ A set of tools for building reliable services of any complexity.
25
28
  - [Internal attributes](#internal-attributes)
26
29
  - [Stage](#stage)
27
30
  - [Failures](#failures)
28
- - [Result](#result)
29
31
  - [Testing](#testing)
30
32
  - [Thanks](#thanks)
31
33
  - [Contributing](#contributing)
@@ -111,6 +113,47 @@ end
111
113
 
112
114
  [More examples](https://github.com/afuno/servactory/tree/main/examples/usual)
113
115
 
116
+ ### Call
117
+
118
+ Services can only be called via `.call` and `.call!` methods.
119
+
120
+ The `.call` method will only fail if it catches an exception in the input arguments.
121
+ Internal and output attributes, as well as methods for failures - all this will be collected in the result.
122
+
123
+ The `.call!` method will fail if it catches any exception.
124
+
125
+ #### Via .call
126
+
127
+ ```ruby
128
+ UsersService::Accept.call(user: User.first)
129
+ ```
130
+
131
+ #### Via .call!
132
+
133
+ ```ruby
134
+ UsersService::Accept.call!(user: User.first)
135
+ ```
136
+
137
+ ### Result
138
+
139
+ All services have the result of their work. For example, in case of success this call:
140
+
141
+ ```ruby
142
+ service_result = UsersService::Accept.call!(user: User.first)
143
+ ```
144
+
145
+ Will return this:
146
+
147
+ ```ruby
148
+ #<Servactory::Result:0x0000000107ad9e88 @user="...">
149
+ ```
150
+
151
+ And then you can work with this result, for example, in this way:
152
+
153
+ ```ruby
154
+ Notification::SendJob.perform_later(service_result.user.id)
155
+ ```
156
+
114
157
  ### Input attributes
115
158
 
116
159
  #### Isolated usage
@@ -163,7 +206,7 @@ class UsersService::Create < ApplicationService::Base
163
206
  end
164
207
  ```
165
208
 
166
- #### As
209
+ #### As (internal name)
167
210
 
168
211
  This option changes the name of the input within the service.
169
212
 
@@ -346,26 +389,6 @@ def check!
346
389
  end
347
390
  ```
348
391
 
349
- ### Result
350
-
351
- All services have the result of their work. For example, in case of success this call:
352
-
353
- ```ruby
354
- service_result = NotificationService::Create.call!(user: User.first)
355
- ```
356
-
357
- Will return this:
358
-
359
- ```ruby
360
- #<Servactory::Result:0x0000000112c00748 @notification=...>
361
- ```
362
-
363
- And then you can work with this result, for example, in this way:
364
-
365
- ```ruby
366
- Notification::SendJob.perform_later(service_result.notification.id)
367
- ```
368
-
369
392
  ## Testing
370
393
 
371
394
  Testing Servactory services is the same as testing regular Ruby classes.
@@ -28,6 +28,29 @@ module Servactory
28
28
  )
29
29
  end
30
30
 
31
+ def call(arguments = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
32
+ @context_store = Store.new(self)
33
+
34
+ assign_data_with(arguments)
35
+
36
+ input_arguments_workbench.find_unnecessary!
37
+ input_arguments_workbench.check_rules!
38
+ output_arguments_workbench.find_conflicts_in!(
39
+ collection_of_internal_arguments: collection_of_internal_arguments
40
+ )
41
+
42
+ prepare_data
43
+
44
+ input_arguments_workbench.check!
45
+
46
+ stage_handyman.run_methods!
47
+
48
+ Servactory::Result.prepare_for(
49
+ context: context_store.context,
50
+ collection_of_output_arguments: collection_of_output_arguments
51
+ )
52
+ end
53
+
31
54
  private
32
55
 
33
56
  attr_reader :context_store
@@ -6,7 +6,7 @@ module Servactory
6
6
  class Errors
7
7
  # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
8
8
  extend Forwardable
9
- def_delegators :@collection, :<<, :filter, :reject, :empty?, :first
9
+ def_delegators :@collection, :<<, :to_a, :filter, :reject, :empty?, :first
10
10
 
11
11
  def initialize(collection = Set.new)
12
12
  @collection = collection
@@ -9,7 +9,7 @@ module Servactory
9
9
  def add_error(message, **arguments)
10
10
  message = message.call(**arguments) if message.is_a?(Proc)
11
11
 
12
- errors.add(message)
12
+ errors << message
13
13
  end
14
14
 
15
15
  private
@@ -6,7 +6,7 @@ module Servactory
6
6
  class Errors
7
7
  # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
8
8
  extend Forwardable
9
- def_delegators :@collection, :add, :to_a
9
+ def_delegators :@collection, :<<, :to_a
10
10
 
11
11
  def initialize(*)
12
12
  @collection = Set.new
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ class DefineInputConflict
6
+ attr_reader :content
7
+
8
+ def initialize(content:)
9
+ @content = content
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ class DefineInputMethod
6
+ attr_reader :name,
7
+ :content
8
+
9
+ def initialize(name:, content:)
10
+ @name = name.to_sym
11
+ @content = content
12
+ end
13
+ end
14
+ end
15
+ end
@@ -55,22 +55,19 @@ module Servactory
55
55
  name: :required,
56
56
  input: self,
57
57
  check_class: Servactory::InputArguments::Checks::Required,
58
- define_input_methods: lambda do
59
- <<-RUBY
60
- def required?
61
- Servactory::Utils.boolean?(required[:is])
62
- end
63
-
64
- def optional?
65
- !required?
66
- end
67
- RUBY
68
- end,
69
- define_conflicts: lambda do
70
- <<-RUBY
71
- return :required_vs_default if required? && default_value_present?
72
- RUBY
73
- end,
58
+ define_input_methods: [
59
+ DefineInputMethod.new(
60
+ name: :required?,
61
+ content: ->(value:) { Servactory::Utils.boolean?(value[:is]) }
62
+ ),
63
+ DefineInputMethod.new(
64
+ name: :optional?,
65
+ content: ->(value:) { !Servactory::Utils.boolean?(value[:is]) }
66
+ )
67
+ ],
68
+ define_input_conflicts: [
69
+ DefineInputConflict.new(content: -> { return :required_vs_default if required? && default_value_present? })
70
+ ],
74
71
  need_for_checks: true,
75
72
  value_key: :is,
76
73
  value_fallback: true,
@@ -95,13 +92,12 @@ module Servactory
95
92
  name: :default,
96
93
  input: self,
97
94
  check_class: Servactory::InputArguments::Checks::Type,
98
- define_input_methods: lambda do
99
- <<-RUBY
100
- def default_value_present?
101
- !default.nil?
102
- end
103
- RUBY
104
- end,
95
+ define_input_methods: [
96
+ DefineInputMethod.new(
97
+ name: :default_value_present?,
98
+ content: ->(value:) { !value.nil? }
99
+ )
100
+ ],
105
101
  need_for_checks: true,
106
102
  value_fallback: nil,
107
103
  with_advanced_mode: false,
@@ -114,19 +110,16 @@ module Servactory
114
110
  name: :array,
115
111
  input: self,
116
112
  check_class: Servactory::InputArguments::Checks::Type,
117
- define_input_methods: lambda do
118
- <<-RUBY
119
- def array?
120
- Servactory::Utils.boolean?(array[:is])
121
- end
122
- RUBY
123
- end,
124
- define_conflicts: lambda do
125
- <<-RUBY
126
- return :array_vs_array if array? && types.include?(Array)
127
- return :array_vs_inclusion if array? && inclusion_present?
128
- RUBY
129
- end,
113
+ define_input_methods: [
114
+ DefineInputMethod.new(
115
+ name: :array?,
116
+ content: ->(value:) { Servactory::Utils.boolean?(value[:is]) }
117
+ )
118
+ ],
119
+ define_input_conflicts: [
120
+ DefineInputConflict.new(content: -> { return :array_vs_array if array? && types.include?(Array) }),
121
+ DefineInputConflict.new(content: -> { return :array_vs_inclusion if array? && inclusion_present? })
122
+ ],
130
123
  need_for_checks: false,
131
124
  value_key: :is,
132
125
  value_fallback: false,
@@ -139,13 +132,12 @@ module Servactory
139
132
  name: :inclusion,
140
133
  input: self,
141
134
  check_class: Servactory::InputArguments::Checks::Inclusion,
142
- define_input_methods: lambda do
143
- <<-RUBY
144
- def inclusion_present?
145
- inclusion[:in].is_a?(Array) && inclusion[:in].present?
146
- end
147
- RUBY
148
- end,
135
+ define_input_methods: [
136
+ DefineInputMethod.new(
137
+ name: :inclusion_present?,
138
+ content: ->(value:) { value[:in].is_a?(Array) && value[:in].present? }
139
+ )
140
+ ],
149
141
  need_for_checks: true,
150
142
  value_key: :in,
151
143
  value_fallback: nil,
@@ -158,13 +150,12 @@ module Servactory
158
150
  name: :must,
159
151
  input: self,
160
152
  check_class: Servactory::InputArguments::Checks::Must,
161
- define_input_methods: lambda do
162
- <<-RUBY
163
- def must_present?
164
- must.present?
165
- end
166
- RUBY
167
- end,
153
+ define_input_methods: [
154
+ DefineInputMethod.new(
155
+ name: :must_present?,
156
+ content: ->(value:) { value.present? }
157
+ )
158
+ ],
168
159
  need_for_checks: true,
169
160
  value_key: :is,
170
161
  value_fallback: nil,
@@ -178,13 +169,12 @@ module Servactory
178
169
  name: :internal,
179
170
  input: self,
180
171
  check_class: nil,
181
- define_input_methods: lambda do
182
- <<-RUBY
183
- def internal?
184
- Servactory::Utils.boolean?(internal[:is])
185
- end
186
- RUBY
187
- end,
172
+ define_input_methods: [
173
+ DefineInputMethod.new(
174
+ name: :internal?,
175
+ content: ->(value:) { Servactory::Utils.boolean?(value[:is]) }
176
+ )
177
+ ],
188
178
  need_for_checks: false,
189
179
  value_key: :is,
190
180
  value_fallback: false,
@@ -197,9 +187,7 @@ module Servactory
197
187
  end
198
188
 
199
189
  def conflict_code
200
- instance_eval(collection_of_options.defined_conflicts)
201
-
202
- nil
190
+ collection_of_options.defined_conflict_code
203
191
  end
204
192
 
205
193
  def with_conflicts?
@@ -9,7 +9,8 @@ module Servactory
9
9
 
10
10
  attr_reader :name,
11
11
  :check_class,
12
- :define_conflicts,
12
+ :define_input_methods,
13
+ :define_input_conflicts,
13
14
  :need_for_checks,
14
15
  :value_key,
15
16
  :value
@@ -24,13 +25,14 @@ module Servactory
24
25
  original_value: nil,
25
26
  value_key: nil,
26
27
  define_input_methods: nil,
27
- define_conflicts: nil,
28
+ define_input_conflicts: nil,
28
29
  with_advanced_mode: true,
29
30
  **options
30
31
  ) # do
31
32
  @name = name.to_sym
32
33
  @check_class = check_class
33
- @define_conflicts = define_conflicts
34
+ @define_input_methods = define_input_methods
35
+ @define_input_conflicts = define_input_conflicts
34
36
  @need_for_checks = need_for_checks
35
37
  @value_key = value_key
36
38
 
@@ -41,7 +43,7 @@ module Servactory
41
43
  with_advanced_mode: with_advanced_mode
42
44
  )
43
45
 
44
- input.instance_eval(define_input_methods.call) if define_input_methods.present?
46
+ prepare_input_methods_for(input)
45
47
  end
46
48
  # rubocop:enable Metrics/MethodLength
47
49
 
@@ -73,6 +75,22 @@ module Servactory
73
75
  DEFAULT_VALUE.call(key: value_key, value: value)
74
76
  end
75
77
  end
78
+
79
+ def prepare_input_methods_for(input)
80
+ input.instance_eval(define_input_methods_template) if define_input_methods_template.present?
81
+ end
82
+
83
+ def define_input_methods_template
84
+ return if @define_input_methods.blank?
85
+
86
+ @define_input_methods_template ||= @define_input_methods.map do |define_input_method|
87
+ <<-RUBY
88
+ def #{define_input_method.name}
89
+ #{define_input_method.content.call(value: @value)}
90
+ end
91
+ RUBY
92
+ end.join("\n")
93
+ end
76
94
  end
77
95
  end
78
96
  end
@@ -5,7 +5,7 @@ module Servactory
5
5
  class OptionsCollection
6
6
  # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
7
7
  extend Forwardable
8
- def_delegators :@collection, :<<, :filter, :each, :map
8
+ def_delegators :@collection, :<<, :filter, :each, :map, :flat_map
9
9
 
10
10
  def initialize(*)
11
11
  @collection = Set.new
@@ -27,8 +27,12 @@ module Servactory
27
27
  end
28
28
  end
29
29
 
30
- def defined_conflicts
31
- map { |option| option.define_conflicts&.call }.reject(&:blank?).uniq.join
30
+ def defined_conflict_code
31
+ flat_map do |option|
32
+ option.define_input_conflicts&.map do |define_input_conflict|
33
+ define_input_conflict.content.call
34
+ end
35
+ end.reject(&:blank?).first
32
36
  end
33
37
  end
34
38
  end
@@ -9,13 +9,24 @@ module Servactory
9
9
  private
10
10
 
11
11
  def prepare_for(context:, collection_of_output_arguments:)
12
+ prepare_outputs_with(context: context, collection_of_output_arguments: collection_of_output_arguments)
13
+ prepare_statuses_with(context: context)
14
+
15
+ self
16
+ end
17
+
18
+ def prepare_outputs_with(context:, collection_of_output_arguments:)
12
19
  collection_of_output_arguments.each do |output|
13
20
  self.class.attr_reader(:"#{output.name}")
14
21
 
15
22
  instance_variable_set(:"@#{output.name}", context.instance_variable_get(:"@#{output.name}"))
16
23
  end
24
+ end
17
25
 
18
- self
26
+ def prepare_statuses_with(context:)
27
+ define_singleton_method(:errors) { context.errors }
28
+ define_singleton_method(:success?) { context.errors.empty? }
29
+ define_singleton_method(:failure?) { !context.errors.empty? }
19
30
  end
20
31
  end
21
32
  end
@@ -4,7 +4,7 @@ module Servactory
4
4
  module VERSION
5
5
  MAJOR = 1
6
6
  MINOR = 4
7
- PATCH = 4
7
+ PATCH = 5
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: servactory
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.4
4
+ version: 1.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Sokolov
@@ -183,6 +183,8 @@ files:
183
183
  - lib/servactory/input_arguments/checks/required.rb
184
184
  - lib/servactory/input_arguments/checks/type.rb
185
185
  - lib/servactory/input_arguments/collection.rb
186
+ - lib/servactory/input_arguments/define_input_conflict.rb
187
+ - lib/servactory/input_arguments/define_input_method.rb
186
188
  - lib/servactory/input_arguments/dsl.rb
187
189
  - lib/servactory/input_arguments/input_argument.rb
188
190
  - lib/servactory/input_arguments/option.rb