servactory 1.6.4 → 1.6.6

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: e8baf1ff3aab8a43c92e83987cb5f4cfbc426be9aacabf5fe63dd01e133c891e
4
- data.tar.gz: 0af5e8fe8bf02fe5f15c68655e298b2d097b402764d231045683907afa35ded3
3
+ metadata.gz: c8df03a33262af045766ff179c047c3260b85f182aa0541137597e13d7d50bbe
4
+ data.tar.gz: 813454694dafd13757259a9c45ad455e30e4fd73013a8d51b5022624d3bd194a
5
5
  SHA512:
6
- metadata.gz: aa8ffc01e6bab482d397f07e18847ec5d0b70bbd1e94bf7ff1ab6f080efca2502547ee92db3b663ba2bac15c2c6add762094ab37d9c47b73eb7b66f8f3ed9b10
7
- data.tar.gz: a15fc2c38e21b6637ff5b9f1273006c0aae2dfe5d8d084390a18e2ce262329510fe80ab2bc16d47f18f3da8a40c5f5daea0533ad4b611a0e9ce8868eacf87b8f
6
+ metadata.gz: b61bf7ef77779b5f8c697c514a2af1baac91095c553a31ff60c9e6080233f6153eae13b24a818e527a3f887755e35c3643ffd81c47fbbabdb5c64f8dc1a98054
7
+ data.tar.gz: 9722ed5fff0d2390a11636e2dc28e313cb338c726e8a9c630c4eee75ac1046a8684cb7dc7385691aaab7393bf1b9568a61b500c5a2334ca2d157d2eb239988d5
data/README.md CHANGED
@@ -9,9 +9,56 @@ A set of tools for building reliable services of any complexity.
9
9
 
10
10
  See [servactory.com](https://servactory.com) for documentation.
11
11
 
12
+ ## Examples
13
+
14
+ ### Service
15
+
16
+ ```ruby
17
+ class UserService::Authenticate < ApplicationService::Base
18
+ input :email, type: String
19
+ input :password, type: String
20
+
21
+ output :user, type: User
22
+
23
+ private
24
+
25
+ def call
26
+ if (user = User.find_by(email: inputs.email)&.authenticate(inputs.password))
27
+ self.user = user
28
+ else
29
+ fail!(message: "Authentication failed")
30
+ end
31
+ end
32
+ end
33
+ ```
34
+
35
+ ### Using in controller
36
+
37
+ ```ruby
38
+ class SessionsController < ApplicationController
39
+ def create
40
+ service_result = UserService::Authenticate.call(**session_params)
41
+
42
+ if service_result.success?
43
+ session[:current_user_id] = service_result.user.id
44
+ redirect_to service_result.user
45
+ else
46
+ flash.now[:message] = service_result.errors.first.message
47
+ render :new
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def session_params
54
+ params.require(:session).permit(:email, :password)
55
+ end
56
+ end
57
+ ```
58
+
12
59
  ## Contributing
13
60
 
14
- This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./docs/pages/CONTRIBUTING.md) as well.
61
+ This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./website/docs/CONTRIBUTING.md) as well.
15
62
 
16
63
  ## License
17
64
 
@@ -16,9 +16,10 @@ module Servactory
16
16
 
17
17
  private
18
18
 
19
- def input(name, **options)
19
+ def input(name, *helpers, **options)
20
20
  collection_of_inputs << Input.new(
21
21
  name,
22
+ *helpers,
22
23
  **options
23
24
  )
24
25
  end
@@ -5,12 +5,21 @@ module Servactory
5
5
  class Input # rubocop:disable Metrics/ClassLength
6
6
  ARRAY_DEFAULT_VALUE = ->(is: false, message: nil) { { is: is, message: message } }
7
7
 
8
+ HELPER_LIBRARY = {
9
+ optional: { required: false },
10
+ internal: { internal: true },
11
+ as_array: { array: true }
12
+ }.freeze
13
+
14
+ private_constant :ARRAY_DEFAULT_VALUE, :HELPER_LIBRARY
15
+
8
16
  attr_reader :name,
9
17
  :internal_name
10
18
 
11
19
  # rubocop:disable Style/KeywordParametersOrder
12
20
  def initialize(
13
21
  name,
22
+ *helpers,
14
23
  as: nil,
15
24
  type:,
16
25
  **options
@@ -18,6 +27,8 @@ module Servactory
18
27
  @name = name
19
28
  @internal_name = as.present? ? as : name
20
29
 
30
+ options = apply_helpers_for_options(helpers: helpers, options: options) if helpers.present?
31
+
21
32
  add_basic_options_with(type: type, options: options)
22
33
 
23
34
  collection_of_options.each do |option|
@@ -28,6 +39,20 @@ module Servactory
28
39
  end
29
40
  # rubocop:enable Style/KeywordParametersOrder
30
41
 
42
+ def apply_helpers_for_options(helpers:, options:)
43
+ prepared_options = {}
44
+
45
+ helpers.each do |helper|
46
+ next unless HELPER_LIBRARY.key?(helper)
47
+
48
+ found_helper = HELPER_LIBRARY[helper]
49
+
50
+ prepared_options.merge!(found_helper)
51
+ end
52
+
53
+ options.merge(prepared_options)
54
+ end
55
+
31
56
  def add_basic_options_with(type:, options:)
32
57
  # Check Class: Servactory::Inputs::Validations::Required
33
58
  add_required_option_with(options)
@@ -63,7 +88,7 @@ module Servactory
63
88
  )
