Skip to content

Instantly share code, notes, and snippets.

@t2
Last active December 21, 2015 12:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t2/6303285 to your computer and use it in GitHub Desktop.
Save t2/6303285 to your computer and use it in GitHub Desktop.
Simple example of using method missing to dynamically build out an object.
# Below I have a name class. It has no properties defined. I want to be able to dynamically
# build it into what I want. This allows me to not have to write code for every scenario, I can
# let the developer decide what it should include.
class Name
ALLOWED_NAME_PARTS = %w(prefix first middle last suffix)
def initialize(&block)
self.instance_eval(&block) if block_given?
end
def print
puts ALLOWED_NAME_PARTS.map{ |name_part| eval("@#{name_part}") }.compact.join(' ')
end
# This is the workhorse of the example. Any method that does not exist on the object
# will be passed to method_missing. You can then determine what should be done with that
# method and it's arguments.
# Notice how I am checking to see if the method they passed in is one that I allow. If it is, I send the attr_accessor method to
# my class with the method_name as an argument. This is dynamically doing the same as:
#
# attr_accessor :method_name
#
# I then use the instance_variable_set method to set my newly created property with the
# argument passed in.
def method_missing(method_name, *args)
if ALLOWED_NAME_PARTS.include? method_name.to_s
self.class.send(:attr_accessor, method_name)
instance_variable_set("@#{method_name}", args.first)
else
puts "'#{method_name}' is not an allowed name part. Options are: #{ALLOWED_NAME_PARTS.inspect}"
end
end
end
# All of the above code allows me to then create a new name object dynamically. Then call any methods on the
# newly created object by just passing them into the block.
# Notice I am not having to call self in front of any of the methods in the block. Passing this block to instance_eval in the
# object's initialize method runs the block in the context of our name instance.
# ex1
#
# trent = Name.new do
# first 'Trenton'
# last 'Kocurek'
# middle 'August'
# end
# trent.print # => Trenton August Kocurek
# ex2
#
# Name.new do
# first 'Trenton'
# middle 'August'
# suffix 'I'
# print
# end # => Trenton August I
# ex 3 - use a method to handle the name. It's cleaner
#
# def new_name(&block)
# Name.new(&block)
# end
# new_name do
# prefix 'Mr.'
# first 'Trenton'
# middle 'August'
# last 'Kocurek'
# suffix 'I'
# print
# end # => Mr. Trenton August Kocurek I
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment