shirinji 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 41ac63bcf3f6fd45d64dbcf12eafde69547eba1d
4
+ data.tar.gz: c07d6da743a36f3800f8a7a29bc3c8c6216368f1
5
+ SHA512:
6
+ metadata.gz: 8060030b8a19b19d5b7abdc90fe8d9c1721851a2ea7c867d42ddb509f3d0d41e86865816a8caa5e1a61c10158be4aa7128be08a9a01384b48d7923e7dbbde133
7
+ data.tar.gz: caf8ca03a33a67c8feb56c9dd5fd57ba20b472dadc7930f00f3b565dad71e5beba3fba97948cf109dec20321b425c2e3af4d1173e90be887ecc4810368d0b263
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .idea
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Style/Documentation:
2
+ Enabled: false
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ shirinji
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.4
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.4
5
+ before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in shirinji.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ shirinji (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (10.5.0)
11
+ rspec (3.7.0)
12
+ rspec-core (~> 3.7.0)
13
+ rspec-expectations (~> 3.7.0)
14
+ rspec-mocks (~> 3.7.0)
15
+ rspec-core (3.7.0)
16
+ rspec-support (~> 3.7.0)
17
+ rspec-expectations (3.7.0)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.7.0)
20
+ rspec-mocks (3.7.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.7.0)
23
+ rspec-support (3.7.0)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 1.16)
30
+ rake (~> 10.0)
31
+ rspec (~> 3.0)
32
+ shirinji!
33
+
34
+ BUNDLED WITH
35
+ 1.16.1
data/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # Shirinji
2
+
3
+ Container manager for dependency injection in Ruby.
4
+
5
+ ## Principles
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.
9
+
10
+ Yet, it's heavily used in javascript world and fit perfectly with prototyped language.
11
+
12
+ ```javascript
13
+ function updateUI(evt) { /* ... */ }
14
+
15
+ $.ajax('/action', { onSuccess: updateUI, ... })
16
+ ```
17
+
18
+ A simple script like that is very common in Javascript and nobody is shocked by that. Yet, it's
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
+
22
+ Dependencies injection is nothing more than the exact same principle but applied to objects instead
23
+ of functions.
24
+
25
+ Let's follow an example step by step from "the rails way" to a proper way to understand it better.
26
+
27
+ ```ruby
28
+ class User < ActiveRecord::Base
29
+ after_create :publish_statistics, :send_confirmation_email
30
+
31
+ private
32
+
33
+ def publish_statistics
34
+ StatisticsGateway.publish_event(:new_user, user.id)
35
+ end
36
+
37
+ def send_confirmation_email
38
+ UserMailer.confirm_email(user).deliver
39
+ end
40
+ end
41
+ ```
42
+
43
+ This is called "the rails way" and everybody with a tiny bit of experience knows that this way
44
+ is not valid. Your model is gonna send statistics and emails each time it's saved, even when it's
45
+ not in the context of signing up a new user (confusion between sign up, a business level operation
46
+ and create, a persistency level operation). There are plenty of situation where you actually don't
47
+ want those operations to be performed (db seeding, imports, fixtures in tests ...)
48
+
49
+ That's where services pattern comes to the rescue. Let's do it in a very simple fashion and just
50
+ move everything "as it is" in a service.
51
+
52
+ ```ruby
53
+ class SignUpUserService
54
+ def call(user)
55
+ user.signed_up_at = Time.now
56
+ user.save!
57
+ StatisticsGateway.publish_event(:new_user, user.id)
58
+ UserMailer.confirm_email(user).deliver
59
+ end
60
+ end
61
+
62
+ ## test
63
+
64
+ RSpec.describe SignUpUserService do
65
+ let(:service) { described_class.new }
66
+
67
+ describe '.call' do
68
+ let(:message_instance) { double(deliver: nil) }
69
+ let(:user) { FactoryGirl.build_stubbed(:user, id: 1) }
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
101
+ end
102
+ ```
103
+
104
+ It's a bit better. Now when we want to write a user in DB, it's not acting as a signup regardless
105
+ the context. It will act as a sign up only when we call SignUpService.
106
+
107
+ Yet, if we look a the tests for this service, we have to mock `StatisticsGateway` and
108
+ `UserMailer` in order for the test to run properly. It means that we need a very precise knowledge
109
+ of the implementation, and we need to mock global static objects which can be a very big problem
110
+ (for example, if the same class is called twice in very different contexts in the same method)
111
+
112
+ Also, if we decide to switch our statistics solution, or if we decide to change the way we notify
113
+ users for identity confirmation, our test for signing up a user will have to change.
114
+ It shouldn't. The way we sign up users should not change according to the solution we chose to
115
+ send emails.
116
+
117
+ This demonstrate that our object has too many responsibilities. If you want to write efficient,
118
+ fast, scalable, readable ... code, you should restrict your objects to one and only one responsbility.
119
+
120
+ ```ruby
121
+ class SignUpUserService
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
+ ```
132
+
133
+ Now, our service has fewer responsibilities BUT, testing will be even harder because mocking `new`
134
+ method on both "sub services" will be even more dirty than before.
135
+ We can solve this problem very easily
136
+
137
+ ```ruby
138
+ class SignUpUserService
139
+ def call(user)
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
148
+
149
+ def publish_user_statistics_service
150
+ PublishUserStatisticsService.new
151
+ end
152
+
153
+ def send_user_email_confirmation_service
154
+ SendUserEmailConfirmationService.new
155
+ end
156
+ end
157
+
158
+ ## test
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 }
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)
169
+ end
170
+
171
+ # ...
172
+ end
173
+ ```
174
+
175
+ Our tests are now much easier to write. They're also much faster because our test is very specialized
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.
180
+
181
+ Instead of having strong dependencies to other services, we can make them "weak" and increase our
182
+ code flexibility if we want to reuse it in another project.
183
+
184
+ ```ruby
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
196
+
197
+ def call(user)
198
+ user.signed_up_at = Time.now
199
+ user.save!
200
+
201
+ publish_user_statistics_service.call(user)
202
+ send_user_email_confirmation_service.call(user)
203
+ end
204
+ end
205
+ ```
206
+
207
+ Now our service is completely agnostic about which solution is used to perform the "sub tasks".
208
+ It's the responsibility of it's environment to provide this information.
209
+
210
+ But in a real world example, building such a tree is a complete nightmare and impossible to
211
+ maintain. It's where Shiringji comes to the rescue.
212
+
213
+ ## Usage
214
+
215
+ ```ruby
216
+ map = Shirinji::Map.new do
217
+ bean(:sign_up_user_service, klass: "SignUpUserService")
218
+ bean(:publish_user_statistics_service, klass: "PublishUserStatisticsService")
219
+ bean(:send_user_email_confirmation_service, klass: "SendUserEmailConfirmationService")
220
+ end
221
+
222
+ resolver = Shirinji::Resolver.new(map)
223
+
224
+ resolver.resolve(:sign_up_user_service)
225
+ #=> <#SignUpUserService @publish_user_statistics_service=<#PublishUserStatisticsService ...> ...>
226
+ ```
227
+
228
+ In this example, because `SingUpUserService` constructor parameters match beans with the same name,
229
+ Shirinji will automatically resolve them.
230
+
231
+ In a case where a parameter name match no bean, it has to be mapped explicitly.
232
+
233
+ ```ruby
234
+ map = Shirinji::Map.new do
235
+ bean(:sign_up_user_service, klass: "SignUpUserService") do
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")
242
+ end
243
+
244
+ resolver = Shirinji::Resolver.new(map)
245
+
246
+ resolver.resolve(:sign_up_user_service)
247
+ #=> <#SignUpUserService @publish_user_statistics_service=<#PublishUserStatisticsService ...> ...>
248
+ ```
249
+
250
+ Shirinji also provide a caching mecanism to achieve singleton pattern without having to implement
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.
253
+
254
+ Singleton is the default access mode for a bean.
255
+
256
+ ```ruby
257
+ map = Shirinji::Map.new do
258
+ bean(:foo, klass: 'Foo', access: :singleton) # foo is singleton
259
+ bean(:bar, klass: 'Bar', access: :instance) # bar is not
260
+ end
261
+
262
+ resolver = Shirinji::Resolver.new(map)
263
+
264
+ resolver.resolve(:foo).object_id #=> 1
265
+ resolver.resolve(:foo).object_id #=> 1
266
+
267
+ resolver.resolve(:bar).object_id #=> 2
268
+ resolver.resolve(:bar).object_id #=> 3
269
+ ```
270
+
271
+ You can also create beans that contain single values. It will help you to avoid referencing global
272
+ variables in your code.
273
+
274
+ ```ruby
275
+ map = Shirinji::Map.new do
276
+ bean(:config, value: Proc.new { Application.config })
277
+ bean(:foo, klass: 'Foo')
278
+ end
279
+
280
+ resolver = Shirinji::Resolver.new(map)
281
+
282
+ class Foo
283
+ attr_reader :config
284
+
285
+ def initialize(config:)
286
+ @config = config
287
+ end
288
+ end
289
+
290
+ resolver.resolve(:foo)
291
+ #=> <#Foo @config=<#OpenStruct ...> ...>
292
+ ```
293
+
294
+ Values can be anything. A `Proc` will be lazily evaluated. They also obey the singleton / instance
295
+ strategy.
296
+
297
+ ## Notes
298
+
299
+ - It is absolutely mandatory for your beans to be stateless to use the singleton mode. If they're
300
+ not, you will probably run into trouble as your objects behavior will depend on their history, leading
301
+ to unpredictable effects.
302
+ - Shirinji only works with named arguments. It will raise errors if you try to use it with "standard"
303
+ method arguments.
304
+
305
+ ## Contributing
306
+
307
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fdutey/shirinji.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'shirinji'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ class Attribute
5
+ attr_reader :name, :reference
6
+
7
+ def initialize(name, reference)
8
+ @name = name
9
+ @reference = reference
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ class Bean
5
+ attr_reader :name, :class_name, :value, :access, :attributes
6
+
7
+ def initialize(name, class_name: nil, value: nil, access:, &block)
8
+ check_params!(class_name, value)
9
+
10
+ @name = name
11
+ @class_name = class_name
12
+ @value = value
13
+ @access = access
14
+ @attributes = {}
15
+
16
+ instance_eval(&block) if block
17
+ end
18
+
19
+ def attr(name, ref:)
20
+ attributes[name] = Attribute.new(name, ref)
21
+ end
22
+
23
+ private
24
+
25
+ def check_params!(class_name, value)
26
+ msg = if class_name && value
27
+ 'you can use either `class_name` or `value` but not both'
28
+ elsif !class_name && !value
29
+ 'you must pass either `class_name` or `value`'
30
+ end
31
+
32
+ raise ArgumentError, msg if msg
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ class Map
5
+ attr_reader :beans
6
+
7
+ def initialize(&block)
8
+ @beans = {}
9
+
10
+ instance_eval(&block)
11
+ end
12
+
13
+ # Returns a bean based on its name
14
+ #
15
+ # @example accessing a bean
16
+ # map.get(:foo)
17
+ # #=> <#Shirinji::Bean ....>
18
+ #
19
+ # @example accessing a bean that doesn't exist
20
+ # map.get(:bar)
21
+ # #=> raises ArgumentError (unknown bean)
22
+ #
23
+ # @param name [Symbol, String] the name of the bean you want to access to
24
+ # @return [Bean] A bean with the given name or raises an error
25
+ # @raise [ArgumentError] if trying to access a bean that doesn't exist
26
+ def get(name)
27
+ bean = beans[name.to_sym]
28
+ raise ArgumentError, "Unknown bean #{name}" unless bean
29
+ bean
30
+ end
31
+
32
+ # Add a bean to the map
33
+ #
34
+ # @example build a class bean
35
+ # map.bean(:foo, klass: 'Foo', access: :singleton)
36
+ #
37
+ # @example build a class bean with attributes
38
+ # map.bean(:foo, klass: 'Foo', access: :singleton) do
39
+ # attr :bar, ref: :baz
40
+ # end
41
+ #
42
+ # @example build a value bean
43
+ # map.bean(:bar, value: 5)
44
+ #
45
+ # @example build a lazy evaluated value bean
46
+ # map.bean(:bar, value: Proc.new { 5 })
47
+ #
48
+ # @param name [Symbol] the name you want to register your bean
49
+ # @option [String] :klass the classname the bean is registering
50
+ # @option [*] :value the object registered by the bean
51
+ # @option [Symbol] :access either :singleton or :instance.
52
+ # @yield additional method to construct our bean
53
+ # @raise [ArgumentError] if trying to register a bean that already exist
54
+ def bean(name, klass: nil, value: nil, access: :singleton, &block)
55
+ name = name.to_sym
56
+
57
+ raise_if_name_already_taken!(name)
58
+
59
+ options = {
60
+ access: access,
61
+ class_name: klass ? klass.freeze : nil,
62
+ value: value
63
+ }
64
+
65
+ beans[name] = Bean.new(name, **options, &block)
66
+ end
67
+
68
+ # Scopes a given set of bean to the default options
69
+ #
70
+ # @example module
71
+ # scope(module: :Foo) do
72
+ # bean(:bar, klass: 'Bar')
73
+ # end
74
+ #
75
+ # #=> bean(:bar, klass: 'Foo::Bar')
76
+ #
77
+ # @example prefix
78
+ # scope(prefix: :foo) do
79
+ # bean(:bar, klass: 'Bar')
80
+ # end
81
+ #
82
+ # #=> bean(:foo_bar, klass: 'Bar')
83
+ #
84
+ # @example suffix
85
+ # scope(suffix: :bar) do
86
+ # bean(:foo, klass: 'Foo')
87
+ # end
88
+ #
89
+ # #=> bean(:foo_bar, klass: 'Foo')
90
+ #
91
+ # @example class suffix
92
+ # scope(klass_suffix: :Bar) do
93
+ # bean(:foo, klass: 'Foo')
94
+ # end
95
+ #
96
+ # #=> bean(:foo, klass: 'FooBar')
97
+ #
98
+ # It comes pretty handy when used with strongly normative naming
99
+ #
100
+ # @example services
101
+ # scope(module: :Services, klass_suffix: :Service, suffix: :service) do
102
+ # scope(module: :User, prefix: :user) do
103
+ # bean(:signup, klass: 'Signup')
104
+ # bean(:ban, klass: 'Ban')
105
+ # end
106
+ # end
107
+ #
108
+ # #=> bean(:user_signup_service, klass: 'Services::User::SignupService')
109
+ # #=> bean(:user_ban_service, klass: 'Services::User::BanService')
110
+ #
111
+ # @param options [Hash]
112
+ # @option options [Symbol] :module prepend module name to class name
113
+ # @option options [Symbol] :prefix prepend prefix to bean name
114
+ # @option options [Symbol] :suffix append suffix to bean name
115
+ # @option options [Symbol] :klass_suffix append suffix to class name
116
+ # @yield a standard map
117
+ def scope(**options, &block)
118
+ Scope.new(self, **options, &block)
119
+ end
120
+
121
+ private
122
+
123
+ def raise_if_name_already_taken!(name)
124
+ return unless beans[name]
125
+ msg = "A bean already exists with the following name: #{name}"
126
+ raise ArgumentError, msg
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ class Resolver
5
+ ARG_TYPES = %i[key keyreq].freeze
6
+
7
+ attr_reader :map, :singletons
8
+
9
+ def initialize(map)
10
+ @map = map
11
+ @singletons = {}
12
+ end
13
+
14
+ def resolve(name)
15
+ bean = map.get(name)
16
+
17
+ if bean.access == :singleton
18
+ single = singletons[name]
19
+ return single if single
20
+ end
21
+
22
+ resolve_bean(bean).tap do |instance|
23
+ singletons[name] = instance if bean.access == :singleton
24
+ end
25
+ end
26
+
27
+ def reset_cache
28
+ @singletons = {}
29
+ end
30
+
31
+ private
32
+
33
+ def resolve_bean(bean)
34
+ send(:"resolve_#{bean.value ? :value : :class}_bean", bean)
35
+ end
36
+
37
+ def resolve_value_bean(bean)
38
+ bean.value.is_a?(Proc) ? bean.value.call : bean.value
39
+ end
40
+
41
+ def resolve_class_bean(bean)
42
+ klass, params = resolve_class(bean)
43
+ return klass.new if params.empty?
44
+
45
+ check_params!(params)
46
+
47
+ args = params.each_with_object({}) do |(_type, arg), memo|
48
+ memo[arg] = bean(resolve_attribute(bean, arg))
49
+ end
50
+
51
+ klass.new(**args)
52
+ end
53
+
54
+ def resolve_class(bean)
55
+ klass = bean.class_name.constantize
56
+ construct = klass.instance_method(:initialize)
57
+
58
+ [klass, construct.parameters]
59
+ end
60
+
61
+ def resolve_attribute(bean, arg)
62
+ (attr = bean.attributes[arg]) ? attr.reference : arg
63
+ end
64
+
65
+ def check_params!(params)
66
+ params.each do |pair|
67
+ next if ARG_TYPES.include?(pair.first)
68
+ raise ArgumentError, 'Only key arguments are allowed'
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ class Scope
5
+ VALID_OPTIONS = %i[module prefix suffix klass_suffix].freeze
6
+
7
+ attr_reader :parent, :mod, :prefix, :suffix, :klass_suffix
8
+
9
+ def initialize(parent, **options, &block)
10
+ validate_options(options)
11
+
12
+ @parent = parent
13
+ @mod = options[:module]
14
+ @prefix = options[:prefix]
15
+ @suffix = options[:suffix]
16
+ @klass_suffix = options[:klass_suffix]
17
+
18
+ instance_eval(&block) if block
19
+ end
20
+
21
+ def bean(name, klass: nil, value: nil, access: :singleton, &block)
22
+ chunks = [mod, "#{klass}#{klass_suffix}"].compact
23
+ options = {
24
+ access: access,
25
+ klass: klass ? chunks.join('::') : nil,
26
+ value: value
27
+ }
28
+
29
+ parent.bean([prefix, name, suffix].compact.join('_'), **options, &block)
30
+ end
31
+
32
+ def scope(**options, &block)
33
+ Scope.new(self, **options, &block)
34
+ end
35
+
36
+ private
37
+
38
+ def validate_options(args)
39
+ args.each_key do |k|
40
+ next if Shirinji::Scope::VALID_OPTIONS.include?(k)
41
+ raise ArgumentError, "Unknown key #{k}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ VERSION = '0.0.1'
5
+ end
data/lib/shirinji.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shirinji
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'shirinji/version'
8
+ require 'shirinji/attribute'
9
+ require 'shirinji/bean'
10
+ require 'shirinji/map'
11
+ require 'shirinji/resolver'
12
+ require 'shirinji/scope'
data/shirinji.gemspec ADDED
@@ -0,0 +1,28 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('../lib', __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'shirinji/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'shirinji'
10
+ spec.version = Shirinji::VERSION
11
+ spec.authors = ['Florian Dutey']
12
+ spec.email = ['fdutey@gmail.com']
13
+
14
+ spec.summary = 'Dependencies injection made easy'
15
+ spec.description = 'Dependencies injections made easy for Ruby'
16
+ spec.homepage = 'https://github.com/fdutey/shirinji'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.16'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shirinji
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Florian Dutey
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-02-23 00:00:00.000000000 Z
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
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Dependencies injections made easy for Ruby
56
+ email:
57
+ - fdutey@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".rubocop.yml"
65
+ - ".ruby-gemset"
66
+ - ".ruby-version"
67
+ - ".travis.yml"
68
+ - Gemfile
69
+ - Gemfile.lock
70
+ - README.md
71
+ - Rakefile
72
+ - bin/console
73
+ - bin/setup
74
+ - lib/shirinji.rb
75
+ - lib/shirinji/attribute.rb
76
+ - lib/shirinji/bean.rb
77
+ - lib/shirinji/map.rb
78
+ - lib/shirinji/resolver.rb
79
+ - lib/shirinji/scope.rb
80
+ - lib/shirinji/version.rb
81
+ - shirinji.gemspec
82
+ homepage: https://github.com/fdutey/shirinji
83
+ licenses: []
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.6.14
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Dependencies injection made easy
105
+ test_files: []