Last active
December 21, 2015 12:08
-
-
Save t2/6303285 to your computer and use it in GitHub Desktop.
Simple example of using method missing to dynamically build out an object.
This file contains hidden or 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
# 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' | |
# 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' | |
# end # => Mr. Trenton August Kocurek I |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment