naught 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +2 -0
  4. data/Changelog.md +6 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +10 -0
  7. data/README.markdown +413 -0
  8. data/Rakefile +5 -0
  9. data/lib/naught.rb +1 -4
  10. data/lib/naught/null_class_builder.rb +37 -137
  11. data/lib/naught/null_class_builder/command.rb +20 -0
  12. data/lib/naught/null_class_builder/commands.rb +8 -0
  13. data/lib/naught/null_class_builder/commands/define_explicit_conversions.rb +13 -24
  14. data/lib/naught/null_class_builder/commands/define_implicit_conversions.rb +14 -0
  15. data/lib/naught/null_class_builder/commands/impersonate.rb +9 -0
  16. data/lib/naught/null_class_builder/commands/mimic.rb +40 -0
  17. data/lib/naught/null_class_builder/commands/pebble.rb +36 -0
  18. data/lib/naught/null_class_builder/commands/predicates_return.rb +47 -0
  19. data/lib/naught/null_class_builder/commands/singleton.rb +24 -0
  20. data/lib/naught/null_class_builder/commands/traceable.rb +19 -0
  21. data/lib/naught/null_class_builder/conversions_module.rb +57 -0
  22. data/lib/naught/version.rb +1 -1
  23. data/naught.gemspec +2 -1
  24. data/spec/base_object_spec.rb +47 -0
  25. data/spec/basic_null_object_spec.rb +35 -0
  26. data/spec/blackhole_spec.rb +16 -0
  27. data/spec/conversions_spec.rb +20 -0
  28. data/spec/functions/actual_spec.rb +22 -0
  29. data/spec/functions/just_spec.rb +22 -0
  30. data/spec/functions/maybe_spec.rb +35 -0
  31. data/spec/functions/null_spec.rb +34 -0
  32. data/spec/implicit_conversions_spec.rb +25 -0
  33. data/spec/mimic_spec.rb +122 -0
  34. data/spec/naught/null_object_builder/command_spec.rb +10 -0
  35. data/spec/naught/null_object_builder_spec.rb +31 -0
  36. data/spec/naught_spec.rb +77 -411
  37. data/spec/pebble_spec.rb +75 -0
  38. data/spec/predicate_spec.rb +80 -0
  39. data/spec/singleton_null_object_spec.rb +35 -0
  40. data/spec/spec_helper.rb +13 -1
  41. data/spec/support/convertable_null.rb +4 -0
  42. metadata +76 -32
  43. data/.rspec +0 -1
  44. data/README.org +0 -340
@@ -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
@@ -17,3 +17,7 @@ test/version_tmp
17
17
  tmp
18
18
  /naught.org
19
19
  /naught.html
20
+ /bin
21
+ /TAGS
22
+ /gems.tags
23
+ /tags
@@ -0,0 +1,2 @@
1
+ env:
2
+ - TRAVIS=true
@@ -0,0 +1,6 @@
1
+ ## 0.0.3
2
+
3
+ Features:
4
+
5
+ - New "pebble" mode
6
+
data/Gemfile CHANGED
@@ -5,4 +5,5 @@ gemspec
5
5
 
6
6
  group :test do
7
7
  gem "libnotify"
8
+ gem 'coveralls', require: false
8
9
  end
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
@@ -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