shirinji 0.0.2 → 0.0.7
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 +5 -5
- data/.codeclimate.yml +3 -0
- data/.gitignore +4 -1
- data/.travis.yml +35 -2
- data/CODE_OF_CONDUCT.md +10 -0
- data/Gemfile.lock +4 -5
- data/README.md +241 -211
- data/lib/shirinji.rb +1 -1
- data/lib/shirinji/attribute.rb +3 -2
- data/lib/shirinji/bean.rb +9 -4
- data/lib/shirinji/map.rb +36 -7
- data/lib/shirinji/resolver.rb +12 -2
- data/lib/shirinji/scope.rb +56 -12
- data/lib/shirinji/utils.rb +6 -0
- data/lib/shirinji/utils/string.rb +24 -0
- data/lib/shirinji/version.rb +1 -1
- data/shirinji.gemspec +2 -4
- metadata +14 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6dc426a5294d566ede0acdf7b46568d09736ef05d044afbed686bb604cd48830
|
4
|
+
data.tar.gz: 2fc85bcb5c7a1c5050cc6a29397e3b2380b89482442f64e119676c463670ffeb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f8cb9af67898e849acd3bc6c1885094dea834db218f5ab6a39b167ebb54d8ea3d6371c62692a83a27c62124714c2762087a7c79e55821717852f22835e0bad9
|
7
|
+
data.tar.gz: 07e1eac7f60615db3ecd869d59d3a648c269d56926f09a8ca5897f618d3a69b2849508c9887623ac12e2f04957fed777b01ec77dd1cd666c674ad1bfd003f856
|
data/.codeclimate.yml
ADDED
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
sudo: false
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
|
-
- 2.
|
5
|
-
|
4
|
+
- 2.4.0
|
5
|
+
- 2.4.1
|
6
|
+
- 2.4.2
|
7
|
+
- 2.4.3
|
8
|
+
- 2.4.4
|
9
|
+
- 2.4.5
|
10
|
+
- 2.4.6
|
11
|
+
- 2.4.7
|
12
|
+
- 2.4.8
|
13
|
+
- 2.4.9
|
14
|
+
- 2.5.0
|
15
|
+
- 2.5.1
|
16
|
+
- 2.5.2
|
17
|
+
- 2.5.3
|
18
|
+
- 2.5.4
|
19
|
+
- 2.5.5
|
20
|
+
- 2.5.6
|
21
|
+
- 2.5.7
|
22
|
+
- 2.5.8
|
23
|
+
- 2.5.9
|
24
|
+
- 2.6.0
|
25
|
+
- 2.6.1
|
26
|
+
- 2.6.2
|
27
|
+
- 2.6.3
|
28
|
+
- 2.6.4
|
29
|
+
- 2.6.5
|
30
|
+
- 2.6.6
|
31
|
+
- 2.6.7
|
32
|
+
- 2.7.0
|
33
|
+
- 2.7.1
|
34
|
+
- 2.7.2
|
35
|
+
- 2.7.3
|
36
|
+
- 3.0.0
|
37
|
+
- 3.0.1
|
38
|
+
before_install: gem install bundler
|
data/CODE_OF_CONDUCT.md
ADDED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shirinji (0.0.
|
4
|
+
shirinji (0.0.7)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
diff-lcs (1.3)
|
10
|
-
rake (
|
10
|
+
rake (13.0.3)
|
11
11
|
rspec (3.7.0)
|
12
12
|
rspec-core (~> 3.7.0)
|
13
13
|
rspec-expectations (~> 3.7.0)
|
@@ -26,10 +26,9 @@ PLATFORMS
|
|
26
26
|
ruby
|
27
27
|
|
28
28
|
DEPENDENCIES
|
29
|
-
|
30
|
-
rake (~> 10.0)
|
29
|
+
rake (>= 12.3.3)
|
31
30
|
rspec (~> 3.0)
|
32
31
|
shirinji!
|
33
32
|
|
34
33
|
BUNDLED WITH
|
35
|
-
|
34
|
+
2.2.16
|
data/README.md
CHANGED
@@ -1,307 +1,337 @@
|
|
1
1
|
# Shirinji
|
2
2
|
|
3
|
-
|
3
|
+
[](
|
4
|
+
https://badge.fury.io/rb/shirinji
|
5
|
+
)
|
6
|
+
[](
|
7
|
+
https://travis-ci.org/fdutey/shirinji
|
8
|
+
)
|
9
|
+
[
|
11
|
+
](https://codeclimate.com/github/fdutey/shirinji/maintainability)
|
12
|
+
|
13
|
+
Dependencies Injection made clean and easy for Ruby.
|
14
|
+
|
15
|
+
## Supported ruby versions
|
16
|
+
|
17
|
+
- 2.4.x
|
18
|
+
- 2.5.x
|
19
|
+
- 2.6.x
|
20
|
+
- 2.7.x
|
21
|
+
- 3.0.x
|
4
22
|
|
5
23
|
## Principles
|
6
24
|
|
7
|
-
|
8
|
-
|
25
|
+
Remove hard dependencies between your objects and delegate object tree building
|
26
|
+
to an unobtrusive framework with cool convention over configuration.
|
9
27
|
|
10
|
-
|
28
|
+
Shirinji relies on a mapping of beans and a resolver. When you resolve a bean,
|
29
|
+
it will return (by default) an instance of the class associated to the bean,
|
30
|
+
with all the bean dependencies resolved.
|
11
31
|
|
12
|
-
```
|
13
|
-
|
32
|
+
```ruby
|
33
|
+
class FooService
|
34
|
+
attr_reader :bar_service
|
35
|
+
|
36
|
+
def initialize(bar_service:)
|
37
|
+
@bar_service = bar_service
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(obj)
|
41
|
+
obj.foo = 123
|
42
|
+
|
43
|
+
bar_service.call(obj)
|
44
|
+
end
|
45
|
+
end
|
14
46
|
|
15
|
-
|
16
|
-
|
47
|
+
map = Shirinji::Map.new do
|
48
|
+
bean(:foo_service, klass: 'FooService')
|
49
|
+
bean(:bar_service, klass: 'BarService')
|
50
|
+
end
|
17
51
|
|
18
|
-
|
19
|
-
using the IOC pattern. The `$.ajax` method is delegating the action to perform when the request
|
20
|
-
is successful to something else, focusing only on handling the http communication part.
|
52
|
+
resolver = Shirinji::Resolver.new(map)
|
21
53
|
|
22
|
-
|
23
|
-
|
54
|
+
resolver.resolve(:foo_service)
|
55
|
+
# => <#FooService @bar_service=<#BarService>>
|
56
|
+
```
|
24
57
|
|
25
|
-
|
58
|
+
Shirinji is unobtrusive. Basically, any of your objects can be used
|
59
|
+
outside of its context.
|
26
60
|
|
27
61
|
```ruby
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
62
|
+
bar_service = BarService.new
|
63
|
+
foo_service = FooService.new(bar_service: bar_service)
|
64
|
+
# => <#FooService @bar_service=<#BarService>>
|
65
|
+
|
66
|
+
# tests
|
67
|
+
|
68
|
+
RSpec.describe FooService do
|
69
|
+
let(:bar_service) { double(call: nil) }
|
70
|
+
let(:service) { described_class.new(bar_service: bar_service) }
|
36
71
|
|
37
|
-
|
38
|
-
|
72
|
+
describe '.call' do
|
73
|
+
# ...
|
39
74
|
end
|
40
75
|
end
|
41
76
|
```
|
42
77
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
78
|
+
## Constructor arguments
|
79
|
+
|
80
|
+
Shirinji relies on constructor to inject dependencies. It's considering that
|
81
|
+
objects that receive dependencies should be immutables and those dependencies
|
82
|
+
should not change during your program lifecycle.
|
83
|
+
|
84
|
+
Shirinji doesn't accept anything else than named parameters. This way,
|
85
|
+
arguments order doesn't matter and it makes everybody's life easier.
|
86
|
+
|
87
|
+
## Name resolution
|
48
88
|
|
49
|
-
|
50
|
-
|
89
|
+
By default, when you try to resolve a bean, Shirinji will look for a bean named
|
90
|
+
accordingly for each constructor parameter.
|
91
|
+
|
92
|
+
It's possible to locally override this behaviour though by using `attr` macro.
|
51
93
|
|
52
94
|
```ruby
|
53
|
-
class
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
UserMailer.confirm_email(user).deliver
|
95
|
+
class FooService
|
96
|
+
attr_reader :bar_service
|
97
|
+
|
98
|
+
def initialize(my_service:)
|
99
|
+
@bar_service = my_service
|
59
100
|
end
|
60
101
|
end
|
61
102
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
describe '.call' do
|
68
|
-
let(:message_instance) { double(deliver: nil) }
|
69
|
-
let(:user) { FactoryGirl.build_stubbed(:user, id: 1) }
|
103
|
+
map = Shirinji::Map.new do
|
104
|
+
bean(:foo_service, klass: 'FooService') do
|
105
|
+
attr :my_service, ref: :bar_service
|
106
|
+
end
|
70
107
|
|
71
|
-
|
72
|
-
allow(StatisticsGateway).to receive(:publish_event)
|
73
|
-
allow(UserMailer).to receive(:confirm_email).and_return(message_instance)
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'saves user' do
|
77
|
-
expect(user).to receive(:save!)
|
78
|
-
|
79
|
-
service.call(user)
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'sets signed up time' do
|
83
|
-
service.call(user)
|
84
|
-
expect(user.signed_up_at).to_not be_nil
|
85
|
-
# there are better ways to test that but we don't care here
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'publishes statistics' do
|
89
|
-
expect(StatisticsGateway).to receive(:publish_event).with(:new_user, 1)
|
90
|
-
|
91
|
-
service.call(user)
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'notifies user for identity confirmation' do
|
95
|
-
expect(UserMailer).to receive(:confirm_email).with(user)
|
96
|
-
expect(message_instance).to receive(:deliver)
|
97
|
-
|
98
|
-
service.call(user)
|
99
|
-
end
|
100
|
-
end
|
108
|
+
bean(:bar_service, klass: 'BarService')
|
101
109
|
end
|
110
|
+
|
111
|
+
resolver = Shirinji::Resolver.new(map)
|
112
|
+
|
113
|
+
resolver.resolve(:foo_service)
|
114
|
+
# => <#FooService @bar_service=<#BarService>>
|
102
115
|
```
|
103
116
|
|
104
|
-
|
105
|
-
the context. It will act as a sign up only when we call SignUpService.
|
117
|
+
## Caching and singletons
|
106
118
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
(for example, if the same class is called twice in very different contexts in the same method)
|
119
|
+
Shirinji provides a caching mecanism to help you improve memory consumption.
|
120
|
+
This cache is safe as long as your beans remains immutable (they should always
|
121
|
+
be).
|
111
122
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
send emails.
|
123
|
+
The consequence is that any cached instance is actually a singleton. Singleton
|
124
|
+
is no more a property of your class but of it's environment, improving the
|
125
|
+
reusability of your code.
|
116
126
|
|
117
|
-
|
118
|
-
fast, scalable, readable ... code, you should restrict your objects to one and only one responsbility.
|
127
|
+
Singleton is the default access mode for a bean.
|
119
128
|
|
120
129
|
```ruby
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
PublishUserStatisticsService.new.call(user)
|
127
|
-
SendUserEmailConfirmationService.new.call(user)
|
128
|
-
# implementation omitted for those services, you can figure it out
|
129
|
-
end
|
130
|
+
map = Shirinji::Map.new do
|
131
|
+
bean(:bar_service, klass: 'BarService', access: :instance)
|
132
|
+
bean(:foo_service, klass: 'FooService', access: :singleton)
|
133
|
+
# same as bean(:foo_service, klass: 'FooService')
|
130
134
|
end
|
135
|
+
|
136
|
+
resolver = Shirinji::Resolver.new(map)
|
137
|
+
|
138
|
+
resolver.resolve(:foo_service).object_id #=> 1
|
139
|
+
resolver.resolve(:foo_service).object_id #=> 1
|
140
|
+
|
141
|
+
resolver.resolve(:bar_service).object_id #=> 2
|
142
|
+
resolver.resolve(:bar_service).object_id #=> 3
|
131
143
|
```
|
132
144
|
|
133
|
-
|
134
|
-
|
135
|
-
|
145
|
+
Cache can be reset with the simple command `resolver.reset_cache`, which can be
|
146
|
+
useful when using a development console like rails console ([shirinji-rails](
|
147
|
+
https://github.com/fdutey/shirinji-rails) is attaching cache reset to `reload!`
|
148
|
+
command).
|
149
|
+
|
150
|
+
## Other type of beans
|
151
|
+
|
152
|
+
Dependencies injection doesn't apply only to classes. You can actually inject
|
153
|
+
anything and therefore, Shirinji allows you to declare anything as a dependency.
|
154
|
+
To achieve that, use the key `value` instead of `class`.
|
136
155
|
|
137
156
|
```ruby
|
138
|
-
|
139
|
-
def
|
140
|
-
|
141
|
-
user.save!
|
142
|
-
|
143
|
-
publish_user_statistics_service.call(user)
|
144
|
-
send_user_email_confirmation_service.call(user)
|
157
|
+
module MyApp
|
158
|
+
def self.config
|
159
|
+
@config
|
145
160
|
end
|
146
|
-
|
147
|
-
private
|
148
161
|
|
149
|
-
def
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
def send_user_email_confirmation_service
|
154
|
-
SendUserEmailConfirmationService.new
|
162
|
+
def self.load!
|
163
|
+
@config = OpenStruct.new
|
155
164
|
end
|
156
165
|
end
|
157
166
|
|
158
|
-
|
159
|
-
|
160
|
-
RSpec.describe SignUpUserService do
|
161
|
-
let(:publish_statistics_service) { double(call: nil) }
|
162
|
-
let(:send_email_confirmation_service) { double(call: nil) }
|
163
|
-
|
164
|
-
let(:service) { described_class.new }
|
167
|
+
class FooService
|
168
|
+
attr_reader :config
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
allow(service).to receive(:send_user_email_confirmation_service).and_return(send_email_confirmation_service)
|
170
|
+
def initialize(config:)
|
171
|
+
@config = config
|
169
172
|
end
|
170
|
-
|
171
|
-
# ...
|
172
173
|
end
|
173
|
-
```
|
174
174
|
|
175
|
-
|
176
|
-
and focus only on the service itself.
|
177
|
-
But if you think about it, this service still has too many responsibilities. It still carrying the
|
178
|
-
responsibility of choosing which service will execute the "sub tasks" and more important, it's in
|
179
|
-
charge of creating those services instances.
|
175
|
+
MyApp.load!
|
180
176
|
|
181
|
-
|
182
|
-
|
177
|
+
map = Shirinji::Map.new do
|
178
|
+
bean(:config, value: Proc.new { MyApp.config })
|
179
|
+
|
180
|
+
bean(:foo_service, klass: 'FooService')
|
181
|
+
end
|
183
182
|
|
184
|
-
|
185
|
-
class SignUpUserService
|
186
|
-
attr_reader :publish_user_statistics_service,
|
187
|
-
:send_user_email_confirmation_service
|
188
|
-
|
189
|
-
def initialize(
|
190
|
-
publish_user_statistics_service:,
|
191
|
-
send_user_email_confirmation_service:,
|
192
|
-
)
|
193
|
-
@publish_user_statistics_service = publish_user_statistics_service
|
194
|
-
@send_user_email_confirmation_service = send_user_email_confirmation_service
|
195
|
-
end
|
183
|
+
resolver = Shirinji::Resolver.new(map)
|
196
184
|
|
197
|
-
|
198
|
-
|
199
|
-
user.save!
|
200
|
-
|
201
|
-
publish_user_statistics_service.call(user)
|
202
|
-
send_user_email_confirmation_service.call(user)
|
203
|
-
end
|
204
|
-
end
|
185
|
+
resolver.resolve(:foo_service)
|
186
|
+
#=> <#FooService @config=<#OpenStruct ...> ...>
|
205
187
|
```
|
206
188
|
|
207
|
-
|
208
|
-
|
189
|
+
A value can be anything. `Proc` will be lazily evaluated. It also obeys the
|
190
|
+
cache mechanism described before.
|
209
191
|
|
210
|
-
|
211
|
-
maintain. It's where Shiringji comes to the rescue.
|
192
|
+
## Skip construction mechanism
|
212
193
|
|
213
|
-
|
194
|
+
In some cases, you need a dependency to be injected as a class and not an
|
195
|
+
instance. In such case, you could use value beans, returning the class itself,
|
196
|
+
but you would lose the benefit of scopes (see below).
|
197
|
+
Instead, Shirinji provides a parameter to skip the object construction.
|
198
|
+
|
199
|
+
A real life example is a Job where `deliver_now` and `deliver_later` are
|
200
|
+
class methods.
|
214
201
|
|
215
202
|
```ruby
|
216
203
|
map = Shirinji::Map.new do
|
217
|
-
bean(:
|
218
|
-
bean(:publish_user_statistics_service, klass: "PublishUserStatisticsService")
|
219
|
-
bean(:send_user_email_confirmation_service, klass: "SendUserEmailConfirmationService")
|
204
|
+
bean(:foo_job, klass: 'FooJob', construct: false)
|
220
205
|
end
|
221
206
|
|
222
207
|
resolver = Shirinji::Resolver.new(map)
|
223
208
|
|
224
|
-
resolver.resolve(:
|
225
|
-
#=> <#SignUpUserService @publish_user_statistics_service=<#PublishUserStatisticsService ...> ...>
|
209
|
+
resolver.resolve(:foo_job) #=> FooJob
|
226
210
|
```
|
227
211
|
|
228
|
-
|
229
|
-
Shirinji will automatically resolve them.
|
212
|
+
## Scopes
|
230
213
|
|
231
|
-
|
214
|
+
Building complex objects mapping leads to lot of repetition. That's why Shirinji
|
215
|
+
also provides a scope mechanism to help you dry your code.
|
232
216
|
|
233
217
|
```ruby
|
234
218
|
map = Shirinji::Map.new do
|
235
|
-
|
236
|
-
|
219
|
+
scope module: :Services, suffix: :service, klass_suffix: :Service do
|
220
|
+
bean(:foo, klass: 'Foo')
|
221
|
+
# same as bean(:foo_service, klass: 'Services::FooService')
|
222
|
+
|
223
|
+
scope module: :User, prefix: :user do
|
224
|
+
bean(:bar, klass: 'Bar')
|
225
|
+
# same as bean(:user_bar_service, klass: 'Services::User::BarService')
|
226
|
+
end
|
237
227
|
end
|
238
|
-
|
239
|
-
# note the name is different
|
240
|
-
bean(:user_publish_statistics_service, klass: "PublishUserStatisticsService")
|
241
|
-
bean(:send_user_email_confirmation_service, klass: "SendUserEmailConfirmationService")
|
242
228
|
end
|
229
|
+
```
|
243
230
|
|
244
|
-
|
231
|
+
Scopes also come with an `auto_klass` attribute to save even more time for
|
232
|
+
common cases
|
245
233
|
|
246
|
-
|
247
|
-
|
234
|
+
```ruby
|
235
|
+
map = Shirinji::Map.new do
|
236
|
+
scope module: :Services,
|
237
|
+
suffix: :service,
|
238
|
+
klass_suffix: :Service,
|
239
|
+
auto_klass: true do
|
240
|
+
bean(:foo)
|
241
|
+
# same as bean(:foo_service, klass: 'Services::FooService')
|
242
|
+
end
|
243
|
+
end
|
248
244
|
```
|
249
245
|
|
250
|
-
|
251
|
-
the pattern in your classes. It means the same class can be used as a singleton AND a regular class
|
252
|
-
at the same time without any code change.
|
246
|
+
Scopes also provides an `auto_prefix` option
|
253
247
|
|
254
|
-
|
248
|
+
```ruby
|
249
|
+
map = Shirinji::Map.new do
|
250
|
+
scope module: :Services,
|
251
|
+
suffix: :service,
|
252
|
+
klass_suffix: :Service,
|
253
|
+
auto_klass: true do
|
254
|
+
|
255
|
+
# Do not use auto prefix on root scope or every bean will be prefixed
|
256
|
+
# with `services_`
|
257
|
+
scope auto_prefix: true do
|
258
|
+
bean(:foo)
|
259
|
+
# same as bean(:foo_service, klass: 'Services::FooService')
|
260
|
+
|
261
|
+
scope module: :User do
|
262
|
+
# same as scope module: :User, prefix: :user
|
263
|
+
|
264
|
+
bean(:bar)
|
265
|
+
# same as bean(:user_bar_service, klass: 'Services::User::BarService')
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
Finally, for mailers / jobs ..., Scopes allow you to specify a global value
|
273
|
+
for `construct`
|
255
274
|
|
256
275
|
```ruby
|
257
276
|
map = Shirinji::Map.new do
|
258
|
-
|
259
|
-
|
277
|
+
scope module: :Jobs,
|
278
|
+
suffix: :job,
|
279
|
+
klass_suffix: :Job,
|
280
|
+
auto_klass: true,
|
281
|
+
construct: false do
|
282
|
+
bean(:foo)
|
283
|
+
# bean(:foo_job, klass: 'Jobs::FooJob', construct: false)
|
284
|
+
end
|
260
285
|
end
|
286
|
+
```
|
261
287
|
|
262
|
-
|
288
|
+
Scopes do not carry property `access`
|
263
289
|
|
264
|
-
|
265
|
-
resolver.resolve(:foo).object_id #=> 1
|
290
|
+
## Code splitting
|
266
291
|
|
267
|
-
|
268
|
-
|
269
|
-
|
292
|
+
When a project grows, dependencies grows too. Keeping them into one single file
|
293
|
+
leads to headaches. One possible solution to keep everything under control is
|
294
|
+
to split your dependencies into many files.
|
270
295
|
|
271
|
-
|
272
|
-
variables in your code.
|
296
|
+
To include a "sub-map" into another one, you can use `include_map` method.
|
273
297
|
|
274
298
|
```ruby
|
275
|
-
|
276
|
-
|
277
|
-
bean(:
|
299
|
+
# dependencies/services.rb
|
300
|
+
Shirinji::Map.new do
|
301
|
+
bean(:foo_service, klass: 'FooService')
|
278
302
|
end
|
279
303
|
|
280
|
-
|
304
|
+
# dependencies/queries.rb
|
305
|
+
Shirinji::Map.new do
|
306
|
+
bean(:foo_query, klass: 'FooQuery')
|
307
|
+
end
|
281
308
|
|
282
|
-
|
283
|
-
|
309
|
+
# dependencies.rb
|
310
|
+
|
311
|
+
root = Pathname.new(File.expand_path('../dependencies', __FILE__))
|
312
|
+
|
313
|
+
Shirinji::Map.new do
|
314
|
+
bean(:config, value: -> { MyApp.config })
|
284
315
|
|
285
|
-
|
286
|
-
|
287
|
-
|
316
|
+
# paths must be absolute
|
317
|
+
include_map(root.join('queries.rb'))
|
318
|
+
include_map(root.join('services.rb'))
|
288
319
|
end
|
289
|
-
|
290
|
-
resolver.resolve(:foo)
|
291
|
-
#=> <#Foo @config=<#OpenStruct ...> ...>
|
292
320
|
```
|
293
321
|
|
294
|
-
Values can be anything. A `Proc` will be lazily evaluated. They also obey the singleton / instance
|
295
|
-
strategy.
|
296
|
-
|
297
322
|
## Notes
|
298
323
|
|
299
|
-
- It is absolutely mandatory for your beans to be stateless to use the singleton
|
300
|
-
not, you will probably run into trouble as your objects
|
301
|
-
to unpredictable effects.
|
302
|
-
- Shirinji only works with named arguments. It will raise
|
303
|
-
method arguments.
|
324
|
+
- It is absolutely mandatory for your beans to be stateless to use the singleton
|
325
|
+
mode. If they're not, you will probably run into trouble as your objects
|
326
|
+
behavior will depend on their history, leading to unpredictable effects.
|
327
|
+
- Shirinji only works with named arguments. It will raise `ArgumentError` if you
|
328
|
+
try to use it with "standard" method arguments.
|
329
|
+
|
330
|
+
## TODOS
|
331
|
+
|
332
|
+
- solve absolute paths problems for `include_map` (`instance_eval` is a problem)
|
304
333
|
|
305
334
|
## Contributing
|
306
335
|
|
307
|
-
Bug reports and pull requests are welcome on GitHub at
|
336
|
+
Bug reports and pull requests are welcome on GitHub at
|
337
|
+
https://github.com/fdutey/shirinji.
|
data/lib/shirinji.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Shirinji
|
4
|
-
# Your code goes here...
|
5
4
|
end
|
6
5
|
|
7
6
|
require 'shirinji/version'
|
@@ -10,3 +9,4 @@ require 'shirinji/bean'
|
|
10
9
|
require 'shirinji/map'
|
11
10
|
require 'shirinji/resolver'
|
12
11
|
require 'shirinji/scope'
|
12
|
+
require 'shirinji/utils'
|
data/lib/shirinji/attribute.rb
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module Shirinji
|
4
4
|
class Attribute
|
5
|
-
attr_reader :name, :reference
|
5
|
+
attr_reader :name, :reference, :value
|
6
6
|
|
7
|
-
def initialize(name, reference)
|
7
|
+
def initialize(name, reference = nil, value = nil)
|
8
8
|
@name = name
|
9
9
|
@reference = reference
|
10
|
+
@value = value
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
data/lib/shirinji/bean.rb
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
|
3
3
|
module Shirinji
|
4
4
|
class Bean
|
5
|
-
attr_reader :name, :class_name, :value, :access, :attributes
|
5
|
+
attr_reader :name, :class_name, :value, :access, :attributes, :construct
|
6
6
|
|
7
|
-
|
7
|
+
# rubocop:disable Metrics/ParameterLists
|
8
|
+
def initialize(
|
9
|
+
name, class_name: nil, value: nil, access:, construct: true, &block
|
10
|
+
)
|
8
11
|
check_params!(class_name, value)
|
9
12
|
|
10
13
|
@name = name
|
@@ -12,12 +15,14 @@ module Shirinji
|
|
12
15
|
@value = value
|
13
16
|
@access = access
|
14
17
|
@attributes = {}
|
18
|
+
@construct = construct
|
15
19
|
|
16
20
|
instance_eval(&block) if block
|
17
21
|
end
|
22
|
+
# rubocop:enable Metrics/ParameterLists
|
18
23
|
|
19
|
-
def attr(name, ref:)
|
20
|
-
attributes[name] = Attribute.new(name, ref)
|
24
|
+
def attr(name, ref: nil, value: nil)
|
25
|
+
attributes[name] = Attribute.new(name, ref, value)
|
21
26
|
end
|
22
27
|
|
23
28
|
private
|
data/lib/shirinji/map.rb
CHANGED
@@ -4,10 +4,34 @@ module Shirinji
|
|
4
4
|
class Map
|
5
5
|
attr_reader :beans
|
6
6
|
|
7
|
+
# Loads a map at a given location
|
8
|
+
#
|
9
|
+
# @param location [string] path to the map to load
|
10
|
+
def self.load(location)
|
11
|
+
eval(File.read(location))
|
12
|
+
end
|
13
|
+
|
7
14
|
def initialize(&block)
|
8
15
|
@beans = {}
|
9
16
|
|
10
|
-
instance_eval(&block)
|
17
|
+
instance_eval(&block) if block
|
18
|
+
end
|
19
|
+
|
20
|
+
# Merges another map at a given location
|
21
|
+
#
|
22
|
+
# @param location [string] the file to include - must be an absolute path
|
23
|
+
def include_map(location)
|
24
|
+
merge(self.class.load(location))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Merges a map into another one
|
28
|
+
#
|
29
|
+
# @param map [Shirinji::Map] the map to merge into this one
|
30
|
+
# @raise [ArgumentError] if both map contains a bean with the same bean
|
31
|
+
def merge(map)
|
32
|
+
map.beans.keys.each { |name| raise_if_name_already_taken!(name) }
|
33
|
+
|
34
|
+
beans.merge!(map.beans)
|
11
35
|
end
|
12
36
|
|
13
37
|
# Returns a bean based on its name
|
@@ -26,6 +50,7 @@ module Shirinji
|
|
26
50
|
def get(name)
|
27
51
|
bean = beans[name.to_sym]
|
28
52
|
raise ArgumentError, "Unknown bean #{name}" unless bean
|
53
|
+
|
29
54
|
bean
|
30
55
|
end
|
31
56
|
|
@@ -47,20 +72,20 @@ module Shirinji
|
|
47
72
|
#
|
48
73
|
# @param name [Symbol] the name you want to register your bean
|
49
74
|
# @option [String] :klass the classname the bean is registering
|
50
|
-
# @option [
|
75
|
+
# @option [Object] :value the object registered by the bean
|
76
|
+
# @option [Boolean] :construct whether the bean should be constructed or not
|
51
77
|
# @option [Symbol] :access either :singleton or :instance.
|
52
78
|
# @yield additional method to construct our bean
|
53
79
|
# @raise [ArgumentError] if trying to register a bean that already exist
|
54
|
-
def bean(name, klass: nil,
|
80
|
+
def bean(name, klass: nil, access: :singleton, **others, &block)
|
55
81
|
name = name.to_sym
|
56
82
|
|
57
83
|
raise_if_name_already_taken!(name)
|
58
84
|
|
59
|
-
options =
|
85
|
+
options = others.merge(
|
60
86
|
access: access,
|
61
|
-
class_name: klass
|
62
|
-
|
63
|
-
}
|
87
|
+
class_name: klass&.freeze
|
88
|
+
)
|
64
89
|
|
65
90
|
beans[name] = Bean.new(name, **options, &block)
|
66
91
|
end
|
@@ -113,6 +138,9 @@ module Shirinji
|
|
113
138
|
# @option options [Symbol] :prefix prepend prefix to bean name
|
114
139
|
# @option options [Symbol] :suffix append suffix to bean name
|
115
140
|
# @option options [Symbol] :klass_suffix append suffix to class name
|
141
|
+
# @option options [Boolean] :auto_klass generates klass from name
|
142
|
+
# @option options [Boolean] :auto_prefix generates prefix from module
|
143
|
+
# @option options [Boolean] :construct applies `construct` on every bean
|
116
144
|
# @yield a standard map
|
117
145
|
def scope(**options, &block)
|
118
146
|
Scope.new(self, **options, &block)
|
@@ -122,6 +150,7 @@ module Shirinji
|
|
122
150
|
|
123
151
|
def raise_if_name_already_taken!(name)
|
124
152
|
return unless beans[name]
|
153
|
+
|
125
154
|
msg = "A bean already exists with the following name: #{name}"
|
126
155
|
raise ArgumentError, msg
|
127
156
|
end
|
data/lib/shirinji/resolver.rb
CHANGED
@@ -24,6 +24,11 @@ module Shirinji
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
def reload(map)
|
28
|
+
@map = map
|
29
|
+
reset_cache
|
30
|
+
end
|
31
|
+
|
27
32
|
def reset_cache
|
28
33
|
@singletons = {}
|
29
34
|
end
|
@@ -40,12 +45,13 @@ module Shirinji
|
|
40
45
|
|
41
46
|
def resolve_class_bean(bean)
|
42
47
|
klass, params = resolve_class(bean)
|
48
|
+
return klass unless bean.construct
|
43
49
|
return klass.new if params.empty?
|
44
50
|
|
45
51
|
check_params!(params)
|
46
52
|
|
47
53
|
args = params.each_with_object({}) do |(_type, arg), memo|
|
48
|
-
memo[arg] =
|
54
|
+
memo[arg] = resolve_attribute(bean, arg)
|
49
55
|
end
|
50
56
|
|
51
57
|
klass.new(**args)
|
@@ -59,12 +65,16 @@ module Shirinji
|
|
59
65
|
end
|
60
66
|
|
61
67
|
def resolve_attribute(bean, arg)
|
62
|
-
(attr = bean.attributes[arg])
|
68
|
+
return resolve(arg) unless (attr = bean.attributes[arg])
|
69
|
+
return attr.value if attr.value
|
70
|
+
|
71
|
+
resolve(attr.reference)
|
63
72
|
end
|
64
73
|
|
65
74
|
def check_params!(params)
|
66
75
|
params.each do |pair|
|
67
76
|
next if ARG_TYPES.include?(pair.first)
|
77
|
+
|
68
78
|
raise ArgumentError, 'Only key arguments are allowed'
|
69
79
|
end
|
70
80
|
end
|
data/lib/shirinji/scope.rb
CHANGED
@@ -2,42 +2,86 @@
|
|
2
2
|
|
3
3
|
module Shirinji
|
4
4
|
class Scope
|
5
|
-
VALID_OPTIONS = %i[
|
5
|
+
VALID_OPTIONS = %i[
|
6
|
+
module prefix suffix klass_suffix auto_klass auto_prefix construct
|
7
|
+
].freeze
|
6
8
|
|
7
|
-
attr_reader :parent, :mod, :prefix, :suffix, :klass_suffix
|
9
|
+
attr_reader :parent, :mod, :prefix, :suffix, :klass_suffix, :auto_klass,
|
10
|
+
:construct, :auto_prefix
|
8
11
|
|
9
12
|
def initialize(parent, **options, &block)
|
10
13
|
validate_options(options)
|
11
14
|
|
12
15
|
@parent = parent
|
13
16
|
@mod = options[:module]
|
14
|
-
@prefix = options[:prefix]
|
15
17
|
@suffix = options[:suffix]
|
16
18
|
@klass_suffix = options[:klass_suffix]
|
19
|
+
@auto_klass = options[:auto_klass]
|
20
|
+
@auto_prefix = options[:auto_prefix]
|
21
|
+
@prefix = generate_prefix(options[:prefix])
|
22
|
+
@construct = options.fetch(:construct, true)
|
17
23
|
|
18
24
|
instance_eval(&block) if block
|
19
25
|
end
|
20
26
|
|
21
|
-
def bean(name, klass: nil,
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
27
|
+
def bean(name, klass: nil, **options, &block)
|
28
|
+
default_opts = compact({ construct: construct })
|
29
|
+
|
30
|
+
klass = generate_klass(name, klass) unless options[:value]
|
31
|
+
options = compact(default_opts.merge(options).merge(klass: klass))
|
32
|
+
scoped_name = generate_scope(name)
|
28
33
|
|
29
|
-
parent.bean(
|
34
|
+
parent.bean(scoped_name, **options, &block)
|
30
35
|
end
|
31
36
|
|
32
37
|
def scope(**options, &block)
|
33
|
-
|
38
|
+
opts = {
|
39
|
+
auto_klass: auto_klass,
|
40
|
+
auto_prefix: auto_prefix,
|
41
|
+
construct: construct
|
42
|
+
}.merge(options)
|
43
|
+
|
44
|
+
Scope.new(self, **opts, &block)
|
34
45
|
end
|
35
46
|
|
36
47
|
private
|
37
48
|
|
49
|
+
def compact(h)
|
50
|
+
h.reject { |_,v| v.nil? }
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_scope(name)
|
54
|
+
[prefix, name, suffix].compact.join('_')
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_klass(name, klass)
|
58
|
+
return if !klass && !auto_klass
|
59
|
+
|
60
|
+
klass ||= klassify(name)
|
61
|
+
chunks = [mod, "#{klass}#{klass_suffix}"].compact
|
62
|
+
|
63
|
+
chunks.join('::')
|
64
|
+
end
|
65
|
+
|
66
|
+
def generate_prefix(prefix)
|
67
|
+
return prefix if prefix
|
68
|
+
return nil unless auto_prefix
|
69
|
+
|
70
|
+
mod && underscore(mod.to_s).to_sym
|
71
|
+
end
|
72
|
+
|
73
|
+
def klassify(name)
|
74
|
+
Shirinji::Utils::String.camelcase(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def underscore(name)
|
78
|
+
Shirinji::Utils::String.snakecase(name)
|
79
|
+
end
|
80
|
+
|
38
81
|
def validate_options(args)
|
39
82
|
args.each_key do |k|
|
40
83
|
next if Shirinji::Scope::VALID_OPTIONS.include?(k)
|
84
|
+
|
41
85
|
raise ArgumentError, "Unknown key #{k}"
|
42
86
|
end
|
43
87
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shirinji
|
2
|
+
module Utils
|
3
|
+
module String
|
4
|
+
CAMEL =
|
5
|
+
/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def camelcase(str)
|
10
|
+
chunks = str.to_s.split('_').map do |w|
|
11
|
+
w = w.downcase
|
12
|
+
w[0] = w[0].upcase
|
13
|
+
w
|
14
|
+
end
|
15
|
+
|
16
|
+
chunks.join
|
17
|
+
end
|
18
|
+
|
19
|
+
def snakecase(str)
|
20
|
+
str.gsub(CAMEL) { |s| s.split('').join('_') }.downcase
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/shirinji/version.rb
CHANGED
data/shirinji.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
5
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
5
|
require 'shirinji/version'
|
7
6
|
|
@@ -22,7 +21,6 @@ Gem::Specification.new do |spec|
|
|
22
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
22
|
spec.require_paths = ['lib']
|
24
23
|
|
25
|
-
spec.add_development_dependency '
|
26
|
-
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
27
25
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
26
|
end
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shirinji
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Dutey
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.16'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.16'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rake
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
|
-
- - "
|
17
|
+
- - ">="
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
19
|
+
version: 12.3.3
|
34
20
|
type: :development
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
|
-
- - "
|
24
|
+
- - ">="
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
26
|
+
version: 12.3.3
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rspec
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,12 +45,14 @@ executables: []
|
|
59
45
|
extensions: []
|
60
46
|
extra_rdoc_files: []
|
61
47
|
files:
|
48
|
+
- ".codeclimate.yml"
|
62
49
|
- ".gitignore"
|
63
50
|
- ".rspec"
|
64
51
|
- ".rubocop.yml"
|
65
52
|
- ".ruby-gemset"
|
66
53
|
- ".ruby-version"
|
67
54
|
- ".travis.yml"
|
55
|
+
- CODE_OF_CONDUCT.md
|
68
56
|
- Gemfile
|
69
57
|
- Gemfile.lock
|
70
58
|
- README.md
|
@@ -77,12 +65,14 @@ files:
|
|
77
65
|
- lib/shirinji/map.rb
|
78
66
|
- lib/shirinji/resolver.rb
|
79
67
|
- lib/shirinji/scope.rb
|
68
|
+
- lib/shirinji/utils.rb
|
69
|
+
- lib/shirinji/utils/string.rb
|
80
70
|
- lib/shirinji/version.rb
|
81
71
|
- shirinji.gemspec
|
82
72
|
homepage: https://github.com/fdutey/shirinji
|
83
73
|
licenses: []
|
84
74
|
metadata: {}
|
85
|
-
post_install_message:
|
75
|
+
post_install_message:
|
86
76
|
rdoc_options: []
|
87
77
|
require_paths:
|
88
78
|
- lib
|
@@ -97,9 +87,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
87
|
- !ruby/object:Gem::Version
|
98
88
|
version: '0'
|
99
89
|
requirements: []
|
100
|
-
|
101
|
-
|
102
|
-
signing_key:
|
90
|
+
rubygems_version: 3.2.15
|
91
|
+
signing_key:
|
103
92
|
specification_version: 4
|
104
93
|
summary: Dependencies injection made easy
|
105
94
|
test_files: []
|