diana 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []