scorpion-ioc 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -10
  3. data/README.md +414 -26
  4. data/circle.yml +5 -0
  5. data/config.ru +7 -0
  6. data/lib/scorpion.rb +87 -0
  7. data/lib/scorpion/attribute.rb +71 -0
  8. data/lib/scorpion/attribute_set.rb +96 -0
  9. data/lib/scorpion/error.rb +29 -0
  10. data/lib/scorpion/hunter.rb +54 -0
  11. data/lib/scorpion/hunting_map.rb +115 -0
  12. data/lib/scorpion/king.rb +150 -0
  13. data/lib/scorpion/locale/en.yml +5 -0
  14. data/lib/scorpion/nest.rb +37 -0
  15. data/lib/scorpion/prey.rb +94 -0
  16. data/lib/scorpion/prey/builder_prey.rb +32 -0
  17. data/lib/scorpion/prey/captured_prey.rb +44 -0
  18. data/lib/scorpion/prey/class_prey.rb +13 -0
  19. data/lib/scorpion/prey/hunted_prey.rb +14 -0
  20. data/lib/scorpion/prey/module_prey.rb +14 -0
  21. data/lib/scorpion/rails.rb +1 -1
  22. data/lib/scorpion/rails/controller.rb +89 -0
  23. data/lib/scorpion/version.rb +1 -1
  24. data/scorpion.gemspec +2 -0
  25. data/spec/internal/config/database.yml +3 -0
  26. data/spec/internal/config/routes.rb +3 -0
  27. data/spec/internal/db/combustion_test.sqlite +0 -0
  28. data/spec/internal/db/schema.rb +3 -0
  29. data/spec/internal/log/.gitignore +1 -0
  30. data/spec/internal/public/favicon.ico +0 -0
  31. data/spec/lib/scorpion/attribute_set_spec.rb +89 -0
  32. data/spec/lib/scorpion/attribute_spec.rb +8 -0
  33. data/spec/lib/scorpion/error_spec.rb +8 -0
  34. data/spec/lib/scorpion/hunter_spec.rb +69 -0
  35. data/spec/lib/scorpion/hunting_map_spec.rb +118 -0
  36. data/spec/lib/scorpion/{scorpion.rb → instance_spec.rb} +0 -0
  37. data/spec/lib/scorpion/king_spec.rb +129 -0
  38. data/spec/lib/scorpion/prey/module_prey_spec.rb +16 -0
  39. data/spec/lib/scorpion/prey_spec.rb +76 -0
  40. data/spec/lib/scorpion/rails/controller_spec.rb +111 -0
  41. data/spec/lib/scorpion_spec.rb +0 -1
  42. data/spec/spec_helper.rb +6 -0
  43. metadata +78 -6
  44. data/Procfile +0 -1
  45. data/lib/scorpion/rails/railtie.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e1ab58a9a8aa144a618d61c775a041eb10153ce
4
- data.tar.gz: 990f3fb1945e307e1294095f2bebfb7973733bdd
3
+ metadata.gz: 19c5f928b91989643e0db1c9fef98016958d5813
4
+ data.tar.gz: 7e03d874e6714dae07b4da0fd3481404087df760
5
5
  SHA512:
6
- metadata.gz: e0f844fe7d5e5134c455a821e92eb47e3306fb19b330656497eb4d70218958ad057d82b60757dc6127e82119a362c0eb1855b8009aa51a23c0322248a6462d7e
7
- data.tar.gz: de4b94cce3afa85209b389a3007f62224d6fd2feb9fd22f6f1a1834ab9308a4657860ff8c6dec6d6da21dc5388f764bdc755ce36b330760d44854b053393a5fc
6
+ metadata.gz: 3e8b8889dafadd148472b73921eb1a1da3af9a0f006870c083abfb9bb600b2e9b21fdb24ab696f6f258621cd4ad3d6a497e0c143817399621f6dc0f2383758ad
7
+ data.tar.gz: bfee710c8d61409b0915dc98a206e0493d9c4b30a5406f41ab1817a0077c46418b27be3d960899b6bcb1904becde4978ab5ec9d1f34b6cff21af97f062fa38fb
data/Gemfile CHANGED
@@ -3,15 +3,19 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in scorpion.gemspec
4
4
  gemspec
