Skip to content

Instantly share code, notes, and snippets.

@rapimo
Created August 7, 2012 11:02
Show Gist options
  • Save rapimo/3284553 to your computer and use it in GitHub Desktop.
Save rapimo/3284553 to your computer and use it in GitHub Desktop.
implementation for the NullObject Pattern in ActiveRecord
module ActiveRecord
module NullObject #:nodoc:
module InstanceMethods
# should quark like a nil
def nil?
true
end
def present?
false
end
def empty?
true
end
def !
true
end
def save(*args)
true
end
def delete(*args)
true
end
def destroy(*args)
true
end
# but in active record context should not find anything
def id
0
end
end #InstanceMethods
module ClassMethods
ActiveRecord::FinderMethods.instance_methods.each do |finder_method|
define_method finder_method do | *args|
return self.instance
end
end
end #ClassMethods
module Has #:nodoc:
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def has_null_object(reflection_name,default_attributes=nil)
#create a new NullObjectClass for the given reflection
reflection = self.reflections[reflection_name]
raise "No Reflection #{reflection_name} found" unless reflection
unless [:has_one,:belongs_to].include? reflection.macro
raise "Found collection reflection #{reflection_name} you can only use has_one/belongs_to reflection."
end
null_obj = Class.new(reflection.klass)
null_obj.class_variable_set("@@parent_model",reflection.klass)
null_obj.send(:include,Singleton)
null_obj.extend(ActiveRecord::NullObject::ClassMethods)
null_obj.send(:include,ActiveRecord::NullObject::InstanceMethods)
# the singletone instance should be lazy initialized so that
# attributes are used as late as possible
null_obj.class_variable_set(:"@@default_attributes",default_attributes)
null_obj.class_eval <<EOF
class << self
def instance_with_default_attributes
instance_without_default_attributes.attributes = @@default_attributes
instance_without_default_attributes
end
alias_method_chain :instance, :default_attributes
end
EOF
# we give the model the parents model name in order to make search for routes
# like in link_to methods possible so link_to('not given', null_obj.instance)
# will behave same as link_to('not_given', parent_model.new)
# since the instance has the id => 0 parent_model_path(null_obj.instance)
# will result into /parent_models_controller/0 so
#
null_obj.class_eval <<EOF
class << self
def model_name
@@parent_model.model_name
end
end
EOF
#we finally give the anonymous class a nice name so we can ask is_a?(Rating::NullObjectAccount)
const_set("NullObject#{reflection.class_name}",null_obj)
define_method("#{reflection_name}_with_null_object") do
self.send("#{reflection_name}_without_null_object").nil? ? null_obj.instance : self.send("#{reflection_name}_without_null_object")
end
alias_method_chain reflection_name, :null_object
end
end #ClassMethods
end # Has
end # NullObject
end # ActiveRecord
ActiveRecord::Base.send(:include, ActiveRecord::NullObject::Has)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment