naught 0.0.1
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.
- 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')
|