5
5
 
6
+ group :test do
6
7
 
7
- gem 'guard', '~> 2.12.8'
8
- gem 'spring'
9
- gem 'byebug'
10
- gem 'pry-byebug'
11
- gem 'guard-rspec'
12
- gem 'fuubar'
13
- gem 'foreman'
14
- gem 'yard'
15
- gem 'simplecov', github: "colszowka/simplecov"
8
+ gem 'guard', '~> 2.12.8'
9
+ gem 'spring'
10
+ gem 'byebug'
11
+ gem 'pry-byebug'
12
+ gem 'guard-rspec'
13
+ gem 'fuubar'
14
+ gem 'yard'
15
+ gem 'simplecov', github: "colszowka/simplecov"
16
+ gem 'ruby_gntp', '~> 0.3.4'
17
+ gem 'awesome_print'
18
+ gem 'sqlite3'
16
19
 
17
- gem "codeclimate-test-reporter", group: :test, require: nil
20
+ gem "codeclimate-test-reporter", group: :test, require: nil
21
+ end
data/README.md CHANGED
@@ -1,19 +1,425 @@
1
1
  # Scorpion
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/shog.svg)](http://badge.fury.io/rb/shog)
3
+ [![Gem Version](https://badge.fury.io/rb/scorpion-ioc.svg)](http://badge.fury.io/rb/scorpion-ioc)
4
4
  [![Code Climate](https://codeclimate.com/github/phallguy/scorpion.png)](https://codeclimate.com/github/phallguy/scorpion)
5
+ [![Test Coverage](https://codeclimate.com/github/phallguy/scorpion/badges/coverage.svg)](https://codeclimate.com/github/phallguy/scorpion/coverage)
5
6
  [![Circle CI](https://circleci.com/gh/phallguy/scorpion.svg?style=svg)](https://circleci.com/gh/phallguy/scorpion)
6
7
 
7
8
  Add IoC to rails with minimal fuss and ceremony.
8
9
 
9
- Embrace convention over configuration while still benefitting from the
10
- dependency injection design principle.
10
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
11
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
11
12
 
12
13
 
13
- ## Using Shog
14
+ - [Dependency Injection](#dependency-injection)
15
+ - [Why might you _Want_ a DI FRamework?](#why-might-you-_want_-a-di-framework)
16
+ - [Using Scorpion](#using-scorpion)
17
+ - [Kings](#kings)
18
+ - [Configuration](#configuration)
19
+ - [Nests](#nests)
20
+ - [Rails](#rails)
21
+ - [Contributing](#contributing)
22
+ - [License](#license)
14
23
 
15
- ...
24
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
16
25
 
26
+ ## Dependency Injection
27
+
28
+ Dependency injection helps to break explicit dependencies between objects making
29
+ it much easier to maintain a [single
30
+ responsibility](https://en.wikipedia.org/wiki/Single_responsibility_principle)
31
+ and reduce [coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming))
32
+ in our class designs. This leads to more testable code and code that is more
33
+ resilient to change.
34
+
35
+ Several have argued that the dynamic properties of Ruby make Dependency
36
+ Injection _frameworks_ irrelevant. Some argue that you can build in defaults and
37
+ make them overridable, or just use module mixins.
38
+
39
+ Most of these counter arguments focus on testing, and given how easy it is to
40
+ mock objects in Ruby, you don't really need a framework. If testing were the
41
+ only virtue they'd be spot on. Despite it's virtues DI doesn't come without it's
42
+ own problems. However for larger projects that you expect to be long-lived, a DI
43
+ framework may help manage the complexity.
44
+
45
+ For a deeper background on Dependency Injection consider the
46
+ [Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection) article on the
47
+ subject.
48
+
49
+ ### Why might you _Want_ a DI FRamework?
50
+
51
+ Assuming you've embraced the general concept of DI why would you want to use a
52
+ framework. Lets consider the alternatives.
53
+
54
+ ##### Setter/Default Injection
55
+
56
+ ```ruby
57
+ class Hunter
58
+ def weapon
59
+ @weapon ||= Weapon.new
60
+ end
61
+ def weapon=( value )
62
+ @weapon = value
63
+ end
64
+ end
65
+ ```
66
+
67
+ In this scenario the Hunter class knows how to create a weapon and provides a
68
+ sane default, but allows the dependency to be overridden if needed.
69
+
70
+ **PROS**
71
+
72
+ - Very simple to understand and debug
73
+ - Provides basic flexibility
74
+ - The dependency is clearly defined.
75
+
76
+ **CONS**
77
+
78
+ - Still coupled to a specific type of Weapon.
79
+ - If multiple classes use this approach and you decide to upgrade your armory,
80
+ you'd have to modify every line that creates new weapons. The factory pattern
81
+ can be used to address such a dependency.
82
+ - No global method of replacing a Weapon class with a specialized or augmented
83
+ class. For example a ThreadLockedWeapon.
84
+
85
+ ##### Constructor/Ignorant Injection
86
+
87
+ ```ruby
88
+ class Hunter
89
+ def initialize( weapon )
90
+ @weapon = weapon
91
+ end
92
+ end
93
+ ```
94
+
95
+ Here Hunters can use any weapon and can be designed to an interface Weapon that
96
+ does not have an implementation yet.
97
+
98
+ **PROS**
99
+
100
+ - Provides flexibility
101
+ - Work can proceed concurrently on Hunter and Weapon classes by different
102
+ engineers on the team.
103
+
104
+ **CONS**
105
+
106
+ - Hard to reason about Hunters and Weapons as a whole.
107
+ - The dependency is not clearly defined - what is a weapon?
108
+ - It pushes the responsibility of constructing dependencies onto the consumer of
109
+ the class. If the class is used in multiple places this becomes a maintenance
110
+ chore when changes are required.
111
+ - It becomes tedious to use classes resulting in repeated boilerplate code that
112
+ distracts from the primary responsibility of the calling code.
113
+
114
+
115
+ #### Using a Framework...like Scorpion
116
+
117
+ Using a good framework can help conserve the pros of each method while
118
+ minimizing the cons. A DI framework works like an automatic factory system
119
+ resolving dependencies cleanly like a factory but without all the effort to
120
+ create custom factories.
121
+
122
+ A good framework should
123
+
124
+ - Make dependencies clear
125
+ - Require a minimal amount of configuration or ceremony
126
+
127
+ ```ruby
128
+ class Hunter
129
+ depend_on do
130
+ weapon Weapon
131
+ end
132
+ end
133
+ ```
134
+
135
+ Here the dependency is clearly defined - and even creates accessors for getting
136
+ and setting the weapon. When a Hunter is created it's dependencies are also
137
+ created - and any of their dependencies and so on. Usage is equally simple
138
+
139
+ ```ruby
140
+ hunter = scorpion.hunt! Hunter
141
+ hunter.weapon # => a Weapon
142
+ ```
143
+
144
+ Overriding the kind of weapons used by hunters.
145
+
146
+ ```ruby
147
+ class Axe < Weapon; end
148
+
149
+ scorpion.prepare do
150
+ hunt_for Axe
151
+ end
152
+
153
+ hunter = scorpion.hunt! Hunter
154
+ hunter.weapon # => an Axe
155
+ ```
156
+
157
+ Overriding hunters!
158
+
159
+ ```ruby
160
+ class Axe < Weapon; end
161
+ class Predator < Hunter; end
162
+
163
+ scorpion.prepare do
164
+ hunt_for Predator
165
+ hunt_for Axe
166
+ end
167
+
168
+ hunter = scorpion.hunt! Hunter
169
+ hunter # => Predator
170
+ hunter.weapon # => an Axe
171
+ ```
172
+
173
+
174
+ ## Using Scorpion
175
+
176
+ Out of the box Scorpion does not need any configuration and will work
177
+ immediately. You can hunt for any Class even if it hasn't been configured.
178
+
179
+ ```ruby
180
+ hash = Scorpion.instance.hunt! Hash
181
+ hash # => {}
182
+ ```
183
+
184
+ ### Kings
185
+
186
+ Scorpions feed their [Scorpion Kings](lib/scorpion/king.rb) - any object that
187
+ should be fed its dependencies when it is created. Simply include the
188
+ Scorpion::King module into your class to benefit from Scorpion injections.
189
+
190
+ ```ruby
191
+ class Keeper
192
+ include Scorpion::King
193
+
194
+ feed_on do
195
+ lunch FastFood
196
+ end
197
+ end
198
+
199
+ class Zoo
200
+ include Scorpion::King
201
+
202
+ feed_on do # or #depend_on if you like
203
+ keeper Zoo::Keeper
204
+ vet Zoo::Vet, lazy: true
205
+ end
206
+ end
207
+
208
+ zoo = scorpion.hunt! Zoo
209
+ zoo.keeper # => an instance of a Zoo::Keeper
210
+ zoo.vet? # => false it hasn't been hunted down yet
211
+ zoo.vet # => an instnace of a Zoo::Vet
212
+ zoo.keeper.lunch # => an instance of FastFood
213
+ ```
214
+
215
+ All of your classes should be kings! And any dependency that is also a King will
216
+ be fed.
217
+
218
+ ### Configuration
219
+
220
+ A good scorpion should be prepared to hunt. An effort that describes what the
221
+ scorpion hunts for and how it should be found. Scorpion uses Classes and Modules
222
+ as the primary means of identifying prey in favor of opaque labels or strings.
223
+ This serves two benefits:
224
+
225
+ 1. The type of object expected by the dependency is clearly identified making it
226
+ easier to understand what the concrete dependencies really are.
227
+ 2. Types (Classes & Modules) explicitly declare the expected behavioral contract
228
+ of an object's dependencies.
229
+
230
+ #### Classes
231
+
232
+ Most scorpion hunts will be for an instance of a specific class (or a more
233
+ derived class). In the absence of any configuration, Scorpion will simply create
234
+ an instance of the specific class requested.
235
+
236
+ ```ruby
237
+ scorpion.hunt! Hash # => Hash.new
238
+
239
+ scorpion.prepare do
240
+ hunt_for Object::HashWithIndifferentAccess
241
+ end
242
+
243
+ scorpion.hunt! Hash # => Object::HashWithIndifferentAccess.new
244
+ ```
245
+
246
+ #### Modules
247
+
248
+ Modules can be hunted for in two ways.
249
+
250
+ 1. If a Class has been prepared for hunting that includes the module, it will
251
+ be used to satisfy requests for that module
252
+ 2. If no Class is found, the Module itself will be returned.
253
+
254
+ ```ruby
255
+ module Sharp
256
+ module_function
257
+ def poke; self.class.name end
258
+ end
259
+
260
+ class Sword
261
+ include Sharp
262
+ end
263
+
264
+ poker = scorpion.hunt! Sharp
265
+ poker.poke # => "Module"
266
+
267
+ scorpion.prepare do
268
+ hunt_for Sword
269
+ end
270
+
271
+ poker = scorpion.hunt! Sharp
272
+ poker.poke # => "Sword"
273
+ ```
274
+
275
+ #### Traits
276
+
277
+ Traits can be used to distinguish between prey of the same type. For example
278
+ a scorpion may be prepare to hunt for several weapons and the king needs a
279
+ blunt weapon.
280
+
281
+ ```ruby
282
+ class Weapon; end
283
+ class Mace < Weapon; end
284
+ class Hammer < Weapon; end
285
+ class Sword < Weapon; end
286
+
287
+ scorpion.prepare do
288
+ hunt_for Hammer, :blunt
289
+ hunt_for Sword, :sharp
290
+ hunt_for Mace, :blunt, :sharp
291
+ end
292
+
293
+ scorpion.hunt! Weapon, :blunt # => Hammer.new
294
+ scorpion.hunt! Weapon, :sharp # => Sword.new
295
+ scorpion.hunt! Weapon, :sharp, :blunt # => Mace.new
296
+ ```
297
+
298
+ Modules can also be used to identify specific traits desired from the hunted
299
+ prey.
300
+
301
+ ```ruby
302
+ module Color; end
303
+ module Streaming; end
304
+ class Logger; end
305
+ class Console < Logger
306
+ include Color
307
+ end
308
+ class SysLog < Logger
309
+ include Streaming
310
+ end
311
+
312
+ scorpion.prepare do
313
+ hunt_for Console
314
+ hunt_for SysLog
315
+ end
316
+
317
+ scorpion.hunt! Logger, Color # => Console.new
318
+ scorpion.hunt! Logger, Streaming # => SysLog.new
319
+ ```
320
+
321
+ #### Builders
322
+
323
+ Sometimes resolving the correct dependencies is a bit more dynamic. In those
324
+ cases you can use a builder block to hunt for prey.
325
+
326
+ ```ruby
327
+ class Samurai < Sword; end
328
+ class Broad < Sword; end
329
+
330
+ scorpion.prepare do
331
+ hunt_for Sword do |scorpion|
332
+ scorpion.spawn Random.rand( 2 ) == 1 ? Samurai : Broad
333
+ end
334
+ end
335
+ ```
336
+
337
+ #### Singletons
338
+
339
+ Scorpion allows you to capture prey and feed the same instance to everyone that
340
+ asks for a matching dependency.
341
+
342
+ DI singletons are different then global singletons in that each scorpion can
343
+ have a unique instance of the class that it shares with all of it's kings. This
344
+ allows, for example, global variable like support per-request without polluting
345
+ the global namespace or dealing with thread concurrency issues.
346
+
347
+
348
+ ```ruby
349
+ class Logger; end
350
+
351
+ scorpion.prepare do
352
+ capture Logger
353
+ end
354
+
355
+ scorpion.hunt! Logger # => Logger.new
356
+ scorpion.hunt! Logger # => Previously captured logger
357
+ ```
358
+
359
+ Captured dependencies are not shared with child scorpions (for example when
360
+ conceiving scorpions from a [Nest](Nests)). To share captured prey with children
361
+ use `share`.
362
+
363
+ ### Nests
364
+
365
+ A scorpion nest is where a mother scorpion lives and conceives young -
366
+ duplicates of the mother but maintaining their own state. The scorpion nest is
367
+ used by the Rails integration to give each request it's own scorpion.
368
+
369
+ All preparation performed by the mother is shared with all the children it
370
+ conceives so that configuration is established when the application starts.
371
+
372
+ ```ruby
373
+ nest.prepare do
374
+ hunt_for Logger
375
+ end
376
+
377
+ scorpion = nest.conceive
378
+ scorpion.hunt! Logger # => Logger.new
379
+ ```
380
+
381
+ ### Rails
382
+
383
+ Scorpion provides simple integration into for rails controllers to establish
384
+ a scorpion for each request.
385
+
386
+ ```ruby
387
+ # user_service.rb
388
+ class UserService
389
+ def find( username ) ... end
390
+ end
391
+
392
+ # config/nest.rb
393
+ require 'scorpion'
394
+
395
+ Scorpion.prepare do
396
+ capture UserService # Share with all the objects that are spawned in _this_ request
397
+
398
+ share do
399
+ capture Logger # Share with every request
400
+ end
401
+ end
402
+
403
+ # application_controller.rb
404
+ require 'scorpion'
405
+
406
+ class ApplicationController < ActionController::Base
407
+ include Scorpion::Rails::Controller
408
+
409
+ feed_on do
410
+ users UserService, lazy: true
411
+ end
412
+
413
+ end
414
+
415
+ # users_controller.rb
416
+ class UsersController < ApplicationController
417
+ def show
418
+ user = users.find( "batman" )
419
+ logger.write "Found a user: #{ user }"
420
+ end
421
+ end
422
+ ```
17
423
 
18
424
  ## Contributing
19
425
 
@@ -24,26 +430,8 @@ dependency injection design principle.
24
430
  5. Create a new Pull Request
25
431
 
26
432
 
27
- # License
28
-
29
- The MIT License (MIT)
30
-
31
- Copyright (c) 2015 Paul Alexander
32
-
33
- Permission is hereby granted, free of charge, to any person obtaining a copy
34
- of this software and associated documentation files (the "Software"), to deal
35
- in the Software without restriction, including without limitation the rights
36
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
- copies of the Software, and to permit persons to whom the Software is
38
- furnished to do so, subject to the following conditions:
433
+ ## License
39
434
 
40
- The above copyright notice and this permission notice shall be included in all
41
- copies or substantial portions of the Software.
435
+ [The MIT License (MIT)](http://opensource.org/licenses/MIT)
42
436
 
43
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49
- SOFTWARE.
437
+ Copyright (c) 2015 Paul Alexander