deject 0.2.2 → 0.2.3

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