diana 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -9
  3. data/VERSION +1 -1
  4. data/lib/diana/config.rb +7 -4
  5. data/lib/diana.rb +42 -1
  6. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d751ec38e00ed56a859741213b470b086262769968e5fbb7abe5ab674cc9a2a
4
- data.tar.gz: c46124c639d0af4dc45e8555aa213125440b5957ba54f6fbb787bbb801d02825
3
+ metadata.gz: 5a42ecda41893911ab6703f513107d3789a191d1a378adf3b4c598816a0cd6ab
4
+ data.tar.gz: 40fe0dc12e4b06ffb4250e16d9bb2036936fb1fbf219e5dd0a2c55ea4e808919
5
5
  SHA512:
6
- metadata.gz: 0e5f0c3fb1a0f58a7b159ab6ef40cd3f78d996e2d1a81c83632522caedc67e001e3cf5542a7b81dc8cdb4aec27ff7968c62c7007a63edd6b3ab0570f51c83e9c
7
- data.tar.gz: ddf038a682978738d514fe3d5a02bd54f9784e46de8aba83f841eff6a21e5a4abf6713dda9708280f04d46e68c262ff0be4e2d5dbbac9d4f8abe97251f187e9e
6
+ metadata.gz: db8f596619a96ee92d7c10e893288213cb8697f8e4b4e6523f54565c4e1a8690f713a8afb323de0d46ac1d193891481c0e15772791a58aa28350615fc4798ff0
7
+ data.tar.gz: 6cb63290c78aa3f332fdb63f996b64ce9b81e6b27e284a8ea7513d6d0eb2616006f376b915ffb473fcc810900b994633a29665d30eb82ebbfe442ac3e379b64b
data/README.md CHANGED
@@ -66,13 +66,30 @@ By default, dependency methods are **private**. You can change this behavior by
66
66
  configuring the Diana module:
67
67
 
68
68
  ```ruby
69
- Diana.methods_visibility = :public
69
+ Diana.methods_visibility = :public # private, public, protected
70
70
  ```
71
71
 
72
- Using public methods can be more convenient in tests, allowing you to access the
73
- real dependency and stub its methods, rather than overwriting the dependency
72
+ Using **public** methods can be more convenient in tests, allowing you to access
73
+ the real dependency and stub its methods, rather than overwriting the dependency
74
74
  entirely. This approach helps ensure you are testing the correct dependency.
75
75
 
76
+ ### Inheritance
77
+
78
+ Classes with included dependencies can be nested. Dependencies from parent and
79
+ child classes are merged.
80
+
81
+ ### Adding dependencies multiple times
82
+
83
+ `Diana.dependencies` method can be used multiple times. In this case
84
+ dependencies are merged.
85
+
86
+ ```ruby
87
+ class SomeClass
88
+ include Diana.dependencies(foo: proc { Foo.new })
89
+ include Diana.dependencies(bar: proc { Bar.new })
90
+ end
91
+ ```
92
+
76
93
  ## How it works
77
94
 
78
95
  - **Dependency Storage**: The `@_diana_dependencies` class variable holds the
@@ -102,17 +119,26 @@ class SomeClass
102
119
  bar: proc { Bar.new }
103
120
  }
104
121
 
122
+ # handles dependency injection
105
123
  def initialize(foo: nil, bar: nil)
106
124
  @foo = foo if foo
107
125
  @bar = bar if bar
108
126
  end
109
127
 
128
+ # handles inheritance
129
+ def self.inherited(subclass)
130
+ subclass.include Diana.dependencies(@_diana_dependencies)
131
+ super
132
+ end
133
+
110
134
  private
111
135
 
136
+ # handles lazy `foo` resolution
112
137
  def foo
113
138
  @foo ||= Diana.resolve(self.class.instance_variable_get(:@_diana_dependencies)[:foo])
114
139
  end
115
140
 
141
+ # handles lazy `bar` resolution
116
142
  def bar
117
143
  @bar ||= Diana.resolve(self.class.instance_variable_get(:@_diana_dependencies)[:bar])
118
144
  end
@@ -154,12 +180,8 @@ SomeClass.include Diana.dependencies(foo: 'utils.foo') # => DI_CONTAINER['utils.
154
180
  method, it will take precedence. In such cases, ensure you include a
155
181
  `super(**deps)` call to override dependencies if needed.
156
182
 
157
- - We do not perform any argument modifications and we do not call `super`
158
- in dependencies `#initialize` method. This approach is chosen to ensure
159
- optimal performance.
160
-
161
- - Dependencies defined in a parent class are not accessible in nested classes.
162
- You should define dependencies within each class where they are required.
183
+ - We avoid calling `super` in the added `#initialize` method to prevent the need
184
+ for arguments modifications, which could negatively impact performance.
163
185
 
164
186
  These limitations ensure that the gem remains predictable and performant,
165
187
  avoiding any hidden complexities or unexpected behaviors.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
data/lib/diana/config.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Diana
4
+ #
5
+ # Defines Diana module configuration methods
6
+ #
4
7
  module Config
5
8
  # The default resolver for dependencies.
6
9
  DEFAULT_RESOLVER = proc { |dependency| dependency.is_a?(Proc) ? dependency.call : dependency }
@@ -58,13 +61,13 @@ module Diana
58
61
  # @example Setting the default visibility of dependency methods to public.
59
62
  # Diana.methods_visibility = :public
60
63
  #
61
- # @param visibility [Symbol] The new visibility for dependency methods (:private or :public).
64
+ # @param visibility [Symbol] The new visibility for dependency methods (:private, :public, :protected).
62
65
  # @return [Symbol] The new default visibility for dependency methods.
63
- # @raise [ArgumentError] If the visibility is not :private or :public.
66
+ # @raise [ArgumentError] If the visibility is not :private, :public, or :protected.
64
67
  #
65
68
  def methods_visibility=(visibility)
66
- if (visibility != :private) && (visibility != :public)
67
- raise ArgumentError, "methods_visibility value must be :private or :public"
69
+ if (visibility != :private) && (visibility != :public) && (visibility != :protected)
70
+ raise ArgumentError, "methods_visibility value must be :private, :public, or :protected"
68
71
  end
69
72
 
70
73
  @methods_visibility = visibility
data/lib/diana.rb CHANGED
@@ -43,11 +43,45 @@ module Diana
43
43
  #
44
44
  def dependencies(deps)
45
45
  Module.new do
46
+ # Adds readable name to this anonymous module when showing `MyClass.ancestors`
46
47
  def self.inspect
47
48
  "<Diana.dependencies:#{object_id.to_s(16)}>"
48
49
  end
49
50
 
51
+ class_mod = Module.new do
52
+ # Adds readable name to this anonymous module when showing `MyClass.singleton_class.ancestors`
53
+ def self.inspect
54
+ "<Diana.inheritance:#{object_id.to_s(16)}>"
55
+ end
56
+
57
+ private
58
+
59
+ def inherited(subclass)
60
+ # When `inherited` is called for the first time, we add the parent's
61
+ # `@_diana_dependencies`. To avoid adding dependencies from
62
+ # ancestors in the `super` call, we check if `@_diana_dependencies`
63
+ # is already defined.
64
+ unless subclass.instance_variable_defined?(:@_diana_dependencies)
65
+ subclass.include Diana.dependencies(@_diana_dependencies)
66
+ end
67
+
68
+ super
69
+ end
70
+ end
71
+
50
72
  define_singleton_method(:included) do |base|
73
+ # Adds .inherited method
74
+ base.extend(class_mod)
75
+
76
+ #
77
+ # Merging dependencies allows to add dependencies multiple times
78
+ #
79
+ # Example:
80
+ # class MyClass
81
+ # include Diana.dependencies(foo: 'foo')
82
+ # include Diana.dependencies(bar: 'bar')
83
+ # end
84
+ #
51
85
  merged_deps =
52
86
  if base.instance_variable_defined?(:@_diana_dependencies)
53
87
  base.instance_variable_get(:@_diana_dependencies).merge!(deps)
@@ -55,6 +89,10 @@ module Diana
55
89
  base.instance_variable_set(:@_diana_dependencies, deps.dup)
56
90
  end
57
91
 
92
+ # Add initialize method
93
+ # Instance variables are set only for not-null dependencies.
94
+ # Using class_eval is slower to define the method, yet it provides the
95
+ # benefit of executing faster than if it were defined using define_method.
58
96
  class_eval(<<~INITIALIZE, __FILE__, __LINE__ + 1)
59
97
  def initialize(#{merged_deps.each_key.map { |dependency| "#{dependency}: nil" }.join(", ")})
60
98
  #{merged_deps.each_key.map { |dependency| " @#{dependency} = #{dependency} if #{dependency}" }.join("\n")}
@@ -62,13 +100,16 @@ module Diana
62
100
  INITIALIZE
63
101
  end
64
102
 
103
+ # Add dependencies attribute readers and set their visibility.
104
+ # Using class_eval is slower to define the method, yet it provides the
105
+ # benefit of executing faster than if it were defined using define_method.
65
106
  deps.each_key do |dependency|
66
107
  class_eval(<<~ATTR_READER, __FILE__, __LINE__ + 1)
67
108
  def #{dependency}
68
109
  @#{dependency} ||= Diana.resolve(self.class.instance_variable_get(:@_diana_dependencies)[:#{dependency}])
69
110
  end
70
111
 
71
- private :#{dependency} if Diana.methods_visibility == :private
112
+ #{Diana.methods_visibility} :#{dependency}
72
113
  ATTR_READER
73
114
  end
74
115
  end
metadata CHANGED
@@ -1,16 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diana
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Glushkov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-17 00:00:00.000000000 Z
11
+ date: 2024-12-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Lazy Dependency Injection
13
+ description: |
14
+ Lazy Dependency Injection.
15
+ Dependencies are allocated only when needed, optimizing performance.
14
16
  email:
15
17
  - aglushkov@shakuro.com
16
18
  executables: []
@@ -26,7 +28,7 @@ homepage: https://github.com/aglushkov/diana
26
28
  licenses:
27
29
  - MIT
28
30
  metadata:
29
- source_code_uri: https://github.com/aglushkov/diana
31
+ homepage_uri: https://github.com/aglushkov/diana
30
32
  changelog_uri: https://github.com/aglushkov/diana/blob/master/CHANGELOG.md
31
33
  post_install_message:
32
34
  rdoc_options: []