Created
August 7, 2012 11:02
-
-
Save rapimo/3284553 to your computer and use it in GitHub Desktop.
implementation for the NullObject Pattern in ActiveRecord
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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