naught 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/Gemfile +8 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.org +340 -0
- data/Rakefile +1 -0
- data/bin/autospec +16 -0
- data/bin/coderay +16 -0
- data/bin/guard +16 -0
- data/bin/htmldiff +16 -0
- data/bin/ldiff +16 -0
- data/bin/pry +16 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/bin/thor +16 -0
- data/lib/naught.rb +16 -0
- data/lib/naught/null_class_builder.rb +274 -0
- data/lib/naught/null_class_builder/commands/define_explicit_conversions.rb +30 -0
- data/lib/naught/version.rb +3 -0
- data/naught.gemspec +26 -0
- data/spec/naught_spec.rb +429 -0
- data/spec/spec_helper.rb +1 -0
- metadata +161 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-fs --color --order rand
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Avdi Grimm
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.org
ADDED
@@ -0,0 +1,340 @@
|
|
1
|
+
#+TITLE: Naught: A Ruby Null Object Library
|
2
|
+
|
3
|
+
* A quick intro to Naught
|
4
|
+
|
5
|
+
*What's all this now then?*
|
6
|
+
|
7
|
+
Naught is a toolkit for building [[http://en.wikipedia.org/wiki/Null_Object_pattern][Null Objects]] in Ruby.
|
8
|
+
|
9
|
+
*What's that supposed to mean?*
|
10
|
+
|
11
|
+
Null Objects can make your code more [[http://confidentruby.com][confident]].
|
12
|
+
|
13
|
+
Here's a method that's not very sure of itself.
|
14
|
+
|
15
|
+
#+BEGIN_SRC ruby
|
16
|
+
class Geordi
|
17
|
+
def make_it_so(logger=nil)
|
18
|
+
logger && logger.info "Reversing the flux phase capacitance!"
|
19
|
+
logger && logger.info "Bounding a tachyon particle beam off of Data's cat!"
|
20
|
+
logger && logger.warn "Warning, bogon levels are rising!"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
#+END_SRC
|
24
|
+
|
25
|
+
Now, observe as we give it a dash of confidence with the Null Object
|
26
|
+
pattern!
|
27
|
+
|
28
|
+
#+BEGIN_SRC ruby
|
29
|
+
class NullLogger
|
30
|
+
def debug(*); end
|
31
|
+
def info(*); end
|
32
|
+
def warn(*); end
|
33
|
+
def error(*); end
|
34
|
+
def fatal(*); end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Geordi
|
38
|
+
def make_it_so(logger=NullLogger.new)
|
39
|
+
logger.info "Reversing the flux phase capacitance!"
|
40
|
+
logger.info "Bounding a tachyon particle beam off of Data's cat!"
|
41
|
+
logger.warn "Warning, bogon levels are rising!"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
#+END_SRC
|
45
|
+
|
46
|
+
By providing a =NullLogger= which implements [some of] the =Logger=
|
47
|
+
interface as no-op methods, we've gotten rid of those unsightly =&&=
|
48
|
+
operators.
|
49
|
+
|
50
|
+
*That was simple enough. Why do I need a library for it?*
|
51
|
+
|
52
|
+
You don't! The Null Object pattern is a very simple one at its core.
|
53
|
+
|
54
|
+
*And yet here we are...*
|
55
|
+
|
56
|
+
Yes. While you don't /need/ a Null Object library, this one offers
|
57
|
+
some conveniences you probably won't find elsewhere.
|
58
|
+
|
59
|
+
But there's an even more important reason I wrote this library. In
|
60
|
+
the immortal last words of James T. Kirk: "It was... /fun!/"
|
61
|
+
|
62
|
+
*OK, so how do I use this thing?*
|
63
|
+
|
64
|
+
Well, what would you like to do?
|
65
|
+
|
66
|
+
*I dunno, gimme an object that responds to any message with =nil=*
|
67
|
+
|
68
|
+
Sure thing!
|
69
|
+
|
70
|
+
#+BEGIN_SRC ruby
|
71
|
+
require 'naught'
|
72
|
+
|
73
|
+
NullObject = Naught.build
|
74
|
+
|
75
|
+
null = NullObject.new
|
76
|
+
null.foo # => nil
|
77
|
+
null.bar # => nil
|
78
|
+
#+END_SRC
|
79
|
+
|
80
|
+
*That was... weird. What's with this "build" business?*
|
81
|
+
|
82
|
+
Naught is a /toolkit/ for building null object classes. It is not a
|
83
|
+
one-size-fits-all solution.
|
84
|
+
|
85
|
+
What else can I make for you?
|
86
|
+
|
87
|
+
*How about a "black hole" null object that supports infinite chaining
|
88
|
+
of methods?*
|
89
|
+
|
90
|
+
OK.
|
91
|
+
|
92
|
+
#+BEGIN_SRC ruby
|
93
|
+
require 'naught'
|
94
|
+
|
95
|
+
BlackHole = Naught.build do |b|
|
96
|
+
b.black_hole
|
97
|
+
end
|
98
|
+
|
99
|
+
null = BlackHole.new
|
100
|
+
null.foo # => <null>
|
101
|
+
null.foo.bar.baz # => <null>
|
102
|
+
null << "hello" << "world" # => <null>
|
103
|
+
#+END_SRC
|
104
|
+
|
105
|
+
*What's that "b" thing?*
|
106
|
+
|
107
|
+
That stands for "builder". Naught uses the [[http://en.wikipedia.org/wiki/Builder_pattern][Builder Pattern]] for
|
108
|
+
rolling custom null object classes.
|
109
|
+
|
110
|
+
*Whatever. What if I want a null object that has conversions to =Integer=, =String=, etc. using sensible conversions to "zero values"?*
|
111
|
+
|
112
|
+
We can do that.
|
113
|
+
|
114
|
+
#+BEGIN_SRC ruby
|
115
|
+
require 'naught'
|
116
|
+
|
117
|
+
NullObject = Naught.build do |b|
|
118
|
+
b.define_explicit_conversions
|
119
|
+
end
|
120
|
+
|
121
|
+
null = NullObject.new
|
122
|
+
|
123
|
+
null.to_s # => ""
|
124
|
+
null.to_i # => 0
|
125
|
+
null.to_f # => 0.0
|
126
|
+
null.to_a # => []
|
127
|
+
null.to_h # => {}
|
128
|
+
null.to_c # => (0+0i)
|
129
|
+
null.to_r # => (0/1)
|
130
|
+
#+END_SRC
|
131
|
+
|
132
|
+
*Ah, but what about /implicit/ conversions such as #to_str? Like what if I want a null object that implicitly splats the same way as an
|
133
|
+
empty array?*
|
134
|
+
|
135
|
+
Gotcha covered.
|
136
|
+
|
137
|
+
#+BEGIN_SRC ruby
|
138
|
+
require 'naught'
|
139
|
+
|
140
|
+
NullObject = Naught.build do |b|
|
141
|
+
b.define_implicit_conversions
|
142
|
+
end
|
143
|
+
|
144
|
+
null = NullObject.new
|
145
|
+
|
146
|
+
null.to_str # => ""
|
147
|
+
null.to_ary # => []
|
148
|
+
|
149
|
+
a, b, c = []
|
150
|
+
a # => nil
|
151
|
+
b # => nil
|
152
|
+
c # => nil
|
153
|
+
x, y, z = null
|
154
|
+
x # => nil
|
155
|
+
y # => nil
|
156
|
+
z # => nil
|
157
|
+
#+END_SRC
|
158
|
+
|
159
|
+
*How about a null object that only stubs out the methods from a specific class*
|
160
|
+
|
161
|
+
That's what =mimic= is for.
|
162
|
+
|
163
|
+
#+BEGIN_SRC ruby
|
164
|
+
require 'naught'
|
165
|
+
|
166
|
+
NullIO = Naught.build do |b|
|
167
|
+
b.mimic IO
|
168
|
+
end
|
169
|
+
|
170
|
+
null_io = NullIO.new
|
171
|
+
|
172
|
+
null_io << "foo" # => nil
|
173
|
+
null_io.readline # => nil
|
174
|
+
null_io.foobar # =>
|
175
|
+
# ~> -:11:in `<main>': undefined method `foobar' for
|
176
|
+
# <null:IO>:NullIO (NoMethodError)
|
177
|
+
#+END_SRC
|
178
|
+
|
179
|
+
There is also =impersonate= which takes =mimic= one step further. The
|
180
|
+
generated null class will be derived from the impersonated class.
|
181
|
+
This is handy when refitting legacy code that contains type checks.
|
182
|
+
|
183
|
+
#+BEGIN_SRC ruby
|
184
|
+
require 'naught'
|
185
|
+
|
186
|
+
NullIO = Naught.build do |b|
|
187
|
+
b.impersonate IO
|
188
|
+
end
|
189
|
+
|
190
|
+
null_io = NullIO.new
|
191
|
+
IO === null_io # => true
|
192
|
+
|
193
|
+
case null_io
|
194
|
+
when IO
|
195
|
+
puts "Yep, checks out!"
|
196
|
+
null_io << "some output"
|
197
|
+
else
|
198
|
+
raise "Hey, I expected an IO!"
|
199
|
+
end
|
200
|
+
# >> Yep, checks out!
|
201
|
+
#+END_SRC
|
202
|
+
|
203
|
+
*Alright smartypants. What if I want to add my own methods?*
|
204
|
+
|
205
|
+
Not a problem, just define them in the =.build= block.
|
206
|
+
|
207
|
+
#+BEGIN_SRC ruby
|
208
|
+
require 'naught'
|
209
|
+
|
210
|
+
NullObject = Naught.build do |b|
|
211
|
+
b.define_explicit_conversions
|
212
|
+
def to_s
|
213
|
+
"NOTHING TO SEE HERE MOVE ALONG"
|
214
|
+
end
|
215
|
+
|
216
|
+
def to_path
|
217
|
+
"/dev/null"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
null = NullObject.new
|
222
|
+
null.to_s # => "NOTHING TO SEE HERE MOVE ALONG"
|
223
|
+
null.to_path # => "/dev/null"
|
224
|
+
#+END_SRC
|
225
|
+
|
226
|
+
*Got anything else up your sleeve?*
|
227
|
+
|
228
|
+
Well, we can make the null class a singleton, since null objects
|
229
|
+
generally have no state.
|
230
|
+
|
231
|
+
#+BEGIN_SRC ruby
|
232
|
+
require 'naught'
|
233
|
+
|
234
|
+
NullObject = Naught.build do |b|
|
235
|
+
b.singleton
|
236
|
+
end
|
237
|
+
|
238
|
+
null = NullObject.instance
|
239
|
+
|
240
|
+
null.__id__ # => 17844080
|
241
|
+
NullObject.instance.__id__ # => 17844080
|
242
|
+
NullObject.new # =>
|
243
|
+
# ~> -:11:in `<main>': private method `new' called for
|
244
|
+
# NullObject:Class (NoMethodError)
|
245
|
+
#+END_SRC
|
246
|
+
|
247
|
+
Speaking of null objects with state, we can also enable tracing. This
|
248
|
+
is handy for playing "where'd that null come from?!" Try doing /that/
|
249
|
+
with =nil=!
|
250
|
+
|
251
|
+
#+BEGIN_SRC ruby
|
252
|
+
require 'naught'
|
253
|
+
|
254
|
+
NullObject = Naught.build do |b|
|
255
|
+
b.traceable
|
256
|
+
end
|
257
|
+
|
258
|
+
null = NullObject.new # line 7
|
259
|
+
|
260
|
+
null.__file__ # => "example.rb"
|
261
|
+
null.__line__ # => 7
|
262
|
+
#+END_SRC
|
263
|
+
|
264
|
+
We can even conditionally enable either singleton mode (for
|
265
|
+
production) or tracing (for development). Here's an example of using
|
266
|
+
the =$DEBUG= global variable (set with the =-d= option to ruby) to
|
267
|
+
choose which one.
|
268
|
+
|
269
|
+
#+BEGIN_SRC ruby
|
270
|
+
require 'naught'
|
271
|
+
|
272
|
+
NullObject = Naught.build do |b|
|
273
|
+
if $DEBUG
|
274
|
+
b.traceable
|
275
|
+
else
|
276
|
+
b.singleton
|
277
|
+
end
|
278
|
+
end
|
279
|
+
#+END_SRC
|
280
|
+
|
281
|
+
The only caveat is that when swapping between singleton and
|
282
|
+
non-singleton implementations, you should be careful to always
|
283
|
+
instantiate your null objects with =NullObject.get=, not =.new= or
|
284
|
+
=.instance=. =.get= will work whether the class is implemented as a
|
285
|
+
singleton or not.
|
286
|
+
|
287
|
+
#+BEGIN_SRC ruby
|
288
|
+
NullObject.get # => <null>
|
289
|
+
#+END_SRC
|
290
|
+
|
291
|
+
*Are you done yet?*
|
292
|
+
|
293
|
+
Just one more thing. For maximum convenience, Naught-generated null
|
294
|
+
classes also come with a full suite of conversion functions which can
|
295
|
+
be included into your classes.
|
296
|
+
|
297
|
+
#+BEGIN_SRC ruby
|
298
|
+
require 'naught'
|
299
|
+
|
300
|
+
NullObject = Naught.build
|
301
|
+
|
302
|
+
include NullObject::Conversions
|
303
|
+
|
304
|
+
# Convert nil to null objects. Everything else passes through.
|
305
|
+
Maybe(42) # => 42
|
306
|
+
Maybe(nil) # => <null>
|
307
|
+
Maybe(NullObject.get) # => <null>
|
308
|
+
Maybe{ 42 } # => 42
|
309
|
+
|
310
|
+
# Insist on a non-null (or nil) value
|
311
|
+
Just(42) # => 42
|
312
|
+
Just(nil) rescue $! # => #<ArgumentError: Null value: nil>
|
313
|
+
Just(NullObject.get) rescue $! # => #<ArgumentError: Null value: <null>>
|
314
|
+
|
315
|
+
# nils and nulls become nulls. Everything else is rejected.
|
316
|
+
Null() # => <null>
|
317
|
+
Null(42) rescue $! # => #<ArgumentError: 42 is not null!>
|
318
|
+
Null(nil) # => <null>
|
319
|
+
Null(NullObject.get) # => <null>
|
320
|
+
|
321
|
+
# Convert nulls back to nils. Everything else passes throuhgh. Useful
|
322
|
+
# for preventing null objects from "leaking" into public API return
|
323
|
+
# values.
|
324
|
+
Actual(42) # => 42
|
325
|
+
Actual(nil) # => nil
|
326
|
+
Actual(NullObject.get) # => nil
|
327
|
+
Actual { 42 } # => 42
|
328
|
+
#+END_SRC
|
329
|
+
|
330
|
+
* Requirements
|
331
|
+
|
332
|
+
- Ruby 1.9
|
333
|
+
|
334
|
+
* Contributing
|
335
|
+
|
336
|
+
- Fork, branch, submit PR, blah blah blah. Don't forget tests.
|
337
|
+
|
338
|
+
* Who's responsible
|
339
|
+
|
340
|
+
Naught is by [[http://devblog.avdi.org][Avdi Grimm]].
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/autospec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'autospec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rspec-core', 'autospec')
|
data/bin/coderay
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'coderay' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('coderay', 'coderay')
|
data/bin/guard
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'guard' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('guard', 'guard')
|