64
89
  ],
65
90
  define_input_conflicts: [
66
- DefineInputConflict.new(content: -> { return :required_vs_default if required? && default_value_present? })
91
+ DefineInputConflict.new(content: -> { :required_vs_default if required? && default_value_present? })
67
92
  ],
68
93
  need_for_checks: true,
69
94
  value_key: :is,
@@ -114,8 +139,8 @@ module Servactory
114
139
  )
115
140
  ],
116
141
  define_input_conflicts: [
117
- DefineInputConflict.new(content: -> { return :array_vs_array if array? && types.include?(Array) }),
118
- DefineInputConflict.new(content: -> { return :array_vs_inclusion if array? && inclusion_present? })
142
+ DefineInputConflict.new(content: -> { :array_vs_array if array? && types.include?(Array) }),
143
+ DefineInputConflict.new(content: -> { :array_vs_inclusion if array? && inclusion_present? })
119
144
  ],
120
145
  need_for_checks: false,
121
146
  value_key: :is,
@@ -31,6 +31,8 @@ module Servactory
31
31
  end
32
32
 
33
33
  def validate!
34
+ return unless should_be_checked?
35
+
34
36
  return if prepared_types.any? { |type| @value.is_a?(type) }
35
37
 
36
38
  raise_error_with(
@@ -44,6 +46,12 @@ module Servactory
44
46
 
45
47
  private
46
48
 
49
+ def should_be_checked?
50
+ @internal.required? || (
51
+ @internal.optional? && !@value.nil?
52
+ )
53
+ end
54
+
47
55
  def prepared_types
48
56
  @prepared_types ||=
49
57
  Array(@internal.types).map do |type|
@@ -11,17 +11,45 @@ module Servactory
11
11
  def inherited(child)
12
12
  super
13
13
 
14
- child.send(:collection_of_methods).merge(collection_of_methods)
14
+ child.send(:collection_of_stages).merge(collection_of_stages)
15
15
  end
16
16
 
17
17
  private
18
18
 
19
+ def stage(&block)
20
+ @current_stage = Stage.new(position: next_position)
21
+
22
+ instance_eval(&block)
23
+
24
+ @current_stage = nil
25
+
26
+ nil
27
+ end
28
+
29
+ def wrap_in(wrapper)
30
+ return if @current_stage.blank?
31
+
32
+ @current_stage.wrapper = wrapper
33
+ end
34
+
35
+ def rollback(rollback)
36
+ return if @current_stage.blank?
37
+
38
+ @current_stage.rollback = rollback
39
+ end
40
+
19
41
  def make(name, position: nil, **options)
20
- collection_of_methods << Method.new(
42
+ position = position.presence || next_position
43
+
44
+ @current_stage = @current_stage.presence || Stage.new(position: position)
45
+
46
+ @current_stage.methods << Method.new(
21
47
  name,
22
- position: position.presence || next_position,
48
+ position: position,
23
49
  **options
24
50
  )
51
+
52
+ collection_of_stages << @current_stage
25
53
  end
26
54
 
27
55
  def method_missing(shortcut_name, *args, &block)
@@ -39,15 +67,15 @@ module Servactory
39
67
  end
40
68
 
41
69
  def next_position
42
- collection_of_methods.size + 1
70
+ collection_of_stages.size + 1
43
71
  end
44
72
 
45
- def collection_of_methods
46
- @collection_of_methods ||= Collection.new
73
+ def collection_of_stages
74
+ @collection_of_stages ||= StageCollection.new
47
75
  end
48
76
 
49
77
  def methods_workbench
50
- @methods_workbench ||= Workbench.work_with(collection_of_methods)
78
+ @methods_workbench ||= Workbench.work_with(collection_of_stages)
51
79
  end
52
80
  end
53
81
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Methods
5
+ class MethodCollection
6
+ # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
7
+ extend Forwardable
8
+ def_delegators :@collection, :<<, :each, :sort_by
9
+
10
+ def initialize(collection = Set.new)
11
+ @collection = collection
12
+ end
13
+
14
+ def sorted_by_position
15
+ MethodCollection.new(sort_by(&:position))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module Methods
5
+ class Stage
6
+ attr_accessor :position,
7
+ :wrapper,
8
+ :rollback
9
+
10
+ def initialize(position:, wrapper: nil, rollback: nil)
11
+ @position = position
12
+ @wrapper = wrapper
13
+ @rollback = rollback
14
+ end
15
+
16
+ def next_method_position
17
+ methods.size + 1
18
+ end
19
+
20
+ def methods
21
+ @methods ||= MethodCollection.new
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Servactory
4
4
  module Methods
5
- class Collection
5
+ class StageCollection
6
6
  # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
7
7
  extend Forwardable
8
8
  def_delegators :@collection, :<<, :each, :merge, :sort_by, :size, :empty?
@@ -12,7 +12,7 @@ module Servactory
12
12
  end
13
13
 
14
14
  def sorted_by_position
15
- Collection.new(sort_by(&:position))
15
+ StageCollection.new(sort_by(&:position))
16
16
  end
17
17
  end
18
18
  end
@@ -7,8 +7,8 @@ module Servactory
7
7
  new(...)
8
8
  end
9
9
 
10
- def initialize(collection_of_methods)
11
- @collection_of_methods = collection_of_methods
10
+ def initialize(collection_of_stages)
11
+ @collection_of_stages = collection_of_stages
12
12
  end
13
13
 
14
14
  def assign(context:)
@@ -16,24 +16,48 @@ module Servactory
16
16
  end
17
17
 
18
18
  def run!
19
- return try_to_use_call if collection_of_methods.empty?
19
+ return try_to_use_call if collection_of_stages.empty?
20
20
 
21
- collection_of_methods.sorted_by_position.each do |make_method|
22
- next if unnecessary_for?(make_method)
23
-
24
- context.send(make_method.name)
21
+ collection_of_stages.sorted_by_position.each do |stage|
22
+ call_stage(stage)
25
23
  end
26
24
  end
27
25
 
28
26
  private
29
27
 
30
28
  attr_reader :context,
31
- :collection_of_methods
29
+ :collection_of_stages
32
30
 
33
31
  def try_to_use_call
34
32
  context.try(:send, :call)
35
33
  end
36
34
 
35
+ def call_stage(stage)
36
+ wrapper = stage.wrapper
37
+ rollback = stage.rollback
38
+ methods = stage.methods.sorted_by_position
39
+
40
+ if wrapper.is_a?(Proc)
41
+ call_wrapper_with_methods(wrapper, rollback, methods)
42
+ else
43
+ call_methods(methods)
44
+ end
45
+ end
46
+
47
+ def call_wrapper_with_methods(wrapper, rollback, methods)
48
+ wrapper.call(methods: -> { call_methods(methods) })
49
+ rescue StandardError => e
50
+ context.send(rollback, e) if rollback.present?
51
+ end
52
+
53
+ def call_methods(methods)
54
+ methods.each do |make_method|
55
+ next if unnecessary_for?(make_method)
56
+
57
+ context.send(make_method.name)
58
+ end
59
+ end
60
+
37
61
  def unnecessary_for?(make_method)
38
62
  condition = make_method.condition
39
63
  is_condition_opposite = make_method.is_condition_opposite
@@ -31,6 +31,8 @@ module Servactory
31
31
  end
32
32
 
33
33
  def validate!
34
+ return unless should_be_checked?
35
+
34
36
  return if prepared_types.any? { |type| @value.is_a?(type) }
35
37
 
36
38
  raise_error_with(
@@ -44,6 +46,14 @@ module Servactory
44
46
 
45
47
  private
46
48
 
49
+ def should_be_checked?
50
+ @output.required? || (
51
+ @output.optional? && !@output.default.nil?
52
+ ) || (
53
+ @output.optional? && !@value.nil?
54
+ )
55
+ end
56
+
47
57
  def prepared_types
48
58
  @prepared_types ||=
49
59
  Array(@output.types).map do |type|
@@ -4,7 +4,7 @@ module Servactory
4
4
  module VERSION
5
5
  MAJOR = 1
6
6
  MINOR = 6
7
- PATCH = 4
7
+ PATCH = 6
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: servactory
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.4
4
+ version: 1.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Sokolov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-03 00:00:00.000000000 Z
11
+ date: 2023-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -231,10 +231,12 @@ files:
231
231
  - lib/servactory/internals/validations/base.rb
232
232
  - lib/servactory/internals/validations/type.rb
233
233
  - lib/servactory/internals/workbench.rb
234
- - lib/servactory/methods/collection.rb
235
234
  - lib/servactory/methods/dsl.rb
236
235
  - lib/servactory/methods/method.rb
236
+ - lib/servactory/methods/method_collection.rb
237
237
  - lib/servactory/methods/shortcuts/collection.rb
238
+ - lib/servactory/methods/stage.rb
239
+ - lib/servactory/methods/stage_collection.rb
238
240
  - lib/servactory/methods/workbench.rb
239
241
  - lib/servactory/outputs/collection.rb
240
242
  - lib/servactory/outputs/dsl.rb
@@ -252,7 +254,9 @@ licenses:
252
254
  - MIT
253
255
  metadata:
254
256
  homepage_uri: https://github.com/afuno/servactory
257
+ documentation_uri: https://servactory.com
255
258
  source_code_uri: https://github.com/afuno/servactory
259
+ bug_tracker_uri: https://github.com/afuno/servactory/issues
256
260
  changelog_uri: https://github.com/afuno/servactory/blob/master/CHANGELOG.md
257
261
  rubygems_mfa_required: 'true'
258
262
  post_install_message: