aux 0.0.9 → 0.1.0

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: f14a4137dfae54717b5c16ff83449ee58daabf5e1be565b777a9f82d297769d4
4
- data.tar.gz: ee6540f4deafd067569aeb755789a533f20e95298639692f0c812289bd3a2366
3
+ metadata.gz: f7ab27a48b02884baff3dedd1a770d02a71be57471b40aaabfd1213068dfd317
4
+ data.tar.gz: 1e38945b37770dc63d2280c1b000cff18b96d06b6544430480c815ab571213ac
5
5
  SHA512:
6
- metadata.gz: f30194532d6aa7016d3c8706c64a0c3c00375a786e628b95df8903bc1419ca3be99321155a15304c3864bd73c4722bc0e0f1f8403cf73187c1eeee4add48c1be
7
- data.tar.gz: 6f1474fcee6e6c56be8c0a59b7833b0deffca0a926702b240ecb3ef2534c2951d0b81bf5fe266d194764baefea6f8f415815660530f03f3ecfdc8ccaf6f9f947
6
+ metadata.gz: 05c2d14e99306c482ca584ca330e0808e653bab12082c5726dfd8495370f1109646afa0a7985c948d180715ce4aa074d4c0eb9cb8f58d409dab30305fb2fcdc3
7
+ data.tar.gz: 116352323a5886ac2e2c8eeafe0a55687bd5e6e37d29e4eb66b4b4c76eb7ad5e7ba5eefbdaec0a7c25cc6afe77318713cef01fcab95aeee957dbc3726b15c986
data/aux.gemspec CHANGED
@@ -11,8 +11,9 @@ Gem::Specification.new do |specification|
11
11
  specification.homepage = 'https://github.com/eboyko/aux'
12
12
 
13
13
  specification.add_dependency 'activemodel', '>= 6.1', '< 8'
14
+ specification.add_dependency 'dry-container', '>= 0.9.0', '<= 0.11'
14
15
 
15
- specification.add_development_dependency 'dry-container', '~> 0.11.0'
16
+ specification.add_development_dependency 'zeitwerk', '~> 2.5'
16
17
  specification.add_development_dependency 'rubocop', '~> 1.36.0'
17
18
 
18
19
  specification.required_ruby_version = '>= 2.7.1'
@@ -4,73 +4,73 @@ module Aux
4
4
  module Pluggable
5
5
  # Describes methods that would be inherited by pluggable classes
6
6
  module ClassMethods
7
- # @param dependencies [Hash{Symbol => Object}]
8
- def new(**dependencies)
7
+ # Create a new instance of the class that includes the Pluggable module
8
+ # @param positional_arguments [Array<Object>]
9
+ # @param keyword_arguments [Hash{Symbol => Object}]
10
+ # @return [Object]
11
+ #
12
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
13
+ def new(*positional_arguments, **keyword_arguments)
9
14
  instance = allocate
10
15
 
16
+ # Configure some local variables for shorter calls
17
+ # @type [Aux::Pluggable::Connector]
18
+ pluggable = instance.class.instance_variable_get(:@_pluggable)
19
+ dependencies_keys = pluggable.dependencies.map(&:pointer)
20
+
11
21
  # Configure dependencies that come from the registry
12
- @_dependencies.each do |name, dependency|
13
- instance.instance_variable_set("@#{name}", dependency)
22
+ pluggable.dependencies.each do |dependency|
23
+ instance.instance_variable_set("@#{dependency.pointer}", dependency.target)
24
+
25
+ # The next lines are required to make resolved dependencies available
26
+ # by its reader methods within customized initialization
27
+ #
28
+ # TODO: Discuss whether this is appropriate behaviour
29
+ next if instance.respond_to?(dependency.pointer, true)
30
+
31
+ define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
32
+ next unless dependency.private
33
+
34
+ private(dependency.pointer)
14
35
  end
15
36
 
16
37
  # 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)
38
+ dependencies_overrides = keyword_arguments.slice(*dependencies_keys)
39
+ dependencies_overrides.each do |pointer, target|
40
+ instance.instance_variable_set("@#{pointer}", target)
19
41
  end
20
42
 
43
+ # Run the origin's initialization procedure if any other arguments given
44
+ additional_keyword_arguments = keyword_arguments.reject { |key, _value| dependencies_keys.include?(key) }
45
+
46
+ # TODO: Consider checks like positional_arguments.any? or additional_keyword_arguments.any?
47
+ instance.send(:initialize, *positional_arguments, **additional_keyword_arguments)
48
+
21
49
  instance
22
50
  end
23
-
24
- private
51
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
25
52
 
26
53
  # @param initialize [TrueClass, FalseClass]
27
54
  # @param memoize [TrueClass, FalseClass]
28
55
  # @param scope [TrueClass, Symbol, String, nil]
29
56
  # @param as [Symbol, String, nil]
30
57
  def register(initialize: false, memoize: false, scope: true, as: nil)
31
- dependency_cipher = Utilities.dependency_cipher(name, scope: scope, code: as)
32
- @_registry.register(dependency_cipher, memoize: memoize) { initialize ? new : self }
58
+ @_pluggable.register(
59
+ initialization_required: initialize,
60
+ memoization_required: memoize,
61
+ namespace: scope,
62
+ code: as
63
+ )
33
64
  end
34
65
 
35
66
  # @param code [Symbol, String]
36
- # @param initialization_block [Proc, nil]
67
+ # @param initialization_block [Block]
37
68
  # @param scope [TrueClass, Symbol, String, nil]
38
- # @param as [Symbol, String]
39
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
40
- def resolve(code, initialization_block = nil, scope: true, as: nil)
41
- dependency_cipher = Utilities.dependency_cipher(name, scope: scope, code: code)
42
- dependency_alias = as || code
43
-
44
- # The process of preloading classes is a bit messy. Therefore, why we wrap the dependency in a Proc.
45
- # This allows us not to worry about the absence of the referenced class in the registry during assembly.
46
- # Using an initialization block can be convenient in some cases, for example, to resolve some
47
- # of the global settings without accessing the whole thing.
48
- @_dependencies[dependency_alias] =
49
- if initialization_block
50
- -> { initialization_block.call(@_registry.resolve(dependency_cipher)) }
51
- else
52
- -> { @_registry.resolve(dependency_cipher) }
53
- end
54
-
55
- # The final step is to declare instance variables and methods to access them.
56
- instance_eval do
57
- define_method(dependency_alias) do
58
- if instance_variable_get("@#{dependency_alias}").is_a?(Proc)
59
- instance_variable_get("@#{dependency_alias}").call
60
- else
61
- instance_variable_get("@#{dependency_alias}")
62
- end
63
- end
64
-
65
- private dependency_alias
66
- end
67
-
68
- singleton_class.class_eval do
69
- define_method(dependency_alias) { @_dependencies[dependency_alias].call }
70
- private dependency_alias
71
- end
69
+ # @param private [TrueClass, FalseClass]
70
+ # @param as [Symbol, String, nil]
71
+ def resolve(code, initialization_block = nil, scope: true, private: true, as: nil)
72
+ @_pluggable.resolve(code, initialization_block, namespace: scope, private: private, as: as)
72
73
  end
73
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
74
74
  end
75
75
  end
76
76
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Pluggable
5
+ # Describes the bridge between a pluggable class and the registry
6
+ class Connector
7
+ # @!attribute [r] dependencies
8
+ # @return [Array<Dependency>]
9
+ attr_reader :dependencies
10
+
11
+ # @param subject [Class]
12
+ # @param registry [Dry::Container]
13
+ def initialize(subject, registry)
14
+ @subject = subject
15
+ @registry = registry
16
+ @dependencies = []
17
+
18
+ configure
19
+ end
20
+
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
+ )
32
+
33
+ # Register the subject in the registry using the provided options
34
+ @registry.register(@cipher, memoize: @memoization_required) do
35
+ @initialization_required ? @subject.new : @subject
36
+ end
37
+ end
38
+
39
+ # @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
42
+ # @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)
48
+
49
+ dependency = Dependency.new(@registry.resolve(cipher), initialization_block, pointer: as || code, private: private)
50
+ @dependencies.push(dependency)
51
+
52
+ if @initialization_required
53
+ @subject.class_eval do
54
+ define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
55
+ private(dependency.pointer) if dependency.private
56
+ end
57
+ else
58
+ @subject.instance_variable_set("@#{dependency.pointer}", dependency.target)
59
+ @subject.singleton_class.class_eval do
60
+ define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
61
+ private(dependency.pointer) if dependency.private
62
+ end
63
+ end
64
+ end
65
+ # rubocop:enable Layout/LineLength, Metrics/AbcSize, Metrics/MethodLength
66
+
67
+ private
68
+
69
+ # @param initialization_required [TrueClass, FalseClass, nil]
70
+ # @param memoization_required [TrueClass, FalseClass, nil]
71
+ # @param namespace [Symbol, String, nil]
72
+ # @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)
75
+ @initialization_required = initialization_required
76
+ @memoization_required = memoization_required
77
+ @namespace = namespace
78
+ @code = code
79
+ end
80
+
81
+ # Loads a class specified by the given cipher string. If any of the classes in the cipher string
82
+ # have not yet been loaded, this method will automatically load them as needed
83
+ #
84
+ # @param cipher [String] the string that represents the fully-qualified name of the class
85
+ # @return [Class] the loaded class
86
+ def load_class(cipher)
87
+ cipher.split('.').reduce(Object) do |namespace, class_name|
88
+ namespace.const_get(class_name.split('_').map(&:capitalize).join)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aux
4
+ module Pluggable
5
+ # Describes the dependency and its preferences
6
+ class Dependency
7
+ # @!attribute [r] target
8
+ # @return [Object]
9
+ # @!attribute [r] pointer
10
+ # @return [Symbol, String]
11
+ # @!attribute [r] private
12
+ # @return [Boolean]
13
+ attr_reader :pointer, :target, :private
14
+
15
+ # @param target [Object]
16
+ # @param initialization_block [Proc, nil]
17
+ # @param pointer [Symbol, String]
18
+ # @param private [Boolean]
19
+ def initialize(target, initialization_block = nil, pointer:, private:)
20
+ @target = initialization_block ? initialization_block.call(target) : target
21
+ @pointer = pointer
22
+ @private = private
23
+ end
24
+ end
25
+ end
26
+ end
@@ -8,23 +8,23 @@ module Aux
8
8
  # By default, we assume that the developers want to resolve a dependency from the same namespace as the
