naught 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +2 -0
- data/Changelog.md +6 -0
- data/Gemfile +1 -0
- data/Guardfile +10 -0
- data/README.markdown +413 -0
- data/Rakefile +5 -0
- data/lib/naught.rb +1 -4
- data/lib/naught/null_class_builder.rb +37 -137
- data/lib/naught/null_class_builder/command.rb +20 -0
- data/lib/naught/null_class_builder/commands.rb +8 -0
- data/lib/naught/null_class_builder/commands/define_explicit_conversions.rb +13 -24
- data/lib/naught/null_class_builder/commands/define_implicit_conversions.rb +14 -0
- data/lib/naught/null_class_builder/commands/impersonate.rb +9 -0
- data/lib/naught/null_class_builder/commands/mimic.rb +40 -0
- data/lib/naught/null_class_builder/commands/pebble.rb +36 -0
- data/lib/naught/null_class_builder/commands/predicates_return.rb +47 -0
- data/lib/naught/null_class_builder/commands/singleton.rb +24 -0
- data/lib/naught/null_class_builder/commands/traceable.rb +19 -0
- data/lib/naught/null_class_builder/conversions_module.rb +57 -0
- data/lib/naught/version.rb +1 -1
- data/naught.gemspec +2 -1
- data/spec/base_object_spec.rb +47 -0
- data/spec/basic_null_object_spec.rb +35 -0
- data/spec/blackhole_spec.rb +16 -0
- data/spec/conversions_spec.rb +20 -0
- data/spec/functions/actual_spec.rb +22 -0
- data/spec/functions/just_spec.rb +22 -0
- data/spec/functions/maybe_spec.rb +35 -0
- data/spec/functions/null_spec.rb +34 -0
- data/spec/implicit_conversions_spec.rb +25 -0
- data/spec/mimic_spec.rb +122 -0
- data/spec/naught/null_object_builder/command_spec.rb +10 -0
- data/spec/naught/null_object_builder_spec.rb +31 -0
- data/spec/naught_spec.rb +77 -411
- data/spec/pebble_spec.rb +75 -0
- data/spec/predicate_spec.rb +80 -0
- data/spec/singleton_null_object_spec.rb +35 -0
- data/spec/spec_helper.rb +13 -1
- data/spec/support/convertable_null.rb +4 -0
- metadata +76 -32
- data/.rspec +0 -1
- data/README.org +0 -340
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 590655f0e3a1d6ace3edabc144bab9b497512816
|
4
|
+
data.tar.gz: 88d5907481c580e2a725f1078ac84029579a1b8e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fb7129d4cef2b7aa3440033c81abf745c094316e44b0ddee3642b73c6208f2f2411d29cce030ba183baa676001204048dcc9510347bfff7aa4c28e4c77dadca5
|
7
|
+
data.tar.gz: 0552239f6f101b7d21e4c227e34bc4feb17c510c8b57693342934064037de01f6a4c7bfe48eb9eaa9875a8f4ac00fea2caa1a73c0c0788441b416130110057da
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Changelog.md
ADDED
data/Gemfile
CHANGED
data/Guardfile
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
+
guard 'bundler' do
|
2
|
+
watch('Gemfile')
|
3
|
+
watch(/^.+\.gemspec/)
|
4
|
+
end
|
5
|
+
|
1
6
|
guard :rspec, cli: '-fs --color --order rand' do
|
2
7
|
watch(%r{^spec/.+_spec\.rb$})
|
3
8
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
4
9
|
watch('spec/spec_helper.rb') { "spec" }
|
5
10
|
end
|
11
|
+
|
12
|
+
guard 'ctags-bundler', emacs: true, src_path: ["lib", "spec/support"] do
|
13
|
+
watch(/^(lib|spec\/support)\/.*\.rb$/)
|
14
|
+
watch('Gemfile.lock')
|
15
|
+
end
|
data/README.markdown
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/avdi/naught.png?branch=master)](https://travis-ci.org/avdi/naught)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/avdi/naught.png)](https://codeclimate.com/github/avdi/naught)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/avdi/naught/badge.png?branch=master)](https://coveralls.io/r/avdi/naught?branch=master)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/naught.png)](http://badge.fury.io/rb/naught)
|
5
|
+
|
6
|
+
A quick intro to Naught
|
7
|
+
-------------------------
|
8
|
+
|
9
|
+
#### What's all this now then?
|
10
|
+
|
11
|
+
Naught is a toolkit for building [Null
|
12
|
+
Objects](http://en.wikipedia.org/wiki/Null_Object_pattern) in Ruby.
|
13
|
+
|
14
|
+
#### What's that supposed to mean?
|
15
|
+
|
16
|
+
Null Objects can make your code more
|
17
|
+
[confident](http://confidentruby.com).
|
18
|
+
|
19
|
+
Here's a method that's not very sure of itself.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Geordi
|
23
|
+
def make_it_so(logger=nil)
|
24
|
+
logger && logger.info("Reversing the flux phase capacitance!")
|
25
|
+
logger && logger.info("Bounding a tachyon particle beam off of Data's cat!")
|
26
|
+
logger && logger.warn("Warning, bogon levels are rising!")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Now, observe as we give it a dash of confidence with the Null Object
|
32
|
+
pattern!
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class NullLogger
|
36
|
+
def debug(*); end
|
37
|
+
def info(*); end
|
38
|
+
def warn(*); end
|
39
|
+
def error(*); end
|
40
|
+
def fatal(*); end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Geordi
|
44
|
+
def make_it_so(logger=NullLogger.new)
|
45
|
+
logger.info "Reversing the flux phase capacitance!"
|
46
|
+
logger.info "Bounding a tachyon particle beam off of Data's cat!"
|
47
|
+
logger.warn "Warning, bogon levels are rising!"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
By providing a `NullLogger` which implements [some of] the `Logger`
|
53
|
+
interface as no-op methods, we've gotten rid of those unsightly `&&`
|
54
|
+
operators.
|
55
|
+
|
56
|
+
#### That was simple enough. Why do I need a library for it?
|
57
|
+
|
58
|
+
You don't! The Null Object pattern is a very simple one at its core.
|
59
|
+
|
60
|
+
#### And yet here we are…
|
61
|
+
|
62
|
+
Yes. While you don't *need* a Null Object library, this one offers some
|
63
|
+
conveniences you probably won't find elsewhere.
|
64
|
+
|
65
|
+
But there's an even more important reason I wrote this library. In the
|
66
|
+
immortal last words of James T. Kirk: "It was… *fun!*"
|
67
|
+
|
68
|
+
#### OK, so how do I use this thing?
|
69
|
+
|
70
|
+
Well, what would you like to do?
|
71
|
+
|
72
|
+
#### I dunno, gimme an object that responds to any message with nil
|
73
|
+
|
74
|
+
Sure thing!
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'naught'
|
78
|
+
|
79
|
+
NullObject = Naught.build
|
80
|
+
|
81
|
+
null = NullObject.new
|
82
|
+
null.foo # => nil
|
83
|
+
null.bar # => nil
|
84
|
+
```
|
85
|
+
|
86
|
+
#### That was… weird. What's with this "build" business?
|
87
|
+
|
88
|
+
Naught is a *toolkit* for building null object classes. It is not a
|
89
|
+
one-size-fits-all solution.
|
90
|
+
|
91
|
+
What else can I make for you?
|
92
|
+
|
93
|
+
#### How about a "black hole" null object that supports infinite chaining of methods?
|
94
|
+
|
95
|
+
OK.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'naught'
|
99
|
+
|
100
|
+
BlackHole = Naught.build do |config|
|
101
|
+
config.black_hole
|
102
|
+
end
|
103
|
+
|
104
|
+
null = BlackHole.new
|
105
|
+
null.foo # => <null>
|
106
|
+
null.foo.bar.baz # => <null>
|
107
|
+
null << "hello" << "world" # => <null>
|
108
|
+
```
|
109
|
+
|
110
|
+
### What's that "config" thing?
|
111
|
+
|
112
|
+
That's what you use to customize the generated class to your
|
113
|
+
liking. Internally, Naught uses the [Builder
|
114
|
+
Pattern](http://en.wikipedia.org/wiki/Builder_pattern) to make this work..
|
115
|
+
|
116
|
+
#### Whatever. What if I want a null object that has conversions to Integer, String, etc. using sensible conversions to "zero values"?
|
117
|
+
|
118
|
+
We can do that.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require 'naught'
|
122
|
+
|
123
|
+
NullObject = Naught.build do |config|
|
124
|
+
config.define_explicit_conversions
|
125
|
+
end
|
126
|
+
|
127
|
+
null = NullObject.new
|
128
|
+
|
129
|
+
null.to_s # => ""
|
130
|
+
null.to_i # => 0
|
131
|
+
null.to_f # => 0.0
|
132
|
+
null.to_a # => []
|
133
|
+
null.to_h # => {}
|
134
|
+
null.to_c # => (0+0i)
|
135
|
+
null.to_r # => (0/1)
|
136
|
+
```
|
137
|
+
|
138
|
+
#### 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 empty array?
|
139
|
+
|
140
|
+
Gotcha covered.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
require 'naught'
|
144
|
+
|
145
|
+
NullObject = Naught.build do |config|
|
146
|
+
config.define_implicit_conversions
|
147
|
+
end
|
148
|
+
|
149
|
+
null = NullObject.new
|
150
|
+
|
151
|
+
null.to_str # => ""
|
152
|
+
null.to_ary # => []
|
153
|
+
|
154
|
+
a, b, c = []
|
155
|
+
a # => nil
|
156
|
+
b # => nil
|
157
|
+
c # => nil
|
158
|
+
x, y, z = null
|
159
|
+
x # => nil
|
160
|
+
y # => nil
|
161
|
+
z # => nil
|
162
|
+
```
|
163
|
+
|
164
|
+
#### How about a null object that only stubs out the methods from a specific class?
|
165
|
+
|
166
|
+
That's what `mimic` is for.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
require 'naught'
|
170
|
+
|
171
|
+
NullIO = Naught.build do |config|
|
172
|
+
config.mimic IO
|
173
|
+
end
|
174
|
+
|
175
|
+
null_io = NullIO.new
|
176
|
+
|
177
|
+
null_io << "foo" # => nil
|
178
|
+
null_io.readline # => nil
|
179
|
+
null_io.foobar # =>
|
180
|
+
# ~> -:11:in `<main>': undefined method `foobar' for
|
181
|
+
# <null:IO>:NullIO (NoMethodError)
|
182
|
+
```
|
183
|
+
|
184
|
+
There is also `impersonate` which takes `mimic` one step further. The
|
185
|
+
generated null class will be derived from the impersonated class. This
|
186
|
+
is handy when refitting legacy code that contains type checks.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
require 'naught'
|
190
|
+
|
191
|
+
NullIO = Naught.build do |config|
|
192
|
+
config.impersonate IO
|
193
|
+
end
|
194
|
+
|
195
|
+
null_io = NullIO.new
|
196
|
+
IO === null_io # => true
|
197
|
+
|
198
|
+
case null_io
|
199
|
+
when IO
|
200
|
+
puts "Yep, checks out!"
|
201
|
+
null_io << "some output"
|
202
|
+
else
|
203
|
+
raise "Hey, I expected an IO!"
|
204
|
+
end
|
205
|
+
# >> Yep, checks out!
|
206
|
+
```
|
207
|
+
|
208
|
+
#### Alright smartypants. What if I want to add my own methods?
|
209
|
+
|
210
|
+
Not a problem, just define them in the `.build` block.
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
require 'naught'
|
214
|
+
|
215
|
+
NullObject = Naught.build do |config|
|
216
|
+
config.define_explicit_conversions
|
217
|
+
def to_path
|
218
|
+
"/dev/null"
|
219
|
+
end
|
220
|
+
|
221
|
+
# You can override methods generated by Naught
|
222
|
+
def to_s
|
223
|
+
"NOTHING TO SEE HERE MOVE ALONG"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
null = NullObject.new
|
228
|
+
null.to_s # => "NOTHING TO SEE HERE MOVE ALONG"
|
229
|
+
null.to_path # => "/dev/null"
|
230
|
+
```
|
231
|
+
|
232
|
+
#### Got anything else up your sleeve?
|
233
|
+
|
234
|
+
Well, we can make the null class a singleton, since null objects
|
235
|
+
generally have no state.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
require 'naught'
|
239
|
+
|
240
|
+
NullObject = Naught.build do |config|
|
241
|
+
config.singleton
|
242
|
+
end
|
243
|
+
|
244
|
+
null = NullObject.instance
|
245
|
+
|
246
|
+
null.__id__ # => 17844080
|
247
|
+
NullObject.instance.__id__ # => 17844080
|
248
|
+
NullObject.new # =>
|
249
|
+
# ~> -:11:in `<main>': private method `new' called for
|
250
|
+
# NullObject:Class (NoMethodError)
|
251
|
+
```
|
252
|
+
|
253
|
+
Speaking of null objects with state, we can also enable tracing. This is
|
254
|
+
handy for playing "where'd that null come from?!" Try doing *that* with
|
255
|
+
`nil`!
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
require 'naught'
|
259
|
+
|
260
|
+
NullObject = Naught.build do |config|
|
261
|
+
config.traceable
|
262
|
+
end
|
263
|
+
|
264
|
+
null = NullObject.new # line 7
|
265
|
+
|
266
|
+
null.__file__ # => "example.rb"
|
267
|
+
null.__line__ # => 7
|
268
|
+
```
|
269
|
+
|
270
|
+
We can even conditionally enable either singleton mode (for production)
|
271
|
+
or tracing (for development). Here's an example of using the `$DEBUG`
|
272
|
+
global variable (set with the `-d` option to ruby) to choose which one.
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
require 'naught'
|
276
|
+
|
277
|
+
NullObject = Naught.build do |config|
|
278
|
+
if $DEBUG
|
279
|
+
config.traceable
|
280
|
+
else
|
281
|
+
config.singleton
|
282
|
+
end
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
The only caveat is that when swapping between singleton and
|
287
|
+
non-singleton implementations, you should be careful to always
|
288
|
+
instantiate your null objects with `NullObject.get`, not `.new` or
|
289
|
+
`.instance`. `.get` will work whether the class is implemented as a
|
290
|
+
singleton or not.
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
NullObject.get # => <null>
|
294
|
+
```
|
295
|
+
|
296
|
+
#### And if I want to know legacy code better?
|
297
|
+
|
298
|
+
Naught can make a null object behave as a pebble object.
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
require 'naught'
|
302
|
+
|
303
|
+
NullObject = Naught.build do |config|
|
304
|
+
if $DEBUG
|
305
|
+
config.pebble
|
306
|
+
else
|
307
|
+
config.black_hole
|
308
|
+
end
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
Now you can pass the pebble object to your code and see which messages are sent to the pebble.
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
null = NullObject.new
|
316
|
+
|
317
|
+
class MyConsumer < Struct.new(:producer)
|
318
|
+
def consume
|
319
|
+
producer.produce
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
MyConsumer.new(null).consume
|
324
|
+
# >> produce() from consume
|
325
|
+
# => <null>
|
326
|
+
```
|
327
|
+
|
328
|
+
#### Are you done yet?
|
329
|
+
|
330
|
+
Just one more thing. For maximum convenience, Naught-generated null
|
331
|
+
classes also come with a full suite of conversion functions which can be
|
332
|
+
included into your classes.
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
require 'naught'
|
336
|
+
|
337
|
+
NullObject = Naught.build
|
338
|
+
|
339
|
+
include NullObject::Conversions
|
340
|
+
|
341
|
+
# Convert nil to null objects. Everything else passes through.
|
342
|
+
Maybe(42) # => 42
|
343
|
+
Maybe(nil) # => <null>
|
344
|
+
Maybe(NullObject.get) # => <null>
|
345
|
+
Maybe{ 42 } # => 42
|
346
|
+
|
347
|
+
# Insist on a non-null (or nil) value
|
348
|
+
Just(42) # => 42
|
349
|
+
Just(nil) rescue $! # => #<ArgumentError: Null value: nil>
|
350
|
+
Just(NullObject.get) rescue $! # => #<ArgumentError: Null value: <null>>
|
351
|
+
|
352
|
+
# nils and nulls become nulls. Everything else is rejected.
|
353
|
+
Null() # => <null>
|
354
|
+
Null(42) rescue $! # => #<ArgumentError: 42 is not null!>
|
355
|
+
Null(nil) # => <null>
|
356
|
+
Null(NullObject.get) # => <null>
|
357
|
+
|
358
|
+
# Convert nulls back to nils. Everything else passes through. Useful
|
359
|
+
# for preventing null objects from "leaking" into public API return
|
360
|
+
# values.
|
361
|
+
Actual(42) # => 42
|
362
|
+
Actual(nil) # => nil
|
363
|
+
Actual(NullObject.get) # => nil
|
364
|
+
Actual { 42 } # => 42
|
365
|
+
```
|
366
|
+
|
367
|
+
Installation
|
368
|
+
--------------
|
369
|
+
|
370
|
+
``` {.example}
|
371
|
+
gem install naught
|
372
|
+
```
|
373
|
+
|
374
|
+
Requirements
|
375
|
+
--------------
|
376
|
+
|
377
|
+
- Ruby 1.9
|
378
|
+
|
379
|
+
Contributing
|
380
|
+
--------------
|
381
|
+
|
382
|
+
- Fork, branch, submit PR, blah blah blah. Don't forget tests.
|
383
|
+
|
384
|
+
Who's responsible
|
385
|
+
-------------------
|
386
|
+
|
387
|
+
Naught is by [Avdi Grimm](http://devblog.avdi.org/).
|
388
|
+
|
389
|
+
Prior Art
|
390
|
+
---------
|
391
|
+
|
392
|
+
This isn't the first Ruby Null Object library. Others to check out include:
|
393
|
+
|
394
|
+
- [NullAndVoid](https://github.com/jfelchner/null_and_void)
|
395
|
+
- [BlankSlate](https://github.com/saturnflyer/blank_slate)
|
396
|
+
|
397
|
+
|
398
|
+
Further reading
|
399
|
+
-----------------
|
400
|
+
|
401
|
+
- [Null Object: Something for
|
402
|
+
Nothing](http://www.two-sdg.demon.co.uk/curbralan/papers/europlop/NullObject.pdf)
|
403
|
+
(PDF) by Kevlin Henney
|
404
|
+
- [The Null Object
|
405
|
+
Pattern](http://www.cs.oberlin.edu/~jwalker/refs/woolf.ps) (PS) by
|
406
|
+
Bobby Woolf
|
407
|
+
- [NullObject](http://www.c2.com/cgi/wiki?NullObject) on WikiWiki
|
408
|
+
- [Null Object
|
409
|
+
pattern](http://en.wikipedia.org/wiki/Null_Object_pattern) on
|
410
|
+
Wikipedia
|
411
|
+
- [Null Objects and
|
412
|
+
Falsiness](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/),
|
413
|
+
by Avdi Grimm
|