Skip to content

Instantly share code, notes, and snippets.

@sbellware
Last active August 29, 2015 14:01
Show Gist options
  • Select an option

  • Save sbellware/f46d2636da005b379ae0 to your computer and use it in GitHub Desktop.

Select an option

Save sbellware/f46d2636da005b379ae0 to your computer and use it in GitHub Desktop.
Declaring null object default dependencies using @avdi's Naught library, and templated modules
class Example
include NullDefault[:some_dependency]
include NullDefault[:some_other_dependency]
end
require 'naught'
class NullDefault < Module
def self.[](attr_name)
new attr_name
end
def initialize(attr_name)
reader_name = attr_name
writer_name = :"#{attr_name}="
var_name = :"@#{attr_name}"
mod = Module.new do
define_method reader_name do
val = instance_variable_get var_name
if val.nil?
val = Null.object
instance_variable_set var_name, val
end
val
end
define_method writer_name do |val|
instance_variable_set var_name, val
end
end
include mod
end
module Null
extend self
def object
::Naught.build.new
end
end
end
ex = Example.new
ex.some_dependency.some_method
ex.some_other_dependency.another_method
@ntl
Copy link
Copy Markdown

ntl commented May 21, 2014

Interesting approach. It does require annotations. Another way to achieve a similar result would be to implement a generic factory:

module NullDefaultBuilder
  extend self

  def build(klass, *args)
     # introspect klass.instance_method(:initialize)
     # merge klass#initialize arguments with args above, supplying Null.object when none was given
     klass.new *merged_args
  end
end

Example:

class Example
  attr :foo, :ping

  def initialize(foo, ping)
    @foo = foo
    @ping = ping
  end
end

=> example = NullDefaultBuilder.build(Example, :foo => 'BAR')
=> assert_equal 'BAR', example.foo
=> assert_kind_of Naught::BasicObject, example.ping

With this approach, you can use the NullDefaultBuilder in your tests, but if you want null objects in your production code, you have to make it explicit.

You could also create a small mix in to override the class' new method and use the builder:

class Example
  include NullDefaultBuilder.override_new

  def initialize(foo, bar)
    #etc
  end
end

Example.new('BAR')

This is one thing I really love about ruby, you can hide a bunch of explicit, decoupled code behind a small, implicit interface. Get the best of both worlds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment