aux 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 862a78e8879ec01edc04a4c59c1a7468366f6b9ca8c10791b974553090e4f6c3
4
- data.tar.gz: f247febfd9e323d3ed778dcf20cd3b73504a33feecbbeae06129465426143fda
3
+ metadata.gz: a5a8f2a8523d27a026f50b5e10f3d87c0603a7e2a6710ec0f788b824075c98e0
4
+ data.tar.gz: 9e5873e8a90853626176568ddd63013a09e5dff9da442a54e6f5955344e2461f
5
5
  SHA512:
6
- metadata.gz: 4fb78b1df17d234f1a1738143ca873f1c470f2aafc194dbcdeed2d730240ba3cfc07aeabf19fb33f0d7e9426fcb4523e53394d93e048cc836a856a2a750b2101
7
- data.tar.gz: 100e911b442a7e422a5afd122f42e908ee39fe05a66f60db5ee8294ab4efb697a94234d5d8676f6e39d9ae8a849aa60c56112bdc7985700b5b50e42acc68a425
6
+ metadata.gz: a4a734ffb338fdb30efc987eaadc47378f733818681a126ceddd2d609116ced8cf23474607e8c5e633d65d7f71c232081c2da04b51ef2c840c242d6a1850b26e
7
+ data.tar.gz: 4a8982b1e6d9e1763676537c8ffb8c900f7209af29c096dab88acced564e5b1d25ef6f17644f2227e40fc872bea30a25f2db5a541a48c3e314db92444026bf68
data/MIT-LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2022 Evgeny Boyko
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/aux.gemspec CHANGED
@@ -4,17 +4,14 @@ Gem::Specification.new do |specification|
4
4
  specification.name = 'aux'
5
5
  specification.version = Aux::VERSION
6
6
  specification.summary = 'Supplementary tools for more effective development'
7
- specification.files = Dir['LICENSE', 'README.md', 'aux.gemspec', 'lib/**/*']
7
+ specification.files = Dir['MIT-LICENSE', 'README.md', 'aux.gemspec', 'lib/**/*']
8
8
 
9
9
  specification.authors = ['Evgeny Boyko']
10
10
  specification.email = ['eboyko@eboyko.ru']
11
11
  specification.homepage = 'https://github.com/eboyko/aux'
12
12
 
13
+ specification.add_dependency 'concurrent-ruby', '~> 1.2', '>= 1.2.3'
13
14
  specification.add_dependency 'activemodel', '>= 6.1', '< 8'
14
- specification.add_dependency 'dry-container', '>= 0.9.0', '<= 0.11'
15
-
16
- specification.add_development_dependency 'zeitwerk', '~> 2.5'
17
- specification.add_development_dependency 'rubocop', '~> 1.36.0'
18
15
 
19
16
  specification.required_ruby_version = '>= 2.7.1'
