deject 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ nguage: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - rbx-19mode
5
+ script: "bundle exec rspec"
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ task :default do
4
+ sh 'bundle exec rspec'
5
+ sh 'bundle exec mountain_berry_fields Readme.md.mountain_berry_fields'
6
+ end
data/Readme.md CHANGED
@@ -8,7 +8,7 @@ Install
8
8
 
9
9
  On most systems:
10
10
 
11
- $ gem install deject # coming soon
11
+ $ gem install deject
12
12
 
13
13
  On some systems:
14
14
 
@@ -19,6 +19,7 @@ If you have to use sudo and you don't know why, it's because you need to set you
19
19
  Examples
20
20
  ========
21
21
 
22
+
22
23
  Add Deject to your class.
23
24
 
24
25
  ```ruby
@@ -75,7 +76,7 @@ Game.new.with_player { MockPlayer.new }.player # => #<MockPlayer:0x007fb2a10155f
75
76
  ```
76
77
 
77
78
  Set a global default value to be used when a value isn't explicitly provided.
78
- If you are worried about clobbering a previously registered value, invoke with `:player2, safe: true`
79
+ If you are worried about clobbering a previously registered value, invoke as `.register(:player2, safe: true)`
79
80
  this is turned off by default because I found that code reloading was horking everything up.
80
81
 
