Every once in a while I find myself in the situation where I need to override a model or create an inheritance without using single table inheritance (STI).
The method_missing call can help us to create an intelligent router that will route calls to the parent class or the child while maintaining an extremely small code footprint.
Here's an example of what I'm talking about. We needed a landing page that would be dynamic for each user that wanted one. But we needed a template landing page which just needed the values substituted in at the right spot. Here's a quick Yaml of what the table might look like.
# Example Page
landing_page_template:
title: Welcome to [COMPANY-NAME]
meta_keywords: [COMPANY-NAME], [COMPANY-META-KEYWORDS]
...
# Example Landing Page
landing_page:
company_name: CV, Inc.
company_logo: www.chuckvose.com/fake_logo.gif
company_meta_keywords: happy, rails, method_missing, inheritance
...
def method_missing(method_name, args*)
if method_name is in the extended database
call method_name
if method_name isn't in the extended database
try calling parent.method_name
return results directly or mangle them somehow
class LandingPage < ActiveRecord::Base
after_initialize :find_template
# Required to activate the after_initialize filter
def after_initialize; end
private
def find_template
@template = Page.find_by_url("landing_page_template")
end
end
class LandingPage < ActiveRecord::Base
private
def method_missing(meth, *args)
# See if this attribute is in this object already.
# If so just call out to super and let
# ActiveRecord::Base deal with trying to find the
# attribute in the database.
if @attributes.include?(meth.to_s) || @attributes.include?(meth.to_s.gsub(/(=|_before_type_cast)/, ""))
super
else
# Call Page#meth and store the response. Usually something like @page.body or @page.title
resp = @template.send(meth)
# If the attribute returned from above is a String, toss it into the regex grinder
# and return the results.
if resp.is_a?(String)
return resp.html_sub(self)
# If the response is a boolean, symbol, or something else, just return it since we
# can't (and probably don't need to) regex it.
else
return resp
end
end
end
end
class String
# Look for snippets like [MERCHANT-NAME] and replace
# them with LandingPage#merchant_name
def html_sub(landing_page)
html = self # self cannot and should not be modified directly.
# Look for [WHATEVER] tags
html = html.gsub(/\[[^\]]+?\]/) do |match|
# For each match, call the LandingPage instance method
# with the same name and return those results or the empty
# string.
landing_page.send(match.gsub(/[\[\]]/, "").gsub(/-/, "_").downcase) || ""
end
end
end