aux 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|