20
17
  specification.metadata['rubygems_mfa_required'] = 'true'
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Carriers
5
+ # Describes a service failure
6
+ class Failure
7
+ # @!attribute [r] code
8
+ # @return [Symbol]
9
+ # @!attribute [r] details
10
+ # @return [Object, nil]
11
+ # @!attribute [r] payload
12
+ # @return [Object, nil]
13
+ attr_reader :code, :details, :payload
14
+
15
+ # @param code [Symbol]
16
+ # @param details [Object, nil]
17
+ # @param payload [Object, nil]
18
+ def initialize(code, details = nil, payload = nil)
19
+ @code = code
20
+ @details = details
21
+ @payload = payload
22
+ end
23
+
24
+ # @return [FalseClass]
25
+ def succeeded?
26
+ false
27
+ end
28
+
29
+ # @!method successful?
30
+ # @return [FalseClass]
31
+ alias successful? succeeded?
32
+
33
+ # @!method success?
34
+ # @return [FalseClass]
35
+ alias success? succeeded?
36
+
37
+ # @return [TrueClass]
38
+ def failed?
39
+ true
40
+ end
41
+
42
+ # @!method failure?
43
+ # @return [TrueClass]
44
+ alias failure? failed?
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Carriers
5
+ class Success
6
+ # @!attribute [r] code
7
+ # @return [Symbol]
8
+ # @!attribute [r] payload
9
+ # @return [Object, nil]
10
+ attr_reader :code, :payload
11
+
12
+ # @param code [Symbol]
13
+ # @param payload [Object, nil]
14
+ def initialize(code, payload = nil)
15
+ @code = code
16
+ @payload = payload
17
+ end
18
+
19
+ # @return [TrueClass]
20
+ def succeeded?
21
+ true
22
+ end
23
+
24
+ # @!method successful?
25
+ # @return [TrueClass]
26
+ alias successful? succeeded?
27
+
28
+ # @!method success?
29
+ # @return [TrueClass]
30
+ alias success? succeeded?
31
+
32
+ # @return [FalseClass]
33
+ def failed?
34
+ false
35
+ end
36
+
37
+ # @!method failure?
38
+ # @return [FalseClass]
39
+ alias failure? failed?
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/carriers/success'
4
+ require 'aux/carriers/failure'
5
+
6
+ module Aux
7
+ module Carriers
8
+ private
9
+
10
+ # @overload success
11
+ # @return [Success]
12
+ # @overload success(code)
13
+ # @param code [Symbol]
14
+ # @return [Success]
15
+ # @overload success(payload)
16
+ # @param payload [Object]
17
+ # @return [Success]
18
+ # @overload success(code, payload)
19
+ # @param code [Symbol]
20
+ # @param payload [Object]
21
+ # @return [Success]
22
+ def success(*arguments)
23
+ case arguments
24
+ in [Symbol] | [Symbol, Object]
25
+ Success.new(*arguments)
26
+ in [Object]
27
+ Success.new(:ok, *arguments)
28
+ else
29
+ Success.new(:ok)
30
+ end
31
+ end
32
+
33
+ # @overload failure
34
+ # @return [Failure]
35
+ # @overload failure(code)
36
+ # @param code [Symbol]
37
+ # @return [Failure]
38
+ # @overload failure(details)
39
+ # @param details [Object]
40
+ # @return [Failure]
41
+ # @overload failure(code, details)
42
+ # @param code [Symbol]
43
+ # @param details [Object]
44
+ # @return [Failure]
45
+ # @overload failure(code, details, payload)
46
+ # @param code [Symbol]
47
+ # @param details [Object]
48
+ # @param payload [Object]
49
+ # @return [Failure]
50
+ def failure(*arguments)
51
+ case arguments
52
+ in [Symbol] | [Symbol, Object] | [Symbol, Object, Object]
53
+ Failure.new(*arguments)
54
+ else
55
+ Failure.new(:internal_server_error, *arguments)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -20,7 +20,7 @@ module Aux
20
20
 
21
21
  # Configure dependencies that come from the registry
22
22
  pluggable.dependencies.each do |dependency|
23
- instance.instance_variable_set("@#{dependency.pointer}", dependency.target)
23
+ instance.instance_variable_set(:"@#{dependency.pointer}", dependency.target)
24
24
 
25
25
  # The next lines are required to make resolved dependencies available
26
26
  # by its reader methods within customized initialization
@@ -28,7 +28,7 @@ module Aux
28
28
  # TODO: Discuss whether this is appropriate behaviour
29
29
  next if instance.respond_to?(dependency.pointer, true)
30
30
 
31
- define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
31
+ define_method(dependency.pointer) { instance_variable_get(:"@#{dependency.pointer}") }
32
32
  next unless dependency.private
33
33
 
34
34
  private(dependency.pointer)
@@ -37,7 +37,7 @@ module Aux
37
37
  # Configure dependencies that come from the customized initialization procedure
38
38
  dependencies_overrides = keyword_arguments.slice(*dependencies_keys)
39
39
  dependencies_overrides.each do |pointer, target|
40
- instance.instance_variable_set("@#{pointer}", target)
40
+ instance.instance_variable_set(:"@#{pointer}", target)
41
41
  end
42
42
 
43
43
  # Run the origin's initialization procedure if any other arguments given
@@ -52,24 +52,19 @@ module Aux
52
52
 
53
53
  # @param initialize [TrueClass, FalseClass]
54
54
  # @param memoize [TrueClass, FalseClass]
55
- # @param scope [TrueClass, Symbol, String, nil]
55
+ # @param scope [Symbol, String, TrueClass, nil]
56
56
  # @param as [Symbol, String, nil]
57
57
  def register(initialize: false, memoize: false, scope: true, as: nil)
58
- @_pluggable.register(
59
- initialization_required: initialize,
60
- memoization_required: memoize,
61
- namespace: scope,
62
- code: as
63
- )
58
+ @_pluggable.register(initialize, memoize, scope, as)
64
59
  end
65
60
 
66
61
  # @param code [Symbol, String]
67
- # @param initialization_block [Block]
62
+ # @param initialization_block [Proc, nil]
68
63
  # @param scope [TrueClass, Symbol, String, nil]
69
64
  # @param private [TrueClass, FalseClass]
70
65
  # @param as [Symbol, String, nil]
71
66
  def resolve(code, initialization_block = nil, scope: true, private: true, as: nil)
