aux 0.0.9 → 0.1.0

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: 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