9
9
  # referencing class. Another approach is to allow developers to set the correct scope themselves.
10
10
  #
11
- # @param class_name [Class]
11
+ # @param subject [ClassName]
12
12
  # @param scope [TrueClass, Symbol, String, nil]
13
13
  # @param code [TrueClass, Symbol, String, nil]
14
14
  # @return [String]
15
- def self.dependency_cipher(class_name, scope: nil, code: nil)
16
- native_cipher = dependency_native_cipher(class_name)
15
+ def self.dependency_cipher(subject, scope: nil, code: nil)
16
+ native_cipher = dependency_native_cipher(subject)
17
17
  native_cipher_partitions = native_cipher.rpartition('.')
18
- scope = scope.is_a?(TrueClass) ? native_cipher_partitions.first : scope
18
+ scope = scope == true ? native_cipher_partitions.first : scope
19
19
  code = code.nil? ? native_cipher_partitions.last : code
20
20
 
21
21
  [scope, code].reject { |part| part.nil? || part.empty? }.join('.')
22
22
  end
23
23
 
24
- # @param class_name [Class]
24
+ # @param subject [ClassName]
25
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
26
+ 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
28
  end
29
29
  end
30
30
  end
data/lib/aux/pluggable.rb CHANGED
@@ -1,18 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aux/pluggable/class_methods'
4
+ require 'aux/pluggable/connector'
5
+ require 'aux/pluggable/dependency'
4
6
  require 'aux/pluggable/utilities'
5
7
 
6
8
  module Aux
7
9
  # Describes interface that makes any class able to register itself as well as resolve dependencies
10
+ # rubocop:disable Style/ClassVars
8
11
  module Pluggable
9
- # @param base [Class]
12
+ # Extends the including class with ClassMethods and initializes a new Connector instance
13
+ # @param base [Class] the class that includes this module
10
14
  def self.included(base)
11
15
  base.extend(ClassMethods)
12
16
 
13
17
  base.class_eval do
14
- @_registry = @@registry
15
- @_dependencies = {}
18
+ @_pluggable = Connector.new(self, @@registry)
16
19
  end
17
20
  end
18
21
 
@@ -26,4 +29,5 @@ module Aux
26
29
  @@registry
27
30
  end
28
31
  end
32
+ # rubocop:enable Style/ClassVars
29
33
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support'
4
+ require 'active_model/validations/validates'
3
5
  require 'aux/validations/errors'
4
6
 
5
7
  module Aux
6
- # Describes enhancement of {ActiveModel::Validations}
8
+ # Describes enhancement of {::ActiveModel::Validations}
7
9
  module Validations
8
10
  extend ActiveSupport::Concern
9
11
  include 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.0.9'
4
+ VERSION = '0.1.0'
5
5
  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.9
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeny Boyko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-19 00:00:00.000000000 Z
11
+ date: 2023-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -32,18 +32,38 @@ dependencies:
32
32
  version: '8'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: dry-container
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.9.0
40
+ - - "<="
41
+ - !ruby/object:Gem::Version
42
+ version: '0.11'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
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
35
55
  requirement: !ruby/object:Gem::Requirement
36
56
  requirements:
37
57
  - - "~>"
38
58
  - !ruby/object:Gem::Version
39
- version: 0.11.0
59
+ version: '2.5'
40
60
  type: :development
41
61
  prerelease: false
42
62
  version_requirements: !ruby/object:Gem::Requirement
43
63
  requirements:
44
64
  - - "~>"
45
65
  - !ruby/object:Gem::Version
46
- version: 0.11.0
66
+ version: '2.5'
47
67
  - !ruby/object:Gem::Dependency
48
68
  name: rubocop
49
69
  requirement: !ruby/object:Gem::Requirement
@@ -70,6 +90,8 @@ files:
70
90
  - lib/aux.rb
71
91
  - lib/aux/pluggable.rb
72
92
  - lib/aux/pluggable/class_methods.rb
93
+ - lib/aux/pluggable/connector.rb
94
+ - lib/aux/pluggable/dependency.rb
73
95
  - lib/aux/pluggable/utilities.rb
74
96
  - lib/aux/validations.rb
75
97
  - lib/aux/validations/error.rb