72
- @_pluggable.resolve(code, initialization_block, namespace: scope, private: private, as: as)
67
+ @_pluggable.resolve(code, scope, private, as, initialization_block)
73
68
  end
74
69
  end
75
70
  end
@@ -3,32 +3,26 @@
3
3
  module Aux
4
4
  module Pluggable
5
5
  # Describes the bridge between a pluggable class and the registry
6
+ # @!visibility private
6
7
  class Connector
7
8
  # @!attribute [r] dependencies
8
9
  # @return [Array<Dependency>]
9
10
  attr_reader :dependencies
10
11
 
11
12
  # @param subject [Class]
12
- # @param registry [Dry::Container]
13
+ # @param registry [Aux::Registry]
13
14
  def initialize(subject, registry)
14
15
  @subject = subject
15
16
  @registry = registry
16
17
  @dependencies = []
17
-
18
- configure
19
18
  end
20
19
 
21
- # @param initialization_required [Boolean] whether the subject requires initialization before use
22
- # @param memoization_required [Boolean] whether the subject should be memoized
23
- # @param namespace [Symbol] the namespace to register the subject
24
- # @param code [Symbol] an alternate name
25
- def register(initialization_required: nil, memoization_required: nil, namespace: nil, code: nil)
26
- configure(
27
- initialization_required: initialization_required,
28
- memoization_required: memoization_required,
29
- namespace: namespace,
30
- code: code
31
- )
20
+ # @param initialization_required [TrueClass, FalseClass] whether the subject requires initialization before use
21
+ # @param memoization_required [TrueClass, FalseClass] whether the subject should be memoized
22
+ # @param namespace [Symbol, String, TrueClass, nil] the namespace to register the subject
23
+ # @param code [Symbol, String, nil] an alternate name
24
+ def register(initialization_required, memoization_required, namespace, code)
25
+ configure(initialization_required, memoization_required, namespace, code)
32
26
 
33
27
  # Register the subject in the registry using the provided options
34
28
  @registry.register(@cipher, memoize: @memoization_required) do
@@ -37,41 +31,41 @@ module Aux
37
31
  end
38
32
 
39
33
  # @param code [Symbol, String] the name of the dependency
40
- # @param initialization_block [Proc] an optional block used to initialize the dependency
41
- # @param namespace [TrueClass, Symbol, String, nil] whether to resolve the dependency in the same namespace
34
+ # @param namespace [Symbol, String, TrueClass, nil] whether to resolve the dependency in the same namespace
42
35
  # @param private [TrueClass, FalseClass] whether to make the dependency private
43
- # @param as [Symbol] an internal alias name for the dependency
44
- # rubocop:disable Layout/LineLength, Metrics/AbcSize, Metrics/MethodLength
45
- def resolve(code, initialization_block = nil, namespace: true, private: true, as: nil)
46
- cipher = Utilities.dependency_cipher(@subject.name, scope: namespace, code: code)
47
- load_class(cipher) unless @registry.key?(cipher)
36
+ # @param as [Symbol, String, nil] an internal alias name for the dependency
37
+ # @param initialization_block [Proc, nil] an optional block used to initialize the dependency
38
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
39
+ def resolve(code, namespace, private, as, initialization_block = nil)
40
+ cipher = Utilities.dependency_cipher(@subject.name, namespace, code)
41
+ load_class(cipher)
48
42
 
49
- dependency = Dependency.new(@registry.resolve(cipher), initialization_block, pointer: as || code, private: private)
43
+ dependency = Dependency.new(@registry.resolve(cipher), as || code, private, initialization_block)
50
44
  @dependencies.push(dependency)
51
45
 
52
46
  if @initialization_required
53
47
  @subject.class_eval do
54
- define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
48
+ define_method(dependency.pointer) { instance_variable_get(:"@#{dependency.pointer}") }
55
49
  private(dependency.pointer) if dependency.private
56
50
  end
57
51
  else
58
- @subject.instance_variable_set("@#{dependency.pointer}", dependency.target)
52
+ @subject.instance_variable_set(:"@#{dependency.pointer}", dependency.target)
59
53
  @subject.singleton_class.class_eval do
60
- define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
54
+ define_method(dependency.pointer) { instance_variable_get(:"@#{dependency.pointer}") }
61
55
  private(dependency.pointer) if dependency.private
62
56
  end
63
57
  end
64
58
  end
65
- # rubocop:enable Layout/LineLength, Metrics/AbcSize, Metrics/MethodLength
59
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
66
60
 
67
61
  private
68
62
 
69
- # @param initialization_required [TrueClass, FalseClass, nil]
70
- # @param memoization_required [TrueClass, FalseClass, nil]
71
- # @param namespace [Symbol, String, nil]
63
+ # @param initialization_required [TrueClass, FalseClass]
64
+ # @param memoization_required [TrueClass, FalseClass]
65
+ # @param namespace [TrueClass, Symbol, String, nil]
72
66
  # @param code [Symbol, String, nil]
73
- def configure(initialization_required: nil, memoization_required: nil, namespace: nil, code: nil)
74
- @cipher = Utilities.dependency_cipher(@subject.name, scope: namespace, code: code)
67
+ def configure(initialization_required, memoization_required, namespace, code)
68
+ @cipher = Utilities.dependency_cipher(@subject.name, namespace, code)
75
69
  @initialization_required = initialization_required
76
70
  @memoization_required = memoization_required
77
71
  @namespace = namespace
@@ -3,6 +3,7 @@
3
3
  module Aux
4
4
  module Pluggable
5
5
  # Describes the dependency and its preferences
6
+ # @!visibility private
6
7
  class Dependency
7
8
  # @!attribute [r] target
8
9
  # @return [Object]
@@ -10,14 +11,14 @@ module Aux
10
11
  # @return [Symbol, String]
11
12
  # @!attribute [r] private
12
13
  # @return [Boolean]
13
- attr_reader :pointer, :target, :private
14
+ attr_reader :target, :pointer, :private
14
15
 
15
16
  # @param target [Object]
16
- # @param initialization_block [Proc, nil]
17
17
  # @param pointer [Symbol, String]
18
18
  # @param private [Boolean]
19
- def initialize(target, initialization_block = nil, pointer:, private:)
20
- @target = initialization_block ? initialization_block.call(target) : target
19
+ # @param initialization_block [Proc, nil]
20
+ def initialize(target, pointer, private, initialization_block = nil)
21
+ @target = initialization_block&.call(target) || target
21
22
  @pointer = pointer
22
23
  @private = private
23
24
  end
@@ -3,28 +3,29 @@
3
3
  module Aux
4
4
  module Pluggable
5
5
  # Random methods for internal usage
6
+ # @!visibility private
6
7
  module Utilities
7
8
  # First, we need to determine the appropriate namespace (also called the scope) in which to resolve something.
8
9
  # By default, we assume that the developers want to resolve a dependency from the same namespace as the
9
10
  # referencing class. Another approach is to allow developers to set the correct scope themselves.
10
11
  #
11
- # @param subject [ClassName]
12
- # @param scope [TrueClass, Symbol, String, nil]
13
- # @param code [TrueClass, Symbol, String, nil]
12
+ # @param subject [String]
13
+ # @param scope [Symbol, String, TrueClass, nil]
14
+ # @param code [Symbol, String, nil]
14
15
  # @return [String]
15
- def self.dependency_cipher(subject, scope: nil, code: nil)
16
+ def self.dependency_cipher(subject, scope, code)
16
17
  native_cipher = dependency_native_cipher(subject)
17
18
  native_cipher_partitions = native_cipher.rpartition('.')
18
- scope = scope == true ? native_cipher_partitions.first : scope
19
- code = code.nil? ? native_cipher_partitions.last : code
19
+ scope = native_cipher_partitions.first if scope == true
20
+ code = native_cipher_partitions.last if code.nil?
20
21
 
21
22
  [scope, code].reject { |part| part.nil? || part.empty? }.join('.')
22
23
  end
23
24
 
24
- # @param subject [ClassName]
25
+ # @param subject [String]
25
26
  # @return [String]
26
27
  def self.dependency_native_cipher(subject)
27
- subject.dup.gsub(/::/, '.').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
28
+ subject.dup.gsub('::', '.').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
28
29
  end
29
30
  end
30
31
  end
data/lib/aux/pluggable.rb CHANGED
@@ -4,11 +4,14 @@ require 'aux/pluggable/class_methods'
4
4
  require 'aux/pluggable/connector'
5
5
  require 'aux/pluggable/dependency'
6
6
  require 'aux/pluggable/utilities'
7
+ require 'aux/registry'
7
8
 
8
9
  module Aux
9
10
  # Describes interface that makes any class able to register itself as well as resolve dependencies
10
11
  # rubocop:disable Style/ClassVars
11
12
  module Pluggable
13
+ @@registry = Aux::Registry.new
14
+
12
15
  # Extends the including class with ClassMethods and initializes a new Connector instance
13
16
  # @param base [Class] the class that includes this module
14
17
  def self.included(base)
@@ -19,12 +22,19 @@ module Aux
19
22
  end
20
23
  end
21
24
 
