shirinji 0.0.1

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 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: []