diana 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a01d25056de39c12dc7b080474f96d2e3645294f77fc199e1ea486d9db4045c
4
+ data.tar.gz: 89722a0df0f1d63a265fd84e46afc37198ec6311f468ac93b4ef5fec9f0bce0d
5
+ SHA512:
6
+ metadata.gz: 6f45f231f1e32a68559cd6bf8ded95c997e8c58bf346611b3d33a46dc547cc9426af536771104ee798dbce70da2804f1672253146e1a70a6d8a42c430f9a28ba
7
+ data.tar.gz: 8e6cfd282fb550df593cd46a04a4e6bc582181dc564e76999c9d41cb9008a5f6bb3c1d0d0c1f453835eb2776a4f913d42816699f30b47a6e3c8e5d15c97a21cf
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Diana - Lazy Dependency Injection
2
+
3
+ This module offers a DSL designed for the lazy resolution of dependency injections.
4
+
5
+ It facilitates efficient and deferred initialization of dependencies,
6
+ ensuring that resources are only allocated when necessary.
7
+
8
+ This approach optimizes performance of application.
9
+
10
+ ## Features
11
+
12
+ - **Lazy Initialization**: Dependencies are lazily initialized, ensuring
13
+ they are only loaded when you need them, optimizing performance
14
+ - **Transparent Behavior**: No hidden or undocumented behaviors, providing a
15
+ clear and predictable experience
16
+ - **Flexible Integration**: No dependencies, no mandatory DI container, but you
17
+ can seamlessly integrate with any container of your choice.
18
+ - **Broad Compatibility**: Supports a wide range of Ruby versions,
19
+ including 2.6 to 3.3, head, JRuby-9.4, and TruffleRuby-24.
20
+
21
+ These features are designed to make your development process smoother and more efficient!
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ bundle add diana
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ The Diana gem provides a streamlined way to define and manage dependencies in
32
+ your Ruby application.
33
+
34
+ ### Defining Dependencies
35
+
36
+ Use the `.dependencies` method to define your dependencies. You can also use the
37
+ `.dependency` alias if you prefer.
38
+
39
+ ```ruby
40
+ class SomeClass
41
+ include Diana.dependencies(
42
+ foo: proc { Foo.new },
43
+ bar: proc { Bar.new }
44
+ )
45
+
46
+ def some_method
47
+ foo # => Foo.new
48
+ bar # => Bar.new
49
+ end
50
+ end
51
+ ```
52
+
53
+ ### Lazy Initialization
54
+
55
+ Dependencies are lazily initialized, meaning they are only loaded when accessed
56
+ for the first time.
57
+
58
+ ### Methods Visibility
59
+
60
+ By default, dependency methods are **private**. You can change this behavior by
61
+ configuring the Diana module:
62
+
63
+ ```ruby
64
+ Diana.methods_visibility = :public
65
+ ```
66
+
67
+ Using public methods can be more convenient in tests, allowing you to access the
68
+ real dependency and stub its methods, rather than overwriting the dependency
69
+ entirely. This approach helps ensure you are testing the correct dependency.
70
+
71
+ ## How it works
72
+
73
+ - **Dependency Storage**: The `@_diana_dependencies` class variable holds the
74
+ provided dependencies.
75
+ - **Initialization**: An `#initialize` method is added to handle dependency
76
+ injection.
77
+ - **Reader Methods**: Private (by default) reader methods for dependencies are
78
+ created.
79
+ - **Lazy Resolution**: Dependencies are resolved upon first access using a
80
+ configurable resolver.
81
+
82
+ Here is an example of dependency injection and the final pseudo-code generated
83
+ by the gem:
84
+
85
+ ```ruby
86
+ class SomeClass
87
+ include Diana.dependencies(
88
+ foo: proc { Foo.new },
89
+ bar: proc { Bar.new }
90
+ )
91
+ end
92
+
93
+ # Generated pseudo-code:
94
+ class SomeClass
95
+ @_diana_dependencies = {
96
+ foo: proc { Foo.new },
97
+ bar: proc { Bar.new }
98
+ }
99
+
100
+ def initialize(foo: nil, bar: nil)
101
+ @foo = foo if foo
102
+ @bar = bar if bar
103
+ end
104
+
105
+ private
106
+
107
+ def foo
108
+ @foo ||= Diana.resolve(self.class.instance_variable_get(:@_diana_dependencies)[:foo])
109
+ end
110
+
111
+ def bar
112
+ @bar ||= Diana.resolve(self.class.instance_variable_get(:@_diana_dependencies)[:bar])
113
+ end
114
+ end
115
+ ```
116
+
117
+ This structure ensures efficient and flexible dependency management.
118
+
119
+ ## Custom Resolvers
120
+
121
+ The default resolver handles only procs, functioning as follows:
122
+
123
+ ```ruby
124
+ DEFAULT_RESOLVER = proc do |dependency|
125
+ dependency.is_a?(Proc) ? dependency.call : dependency
126
+ end
127
+ ```
128
+
129
+ You can customize the resolver to fit your needs. For instance, to resolve
130
+ strings to a DI container, you can modify the resolver like this:
131
+
132
+ ```ruby
133
+ Diana.resolver = proc do |dependency|
134
+ case dependency
135
+ when String then DI_CONTAINER[dependency]
136
+ when Proc then dependency.call
137
+ else dependency
138
+ end
139
+ end
140
+
141
+ SomeClass.include Diana.dependencies(foo: 'utils.foo') # => DI_CONTAINER['utils.foo']
142
+ ```
143
+
144
+ ## Important Notes
145
+
146
+ - This gem is intended for use with classes that have no manually defined
147
+ `#initialize` method. This design choice prevents conflicts or unpredictable
148
+ behavior with custom #initialize methods. If you do add a custom `#initialize`
149
+ method, it will take precedence. In such cases, ensure you include a
150
+ `super(**deps)` call to override dependencies if needed.
151
+
152
+ - We do not perform any argument modifications and we do not call `super`
153
+ in dependencies `#initialize` method. This approach is chosen to ensure
154
+ optimal performance.
155
+
156
+ - Dependencies defined in a parent class are not accessible in nested classes.
157
+ You should define dependencies within each class where they are required.
158
+
159
+ These limitations ensure that the gem remains predictable and performant,
160
+ avoiding any hidden complexities or unexpected behaviors.
161
+
162
+ ## Contributing
163
+
164
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/aglushkov/diana>.
165
+
166
+ ## License
167
+
168
+ The gem is available as open source under the terms of the
169
+ [MIT License](https://opensource.org/licenses/MIT).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diana
4
+ module Config
5
+ # The default resolver for dependencies.
6
+ DEFAULT_RESOLVER = proc { |dependency| dependency.is_a?(Proc) ? dependency.call : dependency }
7
+
8
+ # The default visibility for generated dependency methods.
9
+ DEFAULT_METHODS_VISIBILITY = :private
10
+
11
+ private_constant :DEFAULT_RESOLVER
12
+ private_constant :DEFAULT_METHODS_VISIBILITY
13
+
14
+ # Returns the current resolver.
15
+ #
16
+ # @api private
17
+ # @return [#call] The current resolver.
18
+ def resolver
19
+ @resolver || DEFAULT_RESOLVER
20
+ end
21
+
22
+ # Sets a new resolver.
23
+ #
24
+ # @example Setting a resolver that can resolve strings to a DI container.
25
+ # Diana.resolver = proc do |value|
26
+ # case value
27
+ # when Proc then value.call
28
+ # when String then MyContainer[value].call
29
+ # else value
30
+ # end
31
+ # end
32
+ #
33
+ # @param new_resolver [#call] The new resolver to set.
34
+ # @return [#call] The new resolver.
35
+ def resolver=(new_resolver)
36
+ @resolver = new_resolver
37
+ end
38
+
39
+ # Resolves a given value using the current resolver.
40
+ #
41
+ # @api private
42
+ # @param value [Object] The value to resolve.
43
+ # @return [Object] The resolved value.
44
+ def resolve(value)
45
+ resolver.call(value)
46
+ end
47
+
48
+ # Returns the current visibility of dependency methods.
49
+ #
50
+ # @api private
51
+ # @return [Symbol] The current visibility of dependency methods.
52
+ def methods_visibility
53
+ @methods_visibility || DEFAULT_METHODS_VISIBILITY
54
+ end
55
+
56
+ # Sets the visibility of dependency methods.
57
+ #
58
+ # @example Setting the default visibility of dependency methods to public.
59
+ # Diana.methods_visibility = :public
60
+ #
61
+ # @param visibility [Symbol] The new visibility for dependency methods (:private or :public).
62
+ # @return [Symbol] The new default visibility for dependency methods.
63
+ # @raise [ArgumentError] If the visibility is not :private or :public.
64
+ #
65
+ def methods_visibility=(visibility)
66
+ if (visibility != :private) && (visibility != :public)
67
+ raise ArgumentError, "methods_visibility value must be :private or :public"
68
+ end
69
+
70
+ @methods_visibility = visibility
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diana
4
+ # Returns the version of the Diana gem.
5
+ #
6
+ # @return [String] The version of the gem in Semantic Versioning (SemVer) format.
7
+ #
8
+ VERSION = File.read(File.join(File.dirname(__FILE__), "../../VERSION")).strip
9
+ end
data/lib/diana.rb ADDED
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "diana/version"
4
+ require_relative "diana/config"
5
+
6
+ #
7
+ # Dependency Injection DSL
8
+ #
9
+ # This module offers a DSL designed for the lazy resolution of dependency
10
+ # injections. It facilitates efficient and deferred initialization of
11
+ # dependencies ensuring that resources are only allocated when necessary.
12
+ #
13
+ # This approach optimizes performance of application.
14
+ #
15
+ # @example
16
+ # class MyClass
17
+ # include Diana.dependencies(
18
+ # foo: proc { Foo.new },
19
+ # bar: proc { Bar.new }
20
+ # )
21
+ # end
22
+ #
23
+ #
24
+ module Diana
25
+ extend Config
26
+
27
+ class << self
28
+ # Define dependencies for a class.
29
+ #
30
+ # @example
31
+ # class MyClass
32
+ # include Diana.dependencies(
33
+ # foo: proc { MyFoo.new }
34
+ # bar: proc { MyBar.new }
35
+ # )
36
+ # end
37
+ #
38
+ # param deps [Hash] A hash where keys are the names of the dependencies and
39
+ # values are the dependencies themselves, resolved lazily.
40
+ #
41
+ # @return [Module] A module to be included in your class, providing the
42
+ # defined dependencies.
43
+ #
44
+ def dependencies(deps)
45
+ Module.new do
46
+ def self.inspect
47
+ "<Diana.dependencies:#{object_id.to_s(16)}>"
48
+ end
49
+
50
+ define_singleton_method(:included) do |base|
51
+ merged_deps =
52
+ if base.instance_variable_defined?(:@_diana_dependencies)
53
+ base.instance_variable_get(:@_diana_dependencies).merge!(deps)
54
+ else
55
+ base.instance_variable_set(:@_diana_dependencies, deps.dup)
56
+ end
57
+
58
+ class_eval(<<~INITIALIZE, __FILE__, __LINE__ + 1)
59
+ def initialize(#{merged_deps.each_key.map { |dependency| "#{dependency}: nil" }.join(", ")})
60
+ #{merged_deps.each_key.map { |dependency| " @#{dependency} = #{dependency} if #{dependency}" }.join("\n")}
61
+ end
62
+ INITIALIZE
63
+ end
64
+
65
+ deps.each_key do |dependency|
66
+ class_eval(<<~ATTR_READER, __FILE__, __LINE__ + 1)
67
+ def #{dependency}
68
+ @#{dependency} ||= Diana.resolve(self.class.instance_variable_get(:@_diana_dependencies)[:#{dependency}])
69
+ end
70
+
71
+ private :#{dependency} if Diana.methods_visibility == :private
72
+ ATTR_READER
73
+ end
74
+ end
75
+ end
76
+
77
+ alias_method :dependency, :dependencies
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: diana
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrey Glushkov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ This module offers a DSL designed for the lazy resolution of dependency injections.
15
+
16
+ It facilitates efficient and deferred initialization of dependencies,
17
+ ensuring that resources are only allocated when necessary.
18
+
19
+ This approach optimizes performance of application.
20
+ email:
21
+ - aglushkov@shakuro.com
22
+ executables: []
23
+ extensions: []
24
+ extra_rdoc_files: []
25
+ files:
26
+ - README.md
27
+ - VERSION
28
+ - lib/diana.rb
29
+ - lib/diana/config.rb
30
+ - lib/diana/version.rb
31
+ homepage: https://github.com/aglushkov/diana
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ homepage_uri: https://github.com/aglushkov/diana
36
+ changelog_uri: https://github.com/aglushkov/diana/blob/master/CHANGELOG.md
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.6.0
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.5.22
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Lazy Dependency Injection
56
+ test_files: []