22
- # @param registry [Dry::Container]
25
+ # @yield configure the module
26
+ # @yieldparam pluggable [Module<Aux::Pluggable>]
27
+ # @yieldparam registry [Aux::Registry]
28
+ def self.configure
29
+ yield(self, @@registry) if block_given?
30
+ end
31
+
32
+ # @param registry [Aux::Registry]
23
33
  def self.registry=(registry)
24
34
  @@registry = registry
25
35
  end
26
36
 
27
- # @return [Dry::Container]
37
+ # @return [Aux::Registry]
28
38
  def self.registry
29
39
  @@registry
30
40
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ class Registry
5
+ # Describes a registered dependency
6
+ # @!visibility private
7
+ class Entry
8
+ # @param constructor [Proc]
9
+ # @param memoization_required [TrueClass, FalseClass]
10
+ def initialize(constructor, memoization_required)
11
+ @constructor = constructor
12
+ @memoization_required = memoization_required
13
+
14
+ @mutex = Thread::Mutex.new
15
+ end
16
+
17
+ # @return [Object]
18
+ def call
19
+ return @constructor.call unless @memoization_required
20
+
21
+ @call ||= @mutex.synchronize do
22
+ @constructor.call
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/registry/entry'
4
+ require 'concurrent/map'
5
+
6
+ module Aux
7
+ # Registry for dependency injection
8
+ class Registry
9
+ def initialize
10
+ @prototypes = ::Concurrent::Map.new
11
+ end
12
+
13
+ # @param key [Symbol, String]
14
+ # @param memoize [TrueClass, FalseClass]
15
+ # @param constructor [Proc]
16
+ def register(key, memoize: false, &constructor)
17
+ @prototypes.put(key.to_s, Entry.new(constructor, memoize))
18
+ end
19
+
20
+ # @param key [Symbol, String]
21
+ # @return [Object]
22
+ def resolve(key)
23
+ @prototypes.fetch(key.to_s).call
24
+ end
25
+
26
+ # @param key [Symbol, String]
27
+ # @return [TrueClass, FalseClass]
28
+ def key?(key)
29
+ @prototypes.key?(key.to_s)
30
+ end
31
+ end
32
+ end
@@ -6,22 +6,22 @@ module Aux
6
6
  class Error
7
7
  # @!attribute [r] attribute
8
8
  # @return [Symbol]
9
- # @!attribute [r] type
10
- # @return [Symbol]
11
9
  # @!attribute [r] scope
12
10
  # @return [String]
11
+ # @!attribute [r] type
12
+ # @return [Symbol]
13
13
  # @!attribute [r] details
14
14
  # @return [Hash]
15
- attr_reader :attribute, :type, :scope, :details
15
+ attr_reader :attribute, :scope, :type, :details
16
16
 
17
17
  # @param attribute [Symbol]
18
- # @param type [Symbol]
19
18
  # @param scope [String]
19
+ # @param type [Symbol]
20
20
  # @param details [Hash]
21
- def initialize(attribute, type, scope, **details)
21
+ def initialize(attribute, scope, type, **details)
22
22
  @attribute = attribute
23
- @type = type
24
23
  @scope = scope
24
+ @type = type
25
25
  @details = details
26
26
  end
27
27
  end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Validations
5
+ # Describes methods that represent validation errors in simple structures pretty enough for the API response.
6
+ module ErrorHandler
7
+ private
8
+
9
+ DEFAULT_ERROR_SCOPE_PREFIX = 'errors'
10
+ GENERIC_ERROR_SCOPE = 'errors.generic'
11
+
12
+ # @overload format_errors(error_batch, scope)
13
+ # @param error_batch [Array]
14
+ # @param scope [Symbol, String, nil]
15
+ # @raise [ArgumentError]
16
+ # @return [Array<Hash>]
17
+ # @overload format_errors(error_collection, scope)
18
+ # @param error_collection [Errors]
19
+ # @param scope [Symbol, String, nil]
20
+ # @raise [ArgumentError]
21
+ # @return [Array<Hash>]
22
+ # @overload format_errors(error, scope)
23
+ # @param scope [Symbol, String, nil]
24
+ # @param error [Error]
25
+ # @raise [ArgumentError]
26
+ # @return [Hash]
27
+ def format_errors(payload, scope = nil)
28
+ case payload
29
+ when Array
30
+ format_error_batch(payload, scope)
31
+ when Errors
32
+ format_error_collection(payload, scope)
33
+ when Error
34
+ format_error(payload, scope)
35
+ else
36
+ raise ArgumentError
37
+ end
38
+ end
39
+
40
+ # @param batch [Array<Error, Errors>]
41
+ # @param scope [Symbol, String, nil]
42
+ # @return [Array<Hash>]
43
+ def format_error_batch(batch, scope = nil)
44
+ batch.flat_map { |element| format_errors(element, scope) }
45
+ end
46
+
47
+ # @param collection [Errors]
48
+ # @param scope [Symbol, String, nil]
49
+ def format_error_collection(collection, scope = nil)
50
+ return format_primary_error_collection(collection, scope || collection.scope) unless collection.attribute
51
+
52
+ format_secondary_error_collection(collection, "#{scope}.#{collection.attribute}")
53
+ end
54
+
55
+ # @param collection [Errors]
56
+ # @param scope [Symbol, String, nil]
57
+ # @return [Array<Hash>]
58
+ def format_primary_error_collection(collection, scope = nil)
59
+ collection.group_by_attribute.map do |attribute, entities|
60
+ {
61
+ kind: entities.all?(Error) ? 'collection' : 'batch',
62
+ attribute: attribute,
63
+ errors: format_errors(entities, scope)
64
+ }
65
+ end
66
+ end
67
+
68
+ # @param collection [Errors]
69
+ # @param scope [Symbol, String, nil]
70
+ # @return [Array<Hash>]
71
+ def format_secondary_error_collection(collection, scope = nil)
72
+ collection.group_by_attribute.flat_map do |_attribute, items|
73
+ format_errors(items.one? ? items.first : items, scope)
74
+ end
75
+ end
76
+
77
+ # @param error [Error]
78
+ # @param scope [Symbol, String, nil]
79
+ # @return [Hash]
80
+ def format_error(error, scope = nil)
81
+ {
82
+ type: error.type,
83
+ details: error.details,
84
+ message: format_message(error, scope)
85
+ }
86
+ end
87
+
88
+ # @param error [Error]
89
+ # @param scope [Symbol, String, nil]
90
+ # @return [String]
91
+ def format_message(error, scope = nil)
92
+ scope ? format_secondary_message(error, scope) : format_primary_message(error)
93
+ end
94
+
95
+ # @param error [Error]
96
+ # @return [String]
97
+ def format_primary_message(error)
98
+ I18n.translate(
99
+ error.type,
100
+ scope: "#{self.class::DEFAULT_ERROR_SCOPE_PREFIX}.#{error.scope}.#{error.attribute}",
101
+ default: proc { format_fallback_message(error) },
102
+ **error.details
103
+ )
104
+ end
105
+
106
+ # @param error [Error]
107
+ # @param scope [Symbol, String, nil]
108
+ # @return [String]
109
+ def format_secondary_message(error, scope)
110
+ I18n.translate(
111
+ error.type,
112
+ scope: "#{self.class::DEFAULT_ERROR_SCOPE_PREFIX}.#{scope}.#{error.attribute}",
113
+ default: proc { format_primary_message(error) },
114
+ **error.details
115
+ )
116
+ end
117
+
118
+ # @param error [Error]
119
+ # @return [String]
120
+ def format_fallback_message(error)
121
+ I18n.translate(error.type, scope: self.class::GENERIC_ERROR_SCOPE, default: error.type)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -1,11 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aux/validations/error'
4
- require 'aux/validations/errors_tree_presenter'
5
-
6
3
  module Aux
7
4
  module Validations
8
- # Describes a batch of errors that can be initialized using an attribute name or an object that should be validated
5
+ # Describes a collection of errors that can be initialized with an attribute name or an object to be validated
9
6
  class Errors
10
7
  # @!attribute [r] scope
11
8
  # @return [String, nil]
@@ -14,16 +11,13 @@ module Aux
14
11
  attr_reader :scope, :attribute
15
12
 
16
13
  # @overload initialize(subject)
17
- # @param base [Object]
18
- # @return [self]
14
+ # @param subject [Object]
19
15
  # @overload initialize(attribute, errors)
20
- # Use this approach to handle nested errors
21
16
  # @param attribute [Symbol]
22
- # @param errors [Aux::Validation::Errors]
23
- # @return [self]
24
- def initialize(base, errors = [])
25
- @scope = base.is_a?(Symbol) ? nil : base.class.name.underscore.tr('/', '.')
26
- @attribute = base.is_a?(Symbol) ? base : nil
17
+ # @param errors [Errors]
18
+ def initialize(subject, errors = [])
19
+ @scope = subject.is_a?(Symbol) ? nil : subject.class.name.underscore.tr('/', '.')
20
+ @attribute = subject.is_a?(Symbol) ? subject : nil
27
21
  @errors = errors
28
22
  end
29
23
 
@@ -34,35 +28,25 @@ module Aux
34
28
  # @raise [StandardError]
35
29
  # @overload add(attribute, errors)
36
30
  # @param attribute [Symbol]
37
- # @param errors [Array<Aux::Validations::Errors, Aux::Validations::Error>]
31
+ # @param errors [Array<Errors, Error>]
38
32
  # @raise [StandardError]
39
33
  # @overload add(attribute, errors)
40
34
  # @param attribute [Symbol]
41
- # @param errors [Aux::Validations::Errors]
35
+ # @param errors [Errors]
42
36
  # @raise [StandardError]
43
- def add(attribute, payload, **details)
44
- case payload
37
+ def add(attribute, subject, **details)
38
+ case subject
45
39
  when Symbol
46
- add_error(attribute, payload, **details)
40
+ add_error(attribute, subject, **details)
47
41
  when Array
48
- add_nested_error(attribute, payload)
49
- when Aux::Validations::Errors
50
- add_nested_error(attribute, [payload])
42
+ add_nested_error(attribute, subject)
43
+ when Errors
44
+ add_nested_error(attribute, [subject])
51
45
  else
52
- raise StandardError, :unsupported_type
46
+ raise(ArgumentError, "Unsupported argument given (#{subject.class})")
53
47
  end
54
48
  end
55
49
 
56
- # @return [Hash]
57
- def tree
58
- tree_presenter.render(self)
59
- end
60
-
61
- # @return [Hash]
62
- def group_by_attribute
63
- errors.group_by(&:attribute)
64
- end
65
-
66
50
  def clear
67
51
  self.errors = []
68
52
  end
@@ -72,25 +56,20 @@ module Aux
72
56
  errors.empty?
73
57
  end
74
58
 
59
+ # @param block [Proc]
75
60
  def map(&block)
76
61
  errors.map(&block)
77
62
  end
78
63
 
79
- # @return [Array<Aux::Validations::Error>]
80
- def all
81
- errors.map do |error|
82
- if error.is_a?(Array)
83
- error.map { |e| e.respond_to?(:all) ? e.all : e }
84
- else
85
- error.respond_to?(:all) ? error.all : error
86
- end
87
- end.flatten
64
+ # @return [Hash<Symbol, Array>]
65
+ def group_by_attribute
66
+ errors.group_by(&:attribute)
88
67
  end
89
68
 
90
69
  private
91
70
 
92
71
  # @!attribute [rw] errors
93
- # @return [Array<Aux::Validations::Errors, Aux::Validations::Error>]
72
+ # @return [Array<Error, Errors>]
94
73
  attr_accessor :errors
95
74
 
96
75
  # @param attribute [Symbol]
@@ -101,7 +80,7 @@ module Aux
101
80
  end
102
81
 
103
82
  # @param attribute [Symbol]
104
- # @param nested_errors [Aux::Validations::Errors]
83
+ # @param nested_errors [Errors]
105
84
  def add_nested_error(attribute, nested_errors)
106
85
  errors.push(build_nested_error(attribute, nested_errors))
107
86
  end
@@ -109,26 +88,21 @@ module Aux
109
88
  # @param attribute [Symbol]
110
89
  # @param type [Symbol]
111
90
  # @param details [Hash]
112
- # @return [Aux::Validations::Error]
91
+ # @return [Error]
113
92
  def build_error(attribute, type, **details)
114
- errors_factory.new(attribute, type, scope, **details)
93
+ factory.new(attribute, scope, type, **details)
115
94
  end
116
95
 
117
96
  # @param attribute [Symbol] 3
118
- # @param errors [Aux::Validations::Errors]
119
- # @return [Aux::Validations::Errors]
97
+ # @param errors [Errors]
98
+ # @return [Errors]
120
99
  def build_nested_error(attribute, errors)
121
100
  self.class.new(attribute, errors)
122
101
  end
123
102
 
124
- # @return [Class<Aux::Validations::ErrorsTreePresenter>]
125
- def tree_presenter
126
- Aux::Validations::ErrorsTreePresenter
127
- end
128
-
129
- # @return [Class<Aux::Validations::Error>]
130
- def errors_factory
131
- Aux::Validations::Error
103
+ # @return [Class<Error>]
104
+ def factory
105
+ Error
132
106
  end
133
107
  end
134
108
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_model'
3
4
  require 'active_support'
4
- require 'active_model/validations/validates'
5
+ require 'aux/validations/error'
5
6
  require 'aux/validations/errors'
7
+ require 'aux/validations/error_handler'
6
8
 
7
9
  module Aux
8
10
  # Describes enhancement of {::ActiveModel::Validations}
data/lib/aux/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aux
4
- VERSION = '0.1.1'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/aux.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aux/carriers'
3
4
  require 'aux/pluggable'
4
5
  require 'aux/validations'
5
6
 
metadata CHANGED
@@ -1,109 +1,87 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeny Boyko
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-18 00:00:00.000000000 Z
11
+ date: 2024-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activemodel
14
+ name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '6.1'
20
- - - "<"
19
+ version: '1.2'
20
+ - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: '8'
22
+ version: 1.2.3
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
27
+ - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '6.1'
30
- - - "<"
29
+ version: '1.2'
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '8'
32
+ version: 1.2.3
33
33
  - !ruby/object:Gem::Dependency
34
- name: dry-container
34
+ name: activemodel
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 0.9.0
40
- - - "<="
39
+ version: '6.1'
40
+ - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '0.11'
42
+ version: '8'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 0.9.0
50
- - - "<="
51
- - !ruby/object:Gem::Version
52
- version: '0.11'
53
- - !ruby/object:Gem::Dependency
54
- name: zeitwerk
55
- requirement: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - "~>"
58
- - !ruby/object:Gem::Version
59
- version: '2.5'
60
- type: :development
61
- prerelease: false
62
- version_requirements: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - "~>"
65
- - !ruby/object:Gem::Version
66
- version: '2.5'
67
- - !ruby/object:Gem::Dependency
68
- name: rubocop
69
- requirement: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: 1.36.0
74
- type: :development
75
- prerelease: false
76
- version_requirements: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - "~>"
49
+ version: '6.1'
50
+ - - "<"
79
51
  - !ruby/object:Gem::Version
80
- version: 1.36.0
81
- description:
52
+ version: '8'
53
+ description:
82
54
  email:
83
55
  - eboyko@eboyko.ru
84
56
  executables: []
85
57
  extensions: []
86
58
  extra_rdoc_files: []
87
59
  files:
60
+ - MIT-LICENSE
88
61
  - README.md
89
62
  - aux.gemspec
90
63
  - lib/aux.rb
64
+ - lib/aux/carriers.rb
65
+ - lib/aux/carriers/failure.rb
66
+ - lib/aux/carriers/success.rb
91
67
  - lib/aux/pluggable.rb
92
68
  - lib/aux/pluggable/class_methods.rb
93
69
  - lib/aux/pluggable/connector.rb
94
70
  - lib/aux/pluggable/dependency.rb
95
71
  - lib/aux/pluggable/utilities.rb
72
+ - lib/aux/registry.rb
73
+ - lib/aux/registry/entry.rb
96
74
  - lib/aux/validations.rb
97
75
  - lib/aux/validations/error.rb
76
+ - lib/aux/validations/error_handler.rb
98
77
  - lib/aux/validations/errors.rb
99
- - lib/aux/validations/errors_tree_presenter.rb
100
78
  - lib/aux/version.rb
101
79
  homepage: https://github.com/eboyko/aux
102
80
  licenses:
103
81
  - MIT
104
82
  metadata:
105
83
  rubygems_mfa_required: 'true'
106
- post_install_message:
84
+ post_install_message:
107
85
  rdoc_options: []
108
86
  require_paths:
109
87
  - lib
@@ -119,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
97
  version: '0'
120
98
  requirements: []
121
99
  rubygems_version: 3.1.2
122
- signing_key:
100
+ signing_key:
123
101
  specification_version: 4
124
102
  summary: Supplementary tools for more effective development
125
103
  test_files: []
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Aux
4
- module Validations
5
- # Describes tree structure presentation scenario of errors
6
- class ErrorsTreePresenter
7
- class << self
8
- # @return [Array, Hash, Aux::Validations::Error]
9
- def render(payload)
10
- case payload
11
- when Array
12
- process_array(payload)
13
- when Aux::Validations::Errors
14
- render_collection(payload)
15
- when Aux::Validations::Error
16
- payload
17
- end
18
- end
19
-
20
- private
21
-
22
- # @param errors [Array]
23
- # @return [Array]
24
- def process_array(errors)
25
- errors.map { |item| render(item) }.flatten
26
- end
27
-
28
- # @param errors [Aux::Validations::Errors]
29
- # @return [Array, Hash]
30
- def render_collection(errors)
31
- errors.attribute ? process_attribute_errors(errors) : process_errors(errors)
32
- end
33
-
34
- # @param errors [Aux::Validations::Errors]
35
- # @return [Hash]
36
- def process_errors(errors)
37
- errors.group_by_attribute.transform_values do |items|
38
- render(items.one? ? items.first : items)
39
- end
40
- end
41
-
42
- # @param errors [Aux::Validations::Errors]
43
- # @return [Array]
44
- def process_attribute_errors(errors)
45
- errors.group_by_attribute.map { |_attribute, items| render(items) }.flatten
46
- end
47
- end
48
- end
49
- end
50
- end