81
82
  ```ruby
@@ -303,7 +304,8 @@ Deject does not litter your classes or instances with unexpected methods or vari
303
304
  Special Thanks
304
305
  ==============
305
306
 
306
- To the [8th Light](http://8thlight.com/)ers who have provided feedback, questions, and criticisms.
307
+ * To [Enova](http://www.enovafinancial.com/) for helping me find the best use cases and most relevant missing features.
308
+ * To the [8th Light](http://8thlight.com/)ers who have provided feedback, questions, and criticisms.
307
309
 
308
310
 
309
311
  Todo
@@ -0,0 +1,366 @@
1
+ About
2
+ =====
3
+
4
+ Simple dependency injection
5
+
6
+ Install
7
+ =======
8
+
9
+ On most systems:
10
+
11
+ $ gem install deject
12
+
13
+ On some systems:
14
+
15
+ $ sudo gem install deject
16
+
17
+ If you have to use sudo and you don't know why, it's because you need to set your GEM_HOME environment variable.
18
+
19
+ Examples
20
+ ========
21
+
22
+ <% setup do %>
23
+ $LOAD_PATH.unshift File.expand_path '../lib', __FILE__
24
+ require 'deject'
25
+ <% end %>
26
+
27
+ Add Deject to your class.
28
+
29
+ ```ruby
30
+ <% test 'adding Deject to your class', with: :magic_comments do %>
31
+ class Game
32
+ Deject self
33
+ end
34
+ <% end %>
35
+ ```
36
+
37
+ Declare a dependency with a default.
38
+
39
+ ```ruby
40
+ <% test 'declaring a dependency with a default', with: :magic_comments do %>
41
+ ComputerPlayer = Class.new
42
+
43
+ class Game
44
+ Deject self
45
+ dependency(:player) { ComputerPlayer.new }
46
+ end
47
+
48
+ Game.new.player # => #<ComputerPlayer:0x007fb504945f28>
49
+ <% end %>
50
+ ```
51
+
52
+ Override the dependency for the class.
53
+
54
+ ```ruby
55
+ <% test 'override the dependency for the class', with: :magic_comments do %>
56
+ ComputerPlayer = Class.new
57
+ HumanPlayer = Class.new
58
+
59
+ class Game
60
+ Deject self
61
+ dependency(:player) { ComputerPlayer.new }
62
+ end
63
+
64
+ Game.override(:player) { HumanPlayer.new } # in some initialization file
65
+
66
+ Game.new.player # => #<HumanPlayer:0x007fde2d8ac880>
67
+ <% end %>
68
+ ```
69
+
70
+ Override the dependency for an instance by using `#with_<dependency>`. You can
71
+ pass a specific object, or a block for lazy initialization. This method returns
72
+ the instance.
73
+
74
+ ```ruby
75
+ <% test 'override the dependency for the instance', with: :magic_comments do %>
76
+ ComputerPlayer = Class.new
77
+ HumanPlayer = Class.new
78
+ MockPlayer = Class.new
79
+
80
+ class Game
81
+ Deject self
82
+ dependency(:player) { ComputerPlayer.new }
83
+ end
84
+
85
+ Game.new.with_player(HumanPlayer.new).player # => #<HumanPlayer:0x007fb2a1015e40>
86
+ Game.new.with_player { MockPlayer.new }.player # => #<MockPlayer:0x007fb2a10155f8>
87
+ <% end %>
88
+ ```
89
+
90
+ Set a global default value to be used when a value isn't explicitly provided.
91
+ If you are worried about clobbering a previously registered value, invoke as `.register(:player2, safe: true)`
92
+ this is turned off by default because I found that code reloading was horking everything up.
93
+
94
+ ```ruby
95
+ <% test 'declaring defaults', with: :magic_comments do %>
96
+ ComputerPlayer = Class.new
97
+ HumanPlayer = Class.new
98
+
99
+ # these would go in some initialization file
100
+ Deject.register(:player1) { ComputerPlayer.new }
101
+ Deject.register(:player2) { ComputerPlayer.new }
102
+
103
+ class Game
104
+ Deject self
105
+ dependency(:player1) { HumanPlayer.new }
106
+ dependency :player2
107
+ end
108
+
109
+ Game.new.player1 # => #<HumanPlayer:0x007fed8212d7d0>
110
+ Game.new.player2 # => #<ComputerPlayer:0x007fed8212d348>
111
+ <% end %>
112
+ ```
113
+
114
+ Dependencies without a default block can passed to the Deject function.
115
+
116
+ ```ruby
117
+ <% test 'shorter syntax for when using default', with: :magic_comments do %>
118
+ ComputerPlayer = Class.new
119
+
120
+ class Game
121
+ Deject self, :player
122
+ end
123
+
124
+ Game.new.with_player(ComputerPlayer.new).player # => #<ComputerPlayer:0x007fac2393a248>
125
+ <% end %>
126
+ ```
127
+
128
+ Anywhere a block is used, the instance is passed to it.
129
+
130
+ ```ruby
131
+ <% test 'instances are passed to blocks', with: :magic_comments do %>
132
+ ChattyPlayer = Struct.new :message
133
+
134
+ class Game < Struct.new(:name)
135
+ Deject self
136
+ dependency(:player) { |game| ChattyPlayer.new "You're good at #{game.name}" }
137
+ end
138
+
139
+ player = Game.new('Monopoly').player
140
+ player.message # => "You're good at Monopoly"
141
+
142
+ player = Game.new('Monopoly').with_player { |game| ChattyPlayer.new "You're terrible at #{game.name}" }.player
143
+ player.message # => "You're terrible at Monopoly"
144
+ <% end %>
145
+ ```
146
+
147
+ Results are memoized.
148
+
149
+ ```ruby
150
+ <% test 'results are memoized', with: :magic_comments do %>
151
+ NamedPlayer = Struct.new :name
152
+
153
+ class Game < Struct.new(:name)
154
+ Deject self
155
+
156
+ i = 0
157
+ dependency(:player) { NamedPlayer.new "Player#{i+=1}" }
158
+ end
159
+
160
+ game = Game.new
161
+ game.player.name # => "Player1"
162
+ game.player.name # => "Player1"
163
+
164
+ Game.new.player.name # => "Player2"
165
+ <% end %>
166
+ ```
167
+
168
+
169
+ Reasons
170
+ =======
171
+
172
+
173
+ Why write this?
174
+ ---------------
175
+
176
+ Hard dependencies kick ass. They make your code clear and easy to understand.
177
+ But, of course, they're hard, you can't change them (or can't reasonably change them).
178
+ So when you go to test, it sucks. When you want to reuse, it sucks. How to get around this?
179
+ Inject your dependencies.
180
+
181
+ And while it's not the worst thing in the world to do custom dependency injection in Ruby,
182
+ it still gets obnoxious.
183
+
184
+
185
+ Example: passing dependency when initializing
186
+
187
+ ```ruby
188
+ <% test "example 1", with: :magic_comments do %>
189
+ class SomeClass
190
+ attr_accessor :some_dependency
191
+
192
+ # cannot set this unless also setting arg2
193
+ def initialize(arg1, arg2=default, some_dependency=default)
194
+ end
195
+
196
+ # cannot set arg2 without being forced to set dependency
197
+ def initialize(arg1, some_dependency=default, arg2=default)
198
+ end
199
+
200
+ # forced to deal with the dependency *every place* you use this class
201
+ def initialize(some_dependency, arg1, arg2=default)
202
+ end
203
+
204
+ # okay, this isn't too bad unless:
205
+ # 1) You want to change the default
206
+ # 2) You only have one other optional arg
207
+ # as you must degrade the interface for this new requirement
208
+ # 3) Your options aren't simple,
209
+ # (e.g. will be passed to some other class as I was dealing with when I decided to write this),
210
+ # then you will have to namespace your options and theirs
211
+ def initializing(arg1, options={})
212
+ arg2 = options.fetch(:arg2) { default }
213
+ self.some_dependency = options.fetch(:some_dependency) { default }
214
+ end
215
+ end
216
+ <% end %>
217
+ ```
218
+
219
+
220
+ Example: try to set it in a method that you change later
221
+
222
+ ```ruby
223
+ <% test "example 2", with: :magic_comments do %>
224
+ class SomeClass
225
+ class << self
226
+ attr_writer :some_dependency
227
+ def some_dependency(instance)
228
+ @some_dependency ||= default
229
+ end
230
+ end
231
+
232
+ attr_writer :some_dependency
233
+ def some_dependency
234
+ @some_dependency ||= self.class.some_dependency self
235
+ end
236
+ end
237
+
238
+ # blech, that's:
239
+ # 1) complicated -- as in difficult to easily look at and understand
240
+ # especially if you were to have more than one dependency
241
+ # 2) probably needs explicit tests given that there's quite a bit of
242
+ # indirection and behaviour going on in here
243
+ # 3) the class level override can't take into account anything unique
244
+ # about the instance (ie it must be an object, so must work for all instances)
245
+ # 4) instances must be overridden like this: instance = SomeClass.new
246
+ # instance.some_dependency = override
247
+ # instance.whatever
248
+ # whereas Deject would be like this: SomeClass.new.with_some_dependency(override).whatever
249
+ <% end %>
250
+ ```
251
+
252
+
253
+ Example: redefine the method
254
+
255
+ ```ruby
256
+ <% test 'redefining the method', with: :magic_comments do %>
257
+ class SomeClass
258
+ def some_dependency
259
+ @some_dependency ||= default
260
+ end
261
+ end
262
+
263
+ # then later in some other file, totally unbeknownst to anyone reading the above code
264
+ class SomeClass
265
+ def some_dependency
266
+ @some_dependency ||= new_default
267
+ end
268
+ end
269
+
270
+ # Want to piss off your colleagues? Imagine how long it will take them to figure out
271
+ # why this code doesn't behave as they expect. What's more, guess what happens when
272
+ # someone refactors that main class... your redefinition of some_dependency just becomes
273
+ # a definition. It doesn't fail, it has no idea about the method it's overriding,
274
+ # or the changes that happened to it.
275
+ <% end %>
276
+ ```
277
+
278
+ Compare the above examples to Deject
279
+
280
+ ```ruby
281
+ <% test 'compare to deject', with: :magic_comments do %>
282
+ class SomeClass
283
+ Deject self
284
+ dependency(:some_dependency) { |instance| default }
285
+ end
286
+
287
+ # straightforward (no one will be surprised when this changes),
288
+ # declarative so easy to understand
289
+ # convenient to override for all instances or any specific instance.
290
+ <% end %>
291
+ ```
292
+
293
+
294
+
295
+ About the Code
296
+ --------------
297
+
298
+ There have been maybe four or five implementations of Deject throughout it's life (though I think only two were ever committed to the repo).
299
+ I ultimately chose the current implementation because it was the easiest to add features to.
300
+ That said, it is not canonical Ruby style code, and will take an open mind to work with.
301
+
302
+ I intentially chose to avoid using a module because this is pervasive and widely abused in Ruby, for more, see my [blog post](http://blog.8thlight.com/josh-cheek/2012/02/03/modules-called-they-want-their-integrity-back.html).
303
+ I thought a long time about how to add the functionality, thinking about `Deject.execute` or some other verb that the Deject noun could perform.
304
+ But I couldn't think of a good one. But wait, do I _really_ need a verb? I went and re-read [Execution in the Kingdom of Nouns](http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)
305
+ and decided I was okay with having a method named after the class that applies it, hence `Deject SomeClass`. Not a usual practice
306
+ but not unheard of, and I don't think it makes sense to force an OO like interface where it doesn't fit well.
307
+
308
+ We use `with_<dependency>` instead of `dependency=` because taking blocks is grotesque with assignment methods. I have a general
309
+ disdain for assignment methods as they encourage a mindset that doesn't appreciate the advantages of OO.
310
+ _"When you have a 'setter' on an object, you have turned an object back into a data structure" -- Alan Kay_.
311
+ Furthermore, I nearly always want to be able to override the result inline, which you can't easily do with assignment methods
312
+ as the interpreter guarantees they return the RHS (best solution would be to `tap` the object).
313
+ The `with_<name>` pattern is a common pattern in [IO](http://iolanguage.com/).
314
+
315
+ In general, all variables are stored as locals in closures rather than instance variables on the object. This is
316
+ partially due to the implementation (alternative implementations used ivars), and partially because I wanted to
317
+ make a point that relying on ivars is a bad practice: You cannot change implementations (without changing all the code using the ivar)
318
+ if you use the ivar instead of the getter (e.g. switch from `attr_accessor` to a struct, or in an `ActiveRecord::Base` subclass, moving a variable
319
+ from an `attr_accessor` into the database). Furthermore, directly accessing ivars requires you to know when they were
320
+ initialized, which you should not have to deal with, and this also impedes you from extracting the variable into a
321
+ method you inherit from a module (the module can't lazily initialize it, because their methods are completely bypassed).
322
+ And it even impedes refactoring. If you previously initialized `@full_name` in the `#initialize` method, you could not then decide to
323
+ refactor `def fullname() @fullname end` into `def fullname() "#@firstname #@lastname" end` because users of
324
+ fullname aren't using the method, they're accessing the variable directly. In general, I think it is best to
325
+ encapsulate from everyone, including other methods in the same object. In Deject you don't have a choice,
326
+ you use the methods because there are no variables. If you'd like to read an argument against my position on this,
327
+ Rick Denatale summarizes Kent Beck's opinion on [ruby-talk](http://www.ruby-forum.com/topic/211544#919648).
328
+
329
+ Deject does not litter your classes or instances with unexpected methods or variables.
330
+
331
+
332
+ Special Thanks
333
+ ==============
334
+
335
+ * To [Enova](http://www.enovafinancial.com/) for helping me find the best use cases and most relevant missing features.
336
+ * To the [8th Light](http://8thlight.com/)ers who have provided feedback, questions, and criticisms.
337
+
338
+
339
+ Todo
340
+ ====
341
+
342
+ Maybe raise an error if you call `with_whatever` and pass no args.
343
+ Maybe add a setter rather than only provide the overrider.
344
+
345
+ License
346
+ =======
347
+
348
+ Copyright (c) 2012 Joshua Cheek
349
+
350
+ Permission is hereby granted, free of charge, to any person obtaining a copy
351
+ of this software and associated documentation files (the "Software"), to deal
352
+ in the Software without restriction, including without limitation the rights
353
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
354
+ copies of the Software, and to permit persons to whom the Software is
355
+ furnished to do so, subject to the following conditions:
356
+
357
+ The above copyright notice and this permission notice shall be included in
358
+ all copies or substantial portions of the Software.
359
+
360
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
361
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
362
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
363
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
364
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
365
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
366
+ THE SOFTWARE.
@@ -13,8 +13,6 @@ Gem::Specification.new do |s|
13
13
 
14
14
  s.rubyforge_project = "deject"
15
15
 
16
- s.required_ruby_version = "~> 1.9.2"
17
-
18
16
  s.files = `git ls-files`.split("\n")
19
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -23,4 +21,7 @@ Gem::Specification.new do |s|
23
21
  # specify any dependencies here; for example:
24
22
  s.add_development_dependency "rspec"
25
23
  s.add_development_dependency "pry"
24
+ s.add_development_dependency "rake"
25
+ s.add_development_dependency 'mountain_berry_fields'
26
+ s.add_development_dependency 'mountain_berry_fields-magic_comments'
26
27
  end
@@ -1,3 +1,3 @@
1
1
  module Deject
2
- VERSION = '0.2.2'
2
+ VERSION = '0.2.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deject
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-01 00:00:00.000000000 Z
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70127720072140 !ruby/object:Gem::Requirement
16
+ requirement: &70118802190260 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70127720072140
24
+ version_requirements: *70118802190260
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: pry
27
- requirement: &70127720071620 !ruby/object:Gem::Requirement
27
+ requirement: &70118802189840 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,40 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70127720071620
35
+ version_requirements: *70118802189840
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &70118802189420 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70118802189420
47
+ - !ruby/object:Gem::Dependency
48
+ name: mountain_berry_fields
49
+ requirement: &70118802189000 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70118802189000
58
+ - !ruby/object:Gem::Dependency
59
+ name: mountain_berry_fields-magic_comments
60
+ requirement: &70118802188580 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70118802188580
36
69
  description: Provides a super simple API for dependency injection
37
70
  email:
38
71
  - josh.cheek@gmail.com
@@ -41,9 +74,11 @@ extensions: []
41
74
  extra_rdoc_files: []
42
75
  files:
43
76
  - .gitignore
77
+ - .travis.yml
44
78
  - Gemfile
45
79
  - Rakefile
46
80
  - Readme.md
81
+ - Readme.md.mountain_berry_fields
47
82
  - deject.gemspec
48
83
  - lib/deject.rb
49
84
  - lib/deject/version.rb
@@ -62,9 +97,9 @@ require_paths:
62
97
  required_ruby_version: !ruby/object:Gem::Requirement
63
98
  none: false
64
99
  requirements:
65
- - - ~>
100
+ - - ! '>='
66
101
  - !ruby/object:Gem::Version
67
- version: 1.9.2
102
+ version: '0'
68
103
  required_rubygems_version: !ruby/object:Gem::Requirement
69
104
  none: false
70
105
  requirements:
@@ -73,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
108
  version: '0'
74
109
  requirements: []
75
110
  rubyforge_project: deject
76
- rubygems_version: 1.8.11
111
+ rubygems_version: 1.8.17
77
112
  signing_key:
78
113
  specification_version: 3
79
114
  summary: Simple dependency injection
@@ -84,4 +119,3 @@ test_files:
84
119
  - spec/global_registration_spec.rb
85
120
  - spec/instance_methods_spec.rb
86
121
  - spec/spec_helper.rb
87
- has_rdoc: