scorpion-ioc 0.0.1 → 0.1.0

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.
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