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 +7 -0
- data/README.md +169 -0
- data/VERSION +1 -0
- data/lib/diana/config.rb +73 -0
- data/lib/diana/version.rb +9 -0
- data/lib/diana.rb +79 -0
- metadata +56 -0
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
|
data/lib/diana/config.rb
ADDED
@@ -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: []
|