shirinji 0.0.3 → 0.0.4
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 +4 -4
- data/.codeclimate.yml +3 -0
- data/.travis.yml +11 -2
- data/Gemfile.lock +2 -3
- data/README.md +228 -226
- data/lib/shirinji/bean.rb +2 -0
- data/lib/shirinji/map.rb +32 -3
- data/lib/shirinji/resolver.rb +1 -0
- data/lib/shirinji/scope.rb +55 -8
- data/lib/shirinji/utils/string.rb +24 -0
- data/lib/shirinji/utils.rb +6 -0
- data/lib/shirinji/version.rb +1 -1
- data/lib/shirinji.rb +1 -1
- data/shirinji.gemspec +1 -3
- metadata +5 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77fda6000796dd02133fae4778b4501bc570990f
|
4
|
+
data.tar.gz: 46c1303d621e5fb851a4cc01514a4451df06535f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12e6a82f1e8b7fea438a05bdd7020cabc0197ed2e1ec6398bd0d19b81031a98bb29eeaeeba85b9064d3b11cd213156b9123ff813961e4b374f3a32bbc53d75b3
|
7
|
+
data.tar.gz: 35f01c0f061af476ccab1704656c4187fa5284c025aabd332e89dd7b8b511e5a19f9ab2c73380649e6fc715d71965e6e6066f5c01d632f7163b35a6d8fa53133
|
data/.codeclimate.yml
ADDED
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shirinji (0.0.
|
4
|
+
shirinji (0.0.4)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -26,10 +26,9 @@ PLATFORMS
|
|
26
26
|
ruby
|
27
27
|
|
28
28
|
DEPENDENCIES
|
29
|
-
bundler (~> 1.16)
|
30
29
|
rake (~> 10.0)
|
31
30
|
rspec (~> 3.0)
|
32
31
|
shirinji!
|
33
32
|
|
34
33
|
BUNDLED WITH
|
35
|
-
|
34
|
+
2.0.2
|
data/README.md
CHANGED
@@ -1,333 +1,335 @@
|
|
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)
|
4
12
|
|
5
|
-
|
6
|
-
|
7
|
-
Dependencies Injection is strongly connected with the IOC (inversion of controls) pattern. IOC is
|
8
|
-
often seen as a "Java thing" and tend to be rejected by Ruby community.
|
13
|
+
Dependencies Injection made clean and easy for Ruby.
|
9
14
|
|
10
|
-
|
15
|
+
## Supported ruby versions
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
$.ajax('/action', { onSuccess: updateUI, ... })
|
16
|
-
```
|
17
|
+
- 2.4.x
|
18
|
+
- 2.5.x
|
19
|
+
- 2.6.x
|
17
20
|
|
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.
|
21
|
+
## Principles
|
21
22
|
|
22
|
-
|
23
|
-
|
23
|
+
Remove hard dependencies between your objects and delegate object tree building
|
24
|
+
to an unobtrusive framework with cool convention over configuration.
|
24
25
|
|
25
|
-
|
26
|
+
Shirinji relies on a mapping of beans and a resolver. When you resolve a bean,
|
27
|
+
it will return (by default) an instance of the class associated to the bean,
|
28
|
+
with all the bean dependencies resolved.
|
26
29
|
|
27
30
|
```ruby
|
28
|
-
class
|
29
|
-
|
30
|
-
|
31
|
-
private
|
31
|
+
class FooService
|
32
|
+
attr_reader :bar_service
|
32
33
|
|
33
|
-
def
|
34
|
-
|
34
|
+
def initialize(bar_service:)
|
35
|
+
@bar_service = bar_service
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
-
|
38
|
+
def call(obj)
|
39
|
+
obj.foo = 123
|
40
|
+
|
41
|
+
bar_service.call(obj)
|
39
42
|
end
|
40
43
|
end
|
41
|
-
```
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
want those operations to be performed (db seeding, imports, fixtures in tests ...)
|
45
|
+
map = Shirinji::Map.new do
|
46
|
+
bean(:foo_service, klass: 'FooService')
|
47
|
+
bean(:bar_service, klass: 'BarService')
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
50
|
+
resolver = Shirinji::Resolver.new(map)
|
51
|
+
|
52
|
+
resolver.resolve(:foo_service)
|
53
|
+
# => <#FooService @bar_service=<#BarService>>
|
54
|
+
```
|
55
|
+
|
56
|
+
Shirinji is unobtrusive. Basically, any of your objects can be used
|
57
|
+
outside of its context.
|
51
58
|
|
52
59
|
```ruby
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
user.save!
|
57
|
-
StatisticsGateway.publish_event(:new_user, user.id)
|
58
|
-
UserMailer.confirm_email(user).deliver
|
59
|
-
end
|
60
|
-
end
|
60
|
+
bar_service = BarService.new
|
61
|
+
foo_service = FooService.new(bar_service: bar_service)
|
62
|
+
# => <#FooService @bar_service=<#BarService>>
|
61
63
|
|
62
|
-
|
64
|
+
# tests
|
63
65
|
|
64
|
-
RSpec.describe
|
65
|
-
let(:
|
66
|
+
RSpec.describe FooService do
|
67
|
+
let(:bar_service) { double(call: nil) }
|
68
|
+
let(:service) { described_class.new(bar_service: bar_service) }
|
66
69
|
|
67
70
|
describe '.call' do
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
before do
|
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
|
71
|
+
# ...
|
72
|
+
end
|
101
73
|
end
|
102
74
|
```
|
103
75
|
|
104
|
-
|
105
|
-
the context. It will act as a sign up only when we call SignUpService.
|
76
|
+
## Constructor arguments
|
106
77
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
(for example, if the same class is called twice in very different contexts in the same method)
|
78
|
+
Shirinji relies on constructor to inject dependencies. It's considering that
|
79
|
+
objects that receive dependencies should be immutables and those dependencies
|
80
|
+
should not change during your program lifecycle.
|
111
81
|
|
112
|
-
|
113
|
-
|
114
|
-
It shouldn't. The way we sign up users should not change according to the solution we chose to
|
115
|
-
send emails.
|
82
|
+
Shirinji doesn't accept anything else than named parameters. This way,
|
83
|
+
arguments order doesn't matter and it makes everybody's life easier.
|
116
84
|
|
117
|
-
|
118
|
-
fast, scalable, readable ... code, you should restrict your objects to one and only one responsbility.
|
85
|
+
## Name resolution
|
119
86
|
|
120
|
-
|
121
|
-
|
122
|
-
def call(user)
|
123
|
-
user.signed_up_at = Time.now
|
124
|
-
user.save!
|
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
|
-
end
|
131
|
-
```
|
87
|
+
By default, when you try to resolve a bean, Shirinji will look for a bean named
|
88
|
+
accordingly for each constructor parameter.
|
132
89
|
|
133
|
-
|
134
|
-
method on both "sub services" will be even more dirty than before.
|
135
|
-
We can solve this problem very easily
|
90
|
+
It's possible to locally override this behaviour though by using `attr` macro.
|
136
91
|
|
137
92
|
```ruby
|
138
|
-
class
|
139
|
-
|
140
|
-
user.signed_up_at = Time.now
|
141
|
-
user.save!
|
142
|
-
|
143
|
-
publish_user_statistics_service.call(user)
|
144
|
-
send_user_email_confirmation_service.call(user)
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
93
|
+
class FooService
|
94
|
+
attr_reader :bar_service
|
148
95
|
|
149
|
-
def
|
150
|
-
|
151
|
-
end
|
152
|
-
|
153
|
-
def send_user_email_confirmation_service
|
154
|
-
SendUserEmailConfirmationService.new
|
96
|
+
def initialize(my_service:)
|
97
|
+
@bar_service = my_service
|
155
98
|
end
|
156
99
|
end
|
157
100
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
let(:publish_statistics_service) { double(call: nil) }
|
162
|
-
let(:send_email_confirmation_service) { double(call: nil) }
|
163
|
-
|
164
|
-
let(:service) { described_class.new }
|
165
|
-
|
166
|
-
before do
|
167
|
-
allow(service).to receive(:publish_user_statistics_service).and_return(publish_statistics_service)
|
168
|
-
allow(service).to receive(:send_user_email_confirmation_service).and_return(send_email_confirmation_service)
|
101
|
+
map = Shirinji::Map.new do
|
102
|
+
bean(:foo_service, klass: 'FooService') do
|
103
|
+
attr :my_service, ref: :bar_service
|
169
104
|
end
|
170
105
|
|
171
|
-
|
106
|
+
bean(:bar_service, klass: 'BarService')
|
172
107
|
end
|
108
|
+
|
109
|
+
resolver = Shirinji::Resolver.new(map)
|
110
|
+
|
111
|
+
resolver.resolve(:foo_service)
|
112
|
+
# => <#FooService @bar_service=<#BarService>>
|
173
113
|
```
|
174
114
|
|
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.
|
115
|
+
## Caching and singletons
|
180
116
|
|
181
|
-
|
182
|
-
|
117
|
+
Shirinji provides a caching mecanism to help you improve memory consumption.
|
118
|
+
This cache is safe as long as your beans remains immutable (they should always
|
119
|
+
be).
|
183
120
|
|
184
|
-
|
185
|
-
class
|
186
|
-
|
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
|
121
|
+
The consequence is that any cached instance is actually a singleton. Singleton
|
122
|
+
is no more a property of your class but of it's environment, improving the
|
123
|
+
reusability of your code.
|
196
124
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
125
|
+
Singleton is the default access mode for a bean.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
map = Shirinji::Map.new do
|
129
|
+
bean(:bar_service, klass: 'BarService', access: :instance)
|
130
|
+
bean(:foo_service, klass: 'FooService', access: :singleton)
|
131
|
+
# same as bean(:foo_service, klass: 'FooService')
|
204
132
|
end
|
133
|
+
|
134
|
+
resolver = Shirinji::Resolver.new(map)
|
135
|
+
|
136
|
+
resolver.resolve(:foo).object_id #=> 1
|
137
|
+
resolver.resolve(:foo).object_id #=> 1
|
138
|
+
|
139
|
+
resolver.resolve(:bar).object_id #=> 2
|
140
|
+
resolver.resolve(:bar).object_id #=> 3
|
205
141
|
```
|
206
142
|
|
207
|
-
|
208
|
-
|
143
|
+
Cache can be reset with the simple command `resolver.reset_cache`, which can be
|
144
|
+
useful when using a development console like rails console ([shirinji-rails](
|
145
|
+
https://github.com/fdutey/shirinji-rails) is attaching cache reset to `reload!`
|
146
|
+
command).
|
209
147
|
|
210
|
-
|
211
|
-
maintain. It's where Shiringji comes to the rescue.
|
148
|
+
## Other type of beans
|
212
149
|
|
213
|
-
|
150
|
+
Dependencies injection doesn't apply only to classes. You can actually inject
|
151
|
+
anything and therefore, Shirinji allows you to declare anything as a dependency.
|
152
|
+
To achieve that, use the key `value` instead of `class`.
|
214
153
|
|
215
154
|
```ruby
|
155
|
+
module MyApp
|
156
|
+
def self.config
|
157
|
+
@config
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.load!
|
161
|
+
@config = OpenStruct.new
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class FooService
|
166
|
+
attr_reader :config
|
167
|
+
|
168
|
+
def initialize(config:)
|
169
|
+
@config = config
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
MyApp.load!
|
174
|
+
|
216
175
|
map = Shirinji::Map.new do
|
217
|
-
bean(:
|
218
|
-
|
219
|
-
bean(:
|
176
|
+
bean(:config, value: Proc.new { MyApp.config })
|
177
|
+
|
178
|
+
bean(:foo_service, klass: 'FooService')
|
220
179
|
end
|
221
180
|
|
222
181
|
resolver = Shirinji::Resolver.new(map)
|
223
182
|
|
224
|
-
resolver.resolve(:
|
225
|
-
#=> <#
|
183
|
+
resolver.resolve(:foo_service)
|
184
|
+
#=> <#FooService @config=<#OpenStruct ...> ...>
|
226
185
|
```
|
227
186
|
|
228
|
-
|
229
|
-
|
187
|
+
A value can be anything. `Proc` will be lazily evaluated. It also obeys the
|
188
|
+
cache mechanism described before.
|
189
|
+
|
190
|
+
## Skip construction mechanism
|
230
191
|
|
231
|
-
In
|
192
|
+
In some cases, you need a dependency to be injected as a class and not an
|
193
|
+
instance. In such case, you could use value beans, returning the class itself,
|
194
|
+
but you would lose the benefit of scopes (see below).
|
195
|
+
Instead, Shirinji provides a parameter to skip the object construction.
|
196
|
+
|
197
|
+
A real life example is a Job where `deliver_now` and `deliver_later` are
|
198
|
+
class methods.
|
232
199
|
|
233
200
|
```ruby
|
234
201
|
map = Shirinji::Map.new do
|
235
|
-
bean(:
|
236
|
-
attr :publish_user_statistics_service, ref: :user_publish_statistics_service
|
237
|
-
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")
|
202
|
+
bean(:foo_job, klass: 'FooJob', construct: false)
|
242
203
|
end
|
243
204
|
|
244
205
|
resolver = Shirinji::Resolver.new(map)
|
245
206
|
|
246
|
-
resolver.resolve(:
|
247
|
-
#=> <#SignUpUserService @publish_user_statistics_service=<#PublishUserStatisticsService ...> ...>
|
207
|
+
resolver.resolve(:foo_job) #=> FooJob
|
248
208
|
```
|
249
209
|
|
250
|
-
|
210
|
+
## Scopes
|
211
|
+
|
212
|
+
Building complex objects mapping leads to lot of repetition. That's why Shirinji
|
213
|
+
also provides a scope mechanism to help you dry your code.
|
251
214
|
|
252
215
|
```ruby
|
253
216
|
map = Shirinji::Map.new do
|
254
217
|
scope module: :Services, suffix: :service, klass_suffix: :Service do
|
218
|
+
bean(:foo, klass: 'Foo')
|
219
|
+
# same as bean(:foo_service, klass: 'Services::FooService')
|
220
|
+
|
255
221
|
scope module: :User, prefix: :user do
|
256
|
-
bean(:
|
257
|
-
|
222
|
+
bean(:bar, klass: 'Bar')
|
223
|
+
# same as bean(:user_bar_service, klass: 'Services::User::BarService')
|
224
|
+
end
|
258
225
|
end
|
259
|
-
|
260
|
-
# is the same as
|
261
|
-
bean(:user_signup_service, klass: 'Services::User::SignupService')
|
262
226
|
end
|
263
227
|
```
|
264
228
|
|
265
|
-
|
266
|
-
|
229
|
+
Scopes also come with an `auto_klass` attribute to save even more time for
|
230
|
+
common cases
|
267
231
|
|
268
232
|
```ruby
|
269
233
|
map = Shirinji::Map.new do
|
270
|
-
|
234
|
+
scope module: :Services,
|
235
|
+
suffix: :service,
|
236
|
+
klass_suffix: :Service,
|
237
|
+
auto_klass: true do
|
238
|
+
bean(:foo)
|
239
|
+
# same as bean(:foo_service, klass: 'Services::FooService')
|
240
|
+
end
|
271
241
|
end
|
272
|
-
|
273
|
-
resolver.resolve(:foo) #=> Foo
|
274
242
|
```
|
275
243
|
|
276
|
-
|
277
|
-
the pattern in your classes. It means the same class can be used as a singleton AND a regular class
|
278
|
-
at the same time without any code change.
|
244
|
+
Scopes also provides an `auto_prefix` option
|
279
245
|
|
280
|
-
|
246
|
+
```ruby
|
247
|
+
map = Shirinji::Map.new do
|
248
|
+
scope module: :Services,
|
249
|
+
suffix: :service,
|
250
|
+
klass_suffix: :Service,
|
251
|
+
auto_klass: true do
|
252
|
+
|
253
|
+
# Do not use auto prefix on root scope or every bean will be prefixed
|
254
|
+
# with `services_`
|
255
|
+
scope auto_prefix: true do
|
256
|
+
bean(:foo)
|
257
|
+
# same as bean(:foo_service, klass: 'Services::FooService')
|
258
|
+
|
259
|
+
scope module: :User do
|
260
|
+
# same as scope module: :User, prefix: :user
|
261
|
+
|
262
|
+
bean(:bar)
|
263
|
+
# same as bean(:user_bar_service, klass: 'Services::User::BarService')
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
Finally, for mailers / jobs ..., Scopes allow you to specify a global value
|
271
|
+
for `construct`
|
281
272
|
|
282
273
|
```ruby
|
283
274
|
map = Shirinji::Map.new do
|
284
|
-
|
285
|
-
|
275
|
+
scope module: :Jobs,
|
276
|
+
suffix: :job,
|
277
|
+
klass_suffix: :Job,
|
278
|
+
auto_klass: true,
|
279
|
+
construct: false do
|
280
|
+
bean(:foo)
|
281
|
+
# bean(:foo_job, klass: 'Jobs::FooJob', construct: false)
|
282
|
+
end
|
286
283
|
end
|
284
|
+
```
|
287
285
|
|
288
|
-
|
286
|
+
Scopes do not carry property `access`
|
289
287
|
|
290
|
-
|
291
|
-
resolver.resolve(:foo).object_id #=> 1
|
288
|
+
## Code splitting
|
292
289
|
|
293
|
-
|
294
|
-
|
295
|
-
|
290
|
+
When a project grows, dependencies grows too. Keeping them into one single file
|
291
|
+
leads to headaches. One possible solution to keep everything under control is
|
292
|
+
to split your dependencies into many files.
|
296
293
|
|
297
|
-
|
298
|
-
variables in your code.
|
294
|
+
To include a "sub-map" into another one, you can use `include_map` method.
|
299
295
|
|
300
296
|
```ruby
|
301
|
-
|
302
|
-
|
303
|
-
bean(:
|
297
|
+
# dependencies/services.rb
|
298
|
+
Shirinji::Map.new do
|
299
|
+
bean(:foo_service, klass: 'FooService')
|
304
300
|
end
|
305
301
|
|
306
|
-
|
302
|
+
# dependencies/queries.rb
|
303
|
+
Shirinji::Map.new do
|
304
|
+
bean(:foo_query, klass: 'FooQuery')
|
305
|
+
end
|
307
306
|
|
308
|
-
|
309
|
-
|
307
|
+
# dependencies.rb
|
308
|
+
|
309
|
+
root = Pathname.new(File.expand_path('../dependencies', __FILE__))
|
310
|
+
|
311
|
+
Shirinji::Map.new do
|
312
|
+
bean(:config, value: -> { MyApp.config })
|
310
313
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
+
# paths must be absolute
|
315
|
+
include_map(root.join('queries.rb'))
|
316
|
+
include_map(root.join('services.rb'))
|
314
317
|
end
|
315
|
-
|
316
|
-
resolver.resolve(:foo)
|
317
|
-
#=> <#Foo @config=<#OpenStruct ...> ...>
|
318
318
|
```
|
319
319
|
|
320
|
-
Values can be anything. A `Proc` will be lazily evaluated. They also obey the singleton / instance
|
321
|
-
strategy.
|
322
|
-
|
323
320
|
## Notes
|
324
321
|
|
325
|
-
- It is absolutely mandatory for your beans to be stateless to use the singleton
|
326
|
-
not, you will probably run into trouble as your objects
|
327
|
-
to unpredictable effects.
|
328
|
-
- Shirinji only works with named arguments. It will raise
|
329
|
-
method arguments.
|
322
|
+
- It is absolutely mandatory for your beans to be stateless to use the singleton
|
323
|
+
mode. If they're not, you will probably run into trouble as your objects
|
324
|
+
behavior will depend on their history, leading to unpredictable effects.
|
325
|
+
- Shirinji only works with named arguments. It will raise `ArgumentError` if you
|
326
|
+
try to use it with "standard" method arguments.
|
327
|
+
|
328
|
+
## TODOS
|
329
|
+
|
330
|
+
- solve absolute paths problems for `include_map` (`instance_eval` is a problem)
|
330
331
|
|
331
332
|
## Contributing
|
332
333
|
|
333
|
-
Bug reports and pull requests are welcome on GitHub at
|
334
|
+
Bug reports and pull requests are welcome on GitHub at
|
335
|
+
https://github.com/fdutey/shirinji.
|
data/lib/shirinji/bean.rb
CHANGED
@@ -4,6 +4,7 @@ module Shirinji
|
|
4
4
|
class Bean
|
5
5
|
attr_reader :name, :class_name, :value, :access, :attributes, :construct
|
6
6
|
|
7
|
+
# rubocop:disable Metrics/ParameterLists
|
7
8
|
def initialize(
|
8
9
|
name, class_name: nil, value: nil, access:, construct: true, &block
|
9
10
|
)
|
@@ -18,6 +19,7 @@ module Shirinji
|
|
18
19
|
|
19
20
|
instance_eval(&block) if block
|
20
21
|
end
|
22
|
+
# rubocop:enable Metrics/ParameterLists
|
21
23
|
|
22
24
|
def attr(name, ref:)
|
23
25
|
attributes[name] = Attribute.new(name, ref)
|
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,7 +72,7 @@ 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
|
51
76
|
# @option [Boolean] :construct whether the bean should be constructed or not
|
52
77
|
# @option [Symbol] :access either :singleton or :instance.
|
53
78
|
# @yield additional method to construct our bean
|
@@ -59,7 +84,7 @@ module Shirinji
|
|
59
84
|
|
60
85
|
options = others.merge(
|
61
86
|
access: access,
|
62
|
-
class_name: klass
|
87
|
+
class_name: klass&.freeze
|
63
88
|
)
|
64
89
|
|
65
90
|
beans[name] = Bean.new(name, **options, &block)
|
@@ -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
data/lib/shirinji/scope.rb
CHANGED
@@ -2,39 +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
|
-
|
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)
|
25
33
|
|
26
34
|
parent.bean(scoped_name, **options, &block)
|
27
35
|
end
|
28
36
|
|
29
37
|
def scope(**options, &block)
|
30
|
-
|
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)
|
31
45
|
end
|
32
46
|
|
33
47
|
private
|
34
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
|
+
|
35
81
|
def validate_options(args)
|
36
82
|
args.each_key do |k|
|
37
83
|
next if Shirinji::Scope::VALID_OPTIONS.include?(k)
|
84
|
+
|
38
85
|
raise ArgumentError, "Unknown key #{k}"
|
39
86
|
end
|
40
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/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/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 'bundler', '~> 1.16'
|
26
24
|
spec.add_development_dependency 'rake', '~> 10.0'
|
27
25
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
26
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
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.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Dutey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-08-23 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
|
@@ -59,6 +45,7 @@ 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"
|
@@ -78,6 +65,8 @@ files:
|
|
78
65
|
- lib/shirinji/map.rb
|
79
66
|
- lib/shirinji/resolver.rb
|
80
67
|
- lib/shirinji/scope.rb
|
68
|
+
- lib/shirinji/utils.rb
|
69
|
+
- lib/shirinji/utils/string.rb
|
81
70
|
- lib/shirinji/version.rb
|
82
71
|
- shirinji.gemspec
|
83
72
|
homepage: https://github.com/fdutey/shirinji
|