aux 0.0.2 → 0.0.4

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: c812c16ef07f3e6e43157d04b2e22bd2b72460433e344a89bbb36568ea082f54
4
- data.tar.gz: b5205ffd09fde764555e19f6772f05f19f81453a8ceea73150cdf042132e5237
3
+ metadata.gz: acf9da895d77d8ab39cc92765e586e69e0300e15cedc201fdd18a30a02221d4c
4
+ data.tar.gz: c6aa3892d3a1691f2e0192a3094c33838a5e5b40121538b06862e1c7491c7190
5
5
  SHA512:
6
- metadata.gz: 7cac5ece74ce3d842c856140f3340d7543bf195408e834b7505e18e18418023506954f22ddb3de0c543252162a61f4c22180539bcd238bb4f6114c6f6e0a57d1
7
- data.tar.gz: 0f5350206dde4094116e1706c26e76017cd2864eaa67aa13f03eda8d3a959ce96aead7dc4cdb7ab7f9570a2946f337daf0942dfb2cfd76ff8c7e73ff76b616a3
6
+ metadata.gz: c10a70b5b35a62161e4f6c48362fda7bc51e08a046c638ac3d8a8311fac53648fc1bdcd526e0c4d7302e1c9ce36c80d97f845dc0fa5d32f9e8ebc1332aad408d
7
+ data.tar.gz: ae97078e7e7cc0d627711a1c147befecadcfa70e057376378402d59a8dda844ad63f4596a10fd1ee26c594f0db8f8189d0bfdb6e9a3962b557899747503b3c35
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,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Pluggable
5
+ # Describes methods that would be inherited by pluggable classes
6
+ module ClassMethods
7
+ # @param dependencies [Hash{Symbol => Object}]
8
+ def new(**dependencies)
9
+ instance = allocate
10
+
11
+ # Configure dependencies that come from the registry
12
+ @_dependencies.each do |name, dependency|
13
+ instance.instance_variable_set("@#{name}", dependency)
14
+ end
15
+
16
+ # Configure dependencies that come from the customized initialization procedure
17
+ dependencies.each do |name, dependency|
18
+ instance.instance_variable_set("@#{name}", dependency) if @_dependencies.include?(name)
19
+ end
20
+
21
+ # Run the rest of customized initialization procedure
22
+ instance.send(:initialize, **dependencies)
23
+
24
+ instance
25
+ end
26
+
27
+ private
28
+
29
+ # @param initialize [TrueClass, FalseClass]
30
+ # @param memoize [TrueClass, FalseClass]
31
+ # @param scope [TrueClass, Symbol, String, nil]
32
+ # @param as [Symbol, String, nil]
33
+ def register(initialize: false, memoize: false, scope: true, as: nil)
34
+ dependency_cipher = Utilities.dependency_cipher(name, scope: scope, code: as)
35
+ @_registry.register(dependency_cipher, memoize: memoize) { initialize ? new : self }
36
+ end
37
+
38
+ # @param code [Symbol, String]
39
+ # @param initialization_block [Proc, nil]
40
+ # @param scope [TrueClass, Symbol, String, nil]
41
+ # @param as [Symbol, String]
42
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
43
+ def resolve(code, initialization_block = nil, scope: true, as: nil)
44
+ dependency_cipher = Utilities.dependency_cipher(name, scope: scope, code: code)
45
+ dependency_alias = as || code
46
+
47
+ # The process of preloading classes is a bit messy. Therefore, why we wrap the dependency in a Proc.
48
+ # This allows us not to worry about the absence of the referenced class in the registry during assembly.
49
+ # Using an initialization block can be convenient in some cases, for example, to resolve some
50
+ # of the global settings without accessing the whole thing.
51
+ @_dependencies[dependency_alias] =
52
+ if initialization_block
53
+ -> { initialization_block.call(@_registry.resolve(dependency_cipher)) }
54
+ else
55
+ -> { @_registry.resolve(dependency_cipher) }
56
+ end
57
+
58
+ # The final step is to declare instance variables and methods to access them.
59
+ instance_eval do
60
+ define_method(dependency_alias) do
61
+ if instance_variable_get("@#{dependency_alias}").is_a?(Proc)
62
+ instance_variable_get("@#{dependency_alias}").call
63
+ else
64
+ instance_variable_get("@#{dependency_alias}")
65
+ end
66
+ end
67
+
68
+ private dependency_alias
69
+ end
70
+
71
+ singleton_class.class_eval do
72
+ define_method(dependency_alias) { @_dependencies[dependency_alias].call }
73
+ private dependency_alias
74
+ end
75
+ end
76
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
77
+ end
78
+ end
79
+ 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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Pluggable
5
+ # Random methods for internal usage
6
+ module Utilities
7
+ # First, we need to determine the appropriate namespace (also called the scope) in which to resolve something.
8
+ # By default, we assume that the developers want to resolve a dependency from the same namespace as the
9
+ # referencing class. Another approach is to allow developers to set the correct scope themselves.
10
+ #
11
+ # @param class_name [Class]
12
+ # @param scope [TrueClass, Symbol, String, nil]
13
+ # @param code [TrueClass, Symbol, String, nil]
14
+ # @return [String]
15
+ def self.dependency_cipher(class_name, scope: nil, code: nil)
16
+ native_cipher = dependency_native_cipher(class_name)
17
+ native_cipher_partitions = native_cipher.rpartition('.')
18
+ scope = scope.is_a?(TrueClass) ? native_cipher_partitions.first : scope
19
+ code = code.nil? ? native_cipher_partitions.last : code
20
+
21
+ [scope, code].reject { |part| part.nil? || part.empty? }.join('.')
22
+ end
23
+
24
+ # @param class_name [Class]
25
+ # @return [String]
26
+ def self.dependency_native_cipher(class_name)
27
+ class_name.dup.gsub(/::/, '.').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aux/pluggable/class_methods'
4
+ require 'aux/pluggable/utilities'
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,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.4'
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.4
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-03-05 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/pluggable/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