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