aux 0.0.2 → 0.0.3

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: c812c16ef07f3e6e43157d04b2e22bd2b72460433e344a89bbb36568ea082f54
4
- data.tar.gz: b5205ffd09fde764555e19f6772f05f19f81453a8ceea73150cdf042132e5237
3
+ metadata.gz: e4131935771dd658e83d739479ccd2cac86caf8ef96162328807d73cad1cc741
4
+ data.tar.gz: 388c65d71c5902f843a4537cc6f75cce9af412403920632a03e9a27397561b2f
5
5
  SHA512:
6
- metadata.gz: 7cac5ece74ce3d842c856140f3340d7543bf195408e834b7505e18e18418023506954f22ddb3de0c543252162a61f4c22180539bcd238bb4f6114c6f6e0a57d1
7
- data.tar.gz: 0f5350206dde4094116e1706c26e76017cd2864eaa67aa13f03eda8d3a959ce96aead7dc4cdb7ab7f9570a2946f337daf0942dfb2cfd76ff8c7e73ff76b616a3
6
+ metadata.gz: 3812e0f949dfd058abdc242d086e0b87487d275b0fd2443f871022e11b5ccef21decabf361fef3efd39db9303896694e17a8519e2d0857155919585a2ab92e77
7
+ data.tar.gz: cdbc3645ac99b5e5a42f26a2a0cf268fa03c4f6a108cc4f33c1652354d58a27a8f06ba28a3f327fe557a732dce0fc14a8613127794260182459fe0a3283d8cd8
data/aux.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ require_relative 'lib/aux/version'
2
+
3
+ Gem::Specification.new do |specification|
4
+ specification.name = 'aux'
5
+ specification.version = Aux::VERSION
6
+ specification.summary = 'Supplementary tools for more effective development'
7
+ specification.files = Dir['LICENSE', 'README.md', 'aux.gemspec', 'lib/**/*']
8
+
9
+ specification.authors = ['Evgeny Boyko']
10
+ specification.email = ['eboyko@eboyko.ru']
11
+ specification.homepage = 'https://github.com/eboyko/aux'
12
+
13
+ specification.add_dependency 'activemodel', '~> 6.1.6'
14
+
15
+ specification.add_development_dependency 'dry-container', '~> 0.11.0'
16
+ specification.add_development_dependency 'rubocop', '~> 1.36.0'
17
+
18
+ specification.required_ruby_version = '>= 2.7.1'
19
+ specification.metadata['rubygems_mfa_required'] = 'true'
20
+
21
+ specification.license = 'MIT'
22
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/utilities'
4
+
5
+ module Aux
6
+ module Pluggable
7
+ # Describes methods that would be inherited by pluggable classes
8
+ module ClassMethods
9
+ # @param dependencies [Hash{Symbol => Object}]
10
+ # @return [self]
11
+ def new(**dependencies)
12
+ instance = allocate
13
+
14
+ # Configure dependencies that come from the registry
15
+ @_dependencies.each do |name, dependency|
16
+ instance.instance_variable_set("@#{name}", dependency)
17
+ end
18
+
19
+ # Configure dependencies that come from the customized initialization procedure
20
+ dependencies.each do |name, dependency|
21
+ instance.instance_variable_set("@#{name}", dependency)
22
+ end
23
+
24
+ instance
25
+ end
26
+
27
+ private
28
+
29
+ # @param initialize [TrueClass, FalseClass]
30
+ # @param memoize [TrueClass, FalseClass]
31
+ # @param as [Symbol, String, nil]
32
+ def register(initialize: false, memoize: false, as: nil)
33
+ @_registration = Registration.new(as || Aux::Utilities.underscore(name), initialize, memoize)
34
+
35
+ @_registry.register(@_registration.name, memoize: @_registration.memoization_required) do
36
+ @_registration.initialization_required ? new : self
37
+ end
38
+ end
39
+
40
+ # @param code [Symbol, String]
41
+ # @param initialization_block [Proc, nil]
42
+ # @param scope [TrueClass, Symbol, String]
43
+ # @param as [Symbol, String]
44
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
45
+ def resolve(code, initialization_block = nil, scope: true, as: nil)
46
+ # First, we need to determine the appropriate namespace (also called the scope) in which to resolve something.
47
+ # By default, we assume that the developers want to resolve a dependency from the same namespace as the
48
+ # referencing class. Another approach is to allow developers to set the correct scope themselves.
49
+ scope =
50
+ case scope
51
+ when TrueClass
52
+ Aux::Utilities.underscore(name).rpartition('.').first
53
+ when Symbol, String
54
+ scope
55
+ end
56
+
57
+ # The process of preloading classes is a bit messy. Therefore, why we wrap the dependency in a Proc.
58
+ # This allows us not to worry about the absence of the referenced class in the registry during assembly.
59
+ # Using an initialization block can be convenient in some cases, for example, to resolve some
60
+ # of the global settings without accessing the whole thing.
61
+ dependency_cipher = [scope, code].reject { |part| part.nil? || part.empty? }.join('.')
62
+ dependency =
63
+ if initialization_block
64
+ -> { initialization_block.call(@_registry.resolve(dependency_cipher)) }
65
+ else
66
+ -> { @_registry.resolve(dependency_cipher) }
67
+ end
68
+
69
+ # The final step is to declare instance variables and methods to access them.
70
+ dependency_alias = as || code
71
+
72
+ if defined?(@_registration) && @_registration&.initialization_required
73
+ @_dependencies[dependency_alias] = dependency
74
+ end
75
+
76
+ instance_eval do
77
+ define_method(dependency_alias) do
78
+ if instance_variable_get("@#{dependency_alias}").is_a?(Proc)
79
+ instance_variable_set("@#{dependency_alias}", instance_variable_get("@#{dependency_alias}").call)
80
+ else
81
+ instance_variable_get("@#{dependency_alias}")
82
+ end
83
+ end
84
+
85
+ private dependency_alias
86
+ end
87
+
88
+ singleton_class.class_eval do
89
+ define_method(dependency_alias) { dependency.call }
90
+ private dependency_alias
91
+ end
92
+ end
93
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Pluggable
5
+ # Describes a wrapper for registry data
6
+ class Registration
7
+ # @!attribute [r] name
8
+ # @return [Symbol, String]
9
+ # @!attribute [r] initialization_required
10
+ # @return [TrueClass, FalseClass]
11
+ # @!attribute [r] memoization_required
12
+ # @return [TrueClass, FalseClass]
13
+ attr_reader :name, :initialization_required, :memoization_required
14
+
15
+ # @param name [Symbol, String]
16
+ # @param initialization_required [TrueClass, FalseClass]
17
+ # @param memoization_required [TrueClass, FalseClass]
18
+ def initialize(name, initialization_required, memoization_required)
19
+ @name = name
20
+ @initialization_required = initialization_required
21
+ @memoization_required = memoization_required
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/pluggable/class_methods'
4
+ require 'aux/pluggable/registration'
5
+
6
+ module Aux
7
+ # Describes interface that makes any class able to register itself as well as resolve dependencies
8
+ module Pluggable
9
+ # @param base [Class]
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+
13
+ base.class_eval do
14
+ @_registry = @@registry
15
+ @_dependencies = {}
16
+ end
17
+ end
18
+
19
+ # @param registry [Dry::Container]
20
+ def self.registry=(registry)
21
+ @@registry = registry
22
+ end
23
+
24
+ # @return [Dry::Container]
25
+ def self.registry
26
+ @@registry
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ # Random methods for internal usage
5
+ module Utilities
6
+ # @param class_name [String]
7
+ # @return [String]
8
+ def self.underscore(class_name)
9
+ class_name.dup.gsub(/::/, '.').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Validations
5
+ # Describes a single validation error
6
+ class Error
7
+ # @!attribute [r] attribute
8
+ # @return [Symbol]
9
+ # @!attribute [r] type
10
+ # @return [Symbol]
11
+ # @!attribute [r] scope
12
+ # @return [String]
13
+ # @!attribute [r] details
14
+ # @return [Hash]
15
+ attr_reader :attribute, :type, :scope, :details
16
+
17
+ # @param attribute [Symbol]
18
+ # @param type [Symbol]
19
+ # @param scope [String]
20
+ # @param details [Hash]
21
+ def initialize(attribute, type, scope, **details)
22
+ @attribute = attribute
23
+ @type = type
24
+ @scope = scope
25
+ @details = details
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/validations/error'
4
+ require 'aux/validations/errors_tree_presenter'
5
+
6
+ module Aux
7
+ module Validations
8
+ # Describes a batch of errors that can be initialized using an attribute name or an object that should be validated
9
+ class Errors
10
+ # @!attribute [r] scope
11
+ # @return [String, nil]
12
+ # @!attribute [r] attribute
13
+ # @return [Symbol, nil]
14
+ attr_reader :scope, :attribute
15
+
16
+ # @overload initialize(subject)
17
+ # @param base [Object]
18
+ # @return [self]
19
+ # @overload initialize(attribute, errors)
20
+ # Use this approach to handle nested errors
21
+ # @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
27
+ @errors = errors
28
+ end
29
+
30
+ # @overload add(attribute, type, **details)
31
+ # @param attribute [Symbol]
32
+ # @param type [Symbol]
33
+ # @param details [Hash]
34
+ # @raise [StandardError]
35
+ # @overload add(attribute, errors)
36
+ # @param attribute [Symbol]
37
+ # @param errors [Array<Aux::Validations::Errors, Aux::Validations::Error>]
38
+ # @raise [StandardError]
39
+ # @overload add(attribute, errors)
40
+ # @param attribute [Symbol]
41
+ # @param errors [Aux::Validations::Errors]
42
+ # @raise [StandardError]
43
+ def add(attribute, payload, **details)
44
+ case payload
45
+ when Symbol
46
+ add_error(attribute, payload, **details)
47
+ when Array
48
+ add_nested_error(attribute, payload)
49
+ when Aux::Validations::Errors
50
+ add_nested_error(attribute, [payload])
51
+ else
52
+ raise StandardError, :unsupported_type
53
+ end
54
+ end
55
+
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
+ def clear
67
+ self.errors = []
68
+ end
69
+
70
+ # @return [Boolean]
71
+ def empty?
72
+ errors.empty?
73
+ end
74
+
75
+ def map(&block)
76
+ errors.map(&block)
77
+ end
78
+
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
88
+ end
89
+
90
+ private
91
+
92
+ # @!attribute [rw] errors
93
+ # @return [Array<Aux::Validations::Errors, Aux::Validations::Error>]
94
+ attr_accessor :errors
95
+
96
+ # @param attribute [Symbol]
97
+ # @param type [Symbol]
98
+ # @param details [Hash]
99
+ def add_error(attribute, type, **details)
100
+ errors.push(build_error(attribute, type, **details))
101
+ end
102
+
103
+ # @param attribute [Symbol]
104
+ # @param nested_errors [Aux::Validations::Errors]
105
+ def add_nested_error(attribute, nested_errors)
106
+ errors.push(build_nested_error(attribute, nested_errors))
107
+ end
108
+
109
+ # @param attribute [Symbol]
110
+ # @param type [Symbol]
111
+ # @param details [Hash]
112
+ # @return [Aux::Validations::Error]
113
+ def build_error(attribute, type, **details)
114
+ errors_factory.new(attribute, type, scope, **details)
115
+ end
116
+
117
+ # @param attribute [Symbol] 3
118
+ # @param errors [Aux::Validations::Errors]
119
+ # @return [Aux::Validations::Errors]
120
+ def build_nested_error(attribute, errors)
121
+ self.class.new(attribute, errors)
122
+ end
123
+
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
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,50 @@
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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/validations/errors'
4
+
5
+ module Aux
6
+ # Describes enhancement of {ActiveModel::Validations}
7
+ module Validations
8
+ extend ActiveSupport::Concern
9
+ include ActiveModel::Validations
10
+
11
+ # @return [Aux::Validations::Errors]
12
+ def errors
13
+ @errors ||= Aux::Validations::Errors.new(self)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ VERSION = '0.0.3'
5
+ end
data/lib/aux.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/pluggable'
4
+ require 'aux/validations'
5
+
6
+ # Define module that includes auxiliaries
7
+ module Aux; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeny Boyko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-12 00:00:00.000000000 Z
11
+ date: 2023-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -25,19 +25,33 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 6.1.6
27
27
  - !ruby/object:Gem::Dependency
28
- name: activesupport
28
+ name: dry-container
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 6.1.6
34
- type: :runtime
33
+ version: 0.11.0
34
+ type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 6.1.6
40
+ version: 0.11.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.36.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.36.0
41
55
  description:
42
56
  email:
43
57
  - eboyko@eboyko.ru
@@ -46,6 +60,17 @@ extensions: []
46
60
  extra_rdoc_files: []
47
61
  files:
48
62
  - README.md
63
+ - aux.gemspec
64
+ - lib/aux.rb
65
+ - lib/aux/pluggable.rb
66
+ - lib/aux/pluggable/class_methods.rb
67
+ - lib/aux/pluggable/registration.rb
68
+ - lib/aux/utilities.rb
69
+ - lib/aux/validations.rb
70
+ - lib/aux/validations/error.rb
71
+ - lib/aux/validations/errors.rb
72
+ - lib/aux/validations/errors_tree_presenter.rb
73
+ - lib/aux/version.rb
49
74
  homepage: https://github.com/eboyko/aux
50
75
  licenses:
51
76
  - MIT