Skip to content

Instantly share code, notes, and snippets.

@bmc
Created August 2, 2011 15:11
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 bmc/1120383 to your computer and use it in GitHub Desktop.
Save bmc/1120383 to your computer and use it in GitHub Desktop.
Wrapping multiple objects in Ruby
class Foo
attr_reader :a, :b
def initialize
@a = 'a-Foo'
@b = 'b-Foo'
end
end
class Bar
attr_reader :a, :c
def initialize
@a = 'a-Bar'
@c = 'a-Bar'
end
end
o = OpenStruct.new(:foo => 'ostruct-Foo')
w = WrapMany.new(Foo.new, Bar.new, o)
puts w.a
puts w.b
puts w.c
puts w.foo
w = WrapMany.new(Foo.new, Bar.new, {"foo" => "hash-Foo"})
puts w.a
puts w.b
puts w.c
puts w.foo
require 'ostruct'
# Wraps multiple objects in a single object. Method calls are resolved by
# cycling through all wrapped objects, looking for the appropriate named
# method. Useful for temporarily adding methods to an instance, without
# monkeypatching the class. The class has special-case logic for OpenStruct
# and Hash objects.
#
# Example 1: Add an "age" value to a User object using an OpenStruct
#
# require 'ostruct'
# require 'wrapmany'
#
# age_holder = OpenStruct.new(:age => 43)
# u = User.find(...)
# user_with_age = WrapMany.new(u, age_holder)
#
# Example 2: Add an "age" value to a User object using a hash
#
# require 'wrapmany'
#
# u = User.find(...)
# user_with_age = WrapMany.new(u, {:age => 43})
#
# Example 3: Add an "age" value to a User object using another class
#
# require 'wrapmany'
#
# class AgeHolder
# def initialize(age)
# @age = age
# end
# end
#
# u = User.find(...)
# user_with_age = WrapMany.new(u, AgeHolder.new(43))
class WrapMany
def initialize(*args)
# Map any OpenStruct objects in the arguments to hashes, and add the
# current object (in case someone subclasses this class)
@objects = args.to_a.map {
|a| a.is_a?(OpenStruct) ? a.instance_variable_get("@table") : a
} + [self]
end
def method_missing(meth, *args, &block)
method_name = meth.to_s
# Loop through all objects, looking for something that satisfies the
# method name.
@objects.each do |o|
# First determine if the method exists as a public instance method.
# If so, call it.
if o.class.public_instance_methods.include? method_name
return o.send(method_name, *args, &block)
end
# Otherwise, if there are no arguments, then check for fields
# and hash keys
if args.length == 0
if o.instance_variables.include? ":@#{method_name}"
return o.instance_variable_get method_name
end
# Special case for hash
if o.is_a? Hash
if o.has_key? meth
return o[meth]
elsif o.has_key? method_name
return o[method_name]
end
end
end
end
raise NoMethodError.new("Undefined method: '#{method_name}'", meth)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment