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 +4 -4
- data/aux.gemspec +22 -0
- data/lib/aux/pluggable/class_methods.rb +96 -0
- data/lib/aux/pluggable/registration.rb +25 -0
- data/lib/aux/pluggable.rb +29 -0
- data/lib/aux/utilities.rb +12 -0
- data/lib/aux/validations/error.rb +29 -0
- data/lib/aux/validations/errors.rb +135 -0
- data/lib/aux/validations/errors_tree_presenter.rb +50 -0
- data/lib/aux/validations.rb +16 -0
- data/lib/aux/version.rb +5 -0
- data/lib/aux.rb +7 -0
- metadata +31 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4131935771dd658e83d739479ccd2cac86caf8ef96162328807d73cad1cc741
|
4
|
+
data.tar.gz: 388c65d71c5902f843a4537cc6f75cce9af412403920632a03e9a27397561b2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/aux/version.rb
ADDED
data/lib/aux.rb
ADDED
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.
|
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:
|
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:
|
28
|
+
name: dry-container
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
type: :
|
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:
|
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
|