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 ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ /naught.org
19
+ /naught.html
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -fs --color --order rand
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in naught.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "libnotify"
8
+ end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cli: '-fs --color --order rand' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
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')