437 lines
11 KiB
Markdown
437 lines
11 KiB
Markdown
[![Build Status](https://travis-ci.org/avdi/naught.png?branch=master)](https://travis-ci.org/avdi/naught)
|
|
[![Code Climate](https://codeclimate.com/github/avdi/naught.png)](https://codeclimate.com/github/avdi/naught)
|
|
[![Coverage Status](https://coveralls.io/repos/avdi/naught/badge.png?branch=master)](https://coveralls.io/r/avdi/naught?branch=master)
|
|
[![Gem Version](https://badge.fury.io/rb/naught.png)](http://badge.fury.io/rb/naught)
|
|
|
|
A quick intro to Naught
|
|
-------------------------
|
|
|
|
#### What's all this now then?
|
|
|
|
Naught is a toolkit for building [Null
|
|
Objects](http://en.wikipedia.org/wiki/Null_Object_pattern) in Ruby.
|
|
|
|
#### What's that supposed to mean?
|
|
|
|
Null Objects can make your code more
|
|
[confident](http://confidentruby.com).
|
|
|
|
Here's a method that's not very sure of itself.
|
|
|
|
```ruby
|
|
class Geordi
|
|
def make_it_so(logger=nil)
|
|
logger && logger.info("Reversing the flux phase capacitance!")
|
|
logger && logger.info("Bounding a tachyon particle beam off of Data's cat!")
|
|
logger && logger.warn("Warning, bogon levels are rising!")
|
|
end
|
|
end
|
|
```
|
|
|
|
Now, observe as we give it a dash of confidence with the Null Object
|
|
pattern!
|
|
|
|
```ruby
|
|
class NullLogger
|
|
def debug(*); end
|
|
def info(*); end
|
|
def warn(*); end
|
|
def error(*); end
|
|
def fatal(*); end
|
|
end
|
|
|
|
class Geordi
|
|
def make_it_so(logger=NullLogger.new)
|
|
logger.info "Reversing the flux phase capacitance!"
|
|
logger.info "Bounding a tachyon particle beam off of Data's cat!"
|
|
logger.warn "Warning, bogon levels are rising!"
|
|
end
|
|
end
|
|
```
|
|
|
|
By providing a `NullLogger` which implements [some of] the `Logger`
|
|
interface as no-op methods, we've gotten rid of those unsightly `&&`
|
|
operators.
|
|
|
|
#### That was simple enough. Why do I need a library for it?
|
|
|
|
You don't! The Null Object pattern is a very simple one at its core.
|
|
|
|
#### And yet here we are…
|
|
|
|
Yes. While you don't *need* a Null Object library, this one offers some
|
|
conveniences you probably won't find elsewhere.
|
|
|
|
But there's an even more important reason I wrote this library. In the
|
|
immortal last words of James T. Kirk: "It was… *fun!*"
|
|
|
|
#### OK, so how do I use this thing?
|
|
|
|
Well, what would you like to do?
|
|
|
|
#### I dunno, gimme an object that responds to any message with nil
|
|
|
|
Sure thing!
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build
|
|
|
|
null = NullObject.new
|
|
null.foo # => nil
|
|
null.bar # => nil
|
|
```
|
|
|
|
#### That was… weird. What's with this "build" business?
|
|
|
|
Naught is a *toolkit* for building null object classes. It is not a
|
|
one-size-fits-all solution.
|
|
|
|
What else can I make for you?
|
|
|
|
#### How about a "black hole" null object that supports infinite chaining of methods?
|
|
|
|
OK.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
BlackHole = Naught.build do |config|
|
|
config.black_hole
|
|
end
|
|
|
|
null = BlackHole.new
|
|
null.foo # => <null>
|
|
null.foo.bar.baz # => <null>
|
|
null << "hello" << "world" # => <null>
|
|
```
|
|
|
|
#### What's that "config" thing?
|
|
|
|
That's what you use to customize the generated class to your
|
|
liking. Internally, Naught uses the [Builder
|
|
Pattern](http://en.wikipedia.org/wiki/Builder_pattern) to make this work..
|
|
|
|
#### Whatever. What if I want a null object that has conversions to Integer, String, etc. using sensible conversions to "zero values"?
|
|
|
|
We can do that.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
config.define_explicit_conversions
|
|
end
|
|
|
|
null = NullObject.new
|
|
|
|
null.to_s # => ""
|
|
null.to_i # => 0
|
|
null.to_f # => 0.0
|
|
null.to_a # => []
|
|
null.to_h # => {}
|
|
null.to_c # => (0+0i)
|
|
null.to_r # => (0/1)
|
|
```
|
|
|
|
#### 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?
|
|
|
|
Gotcha covered.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
config.define_implicit_conversions
|
|
end
|
|
|
|
null = NullObject.new
|
|
|
|
null.to_str # => ""
|
|
null.to_ary # => []
|
|
|
|
a, b, c = []
|
|
a # => nil
|
|
b # => nil
|
|
c # => nil
|
|
x, y, z = null
|
|
x # => nil
|
|
y # => nil
|
|
z # => nil
|
|
```
|
|
|
|
#### How about a null object that only stubs out the methods from a specific class?
|
|
|
|
That's what `mimic` is for.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullIO = Naught.build do |config|
|
|
config.mimic IO
|
|
end
|
|
|
|
null_io = NullIO.new
|
|
|
|
null_io << "foo" # => nil
|
|
null_io.readline # => nil
|
|
null_io.foobar # =>
|
|
# ~> -:11:in `<main>': undefined method `foobar' for
|
|
# <null:IO>:NullIO (NoMethodError)
|
|
```
|
|
|
|
There is also `impersonate` which takes `mimic` one step further. The
|
|
generated null class will be derived from the impersonated class. This
|
|
is handy when refitting legacy code that contains type checks.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullIO = Naught.build do |config|
|
|
config.impersonate IO
|
|
end
|
|
|
|
null_io = NullIO.new
|
|
IO === null_io # => true
|
|
|
|
case null_io
|
|
when IO
|
|
puts "Yep, checks out!"
|
|
null_io << "some output"
|
|
else
|
|
raise "Hey, I expected an IO!"
|
|
end
|
|
# >> Yep, checks out!
|
|
```
|
|
|
|
#### What about predicate methods? You know, the ones that end with question marks? Shouldn't they return `false` instead of `nil`?
|
|
|
|
Sure, if you'd like.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
config.predicates_return false
|
|
end
|
|
|
|
null = NullObject.new
|
|
null.foo # => nil
|
|
null.bar? # => false
|
|
null.nil? # => false
|
|
```
|
|
|
|
#### Alright smartypants. What if I want to add my own methods?
|
|
|
|
Not a problem, just define them in the `.build` block.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
config.define_explicit_conversions
|
|
config.predicates_return false
|
|
def to_path
|
|
"/dev/null"
|
|
end
|
|
|
|
# You can override methods generated by Naught
|
|
def to_s
|
|
"NOTHING TO SEE HERE MOVE ALONG"
|
|
end
|
|
|
|
def nil?
|
|
true
|
|
end
|
|
end
|
|
|
|
null = NullObject.new
|
|
null.to_path # => "/dev/null"
|
|
null.to_s # => "NOTHING TO SEE HERE MOVE ALONG"
|
|
null.nil? # => true
|
|
```
|
|
|
|
#### Got anything else up your sleeve?
|
|
|
|
Well, we can make the null class a singleton, since null objects
|
|
generally have no state.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
config.singleton
|
|
end
|
|
|
|
null = NullObject.instance
|
|
|
|
null.__id__ # => 17844080
|
|
NullObject.instance.__id__ # => 17844080
|
|
NullObject.new # =>
|
|
# ~> -:11:in `<main>': private method `new' called for
|
|
# NullObject:Class (NoMethodError)
|
|
```
|
|
|
|
Speaking of null objects with state, we can also enable tracing. This is
|
|
handy for playing "where'd that null come from?!" Try doing *that* with
|
|
`nil`!
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
config.traceable
|
|
end
|
|
|
|
null = NullObject.new # line 7
|
|
|
|
null.__file__ # => "example.rb"
|
|
null.__line__ # => 7
|
|
```
|
|
|
|
We can even conditionally enable either singleton mode (for production)
|
|
or tracing (for development). Here's an example of using the `$DEBUG`
|
|
global variable (set with the `-d` option to ruby) to choose which one.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
if $DEBUG
|
|
config.traceable
|
|
else
|
|
config.singleton
|
|
end
|
|
end
|
|
```
|
|
|
|
The only caveat is that when swapping between singleton and
|
|
non-singleton implementations, you should be careful to always
|
|
instantiate your null objects with `NullObject.get`, not `.new` or
|
|
`.instance`. `.get` will work whether the class is implemented as a
|
|
singleton or not.
|
|
|
|
```ruby
|
|
NullObject.get # => <null>
|
|
```
|
|
|
|
#### And if I want to know legacy code better?
|
|
|
|
Naught can make a null object behave as a pebble object.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build do |config|
|
|
if $DEBUG
|
|
config.pebble
|
|
else
|
|
config.black_hole
|
|
end
|
|
end
|
|
```
|
|
|
|
Now you can pass the pebble object to your code and see which messages are sent to the pebble.
|
|
|
|
```ruby
|
|
null = NullObject.new
|
|
|
|
class MyConsumer < Struct.new(:producer)
|
|
def consume
|
|
producer.produce
|
|
end
|
|
end
|
|
|
|
MyConsumer.new(null).consume
|
|
# >> produce() from consume
|
|
# => <null>
|
|
```
|
|
|
|
#### Are you done yet?
|
|
|
|
Just one more thing. For maximum convenience, Naught-generated null
|
|
classes also come with a full suite of conversion functions which can be
|
|
included into your classes.
|
|
|
|
```ruby
|
|
require 'naught'
|
|
|
|
NullObject = Naught.build
|
|
|
|
include NullObject::Conversions
|
|
|
|
# Convert nil to null objects. Everything else passes through.
|
|
Maybe(42) # => 42
|
|
Maybe(nil) # => <null>
|
|
Maybe(NullObject.get) # => <null>
|
|
Maybe{ 42 } # => 42
|
|
|
|
# Insist on a non-null (or nil) value
|
|
Just(42) # => 42
|
|
Just(nil) rescue $! # => #<ArgumentError: Null value: nil>
|
|
Just(NullObject.get) rescue $! # => #<ArgumentError: Null value: <null>>
|
|
|
|
# nils and nulls become nulls. Everything else is rejected.
|
|
Null() # => <null>
|
|
Null(42) rescue $! # => #<ArgumentError: 42 is not null!>
|
|
Null(nil) # => <null>
|
|
Null(NullObject.get) # => <null>
|
|
|
|
# Convert nulls back to nils. Everything else passes through. Useful
|
|
# for preventing null objects from "leaking" into public API return
|
|
# values.
|
|
Actual(42) # => 42
|
|
Actual(nil) # => nil
|
|
Actual(NullObject.get) # => nil
|
|
Actual { 42 } # => 42
|
|
```
|
|
|
|
Installation
|
|
--------------
|
|
|
|
``` {.example}
|
|
gem install naught
|
|
```
|
|
|
|
Requirements
|
|
--------------
|
|
|
|
- Ruby 1.9
|
|
|
|
Contributing
|
|
--------------
|
|
|
|
- Fork, branch, submit PR, blah blah blah. Don't forget tests.
|
|
|
|
Who's responsible
|
|
-------------------
|
|
|
|
Naught is by [Avdi Grimm](http://devblog.avdi.org/).
|
|
|
|
Prior Art
|
|
---------
|
|
|
|
This isn't the first Ruby Null Object library. Others to check out include:
|
|
|
|
- [NullAndVoid](https://github.com/jfelchner/null_and_void)
|
|
- [BlankSlate](https://github.com/saturnflyer/blank_slate)
|
|
|
|
|
|
Further reading
|
|
-----------------
|
|
|
|
- [Null Object: Something for
|
|
Nothing](http://www.two-sdg.demon.co.uk/curbralan/papers/europlop/NullObject.pdf)
|
|
(PDF) by Kevlin Henney
|
|
- [The Null Object
|
|
Pattern](http://www.cs.oberlin.edu/~jwalker/refs/woolf.ps) (PS) by
|
|
Bobby Woolf
|
|
- [NullObject](http://www.c2.com/cgi/wiki?NullObject) on WikiWiki
|
|
- [Null Object
|
|
pattern](http://en.wikipedia.org/wiki/Null_Object_pattern) on
|
|
Wikipedia
|
|
- [Null Objects and
|
|
Falsiness](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/),
|
|
by Avdi Grimm
|