Sometimes you need a quick object one-off object. Maybe you’re writing a Fake Object that needs to mimic a subset of a real library’s functionality. Ruby provides several facilities which make it easy to throw together objects without the ceremony of writing a dedicated class.
OpenStruct
If all you need is an object which responds to certain accessor calls with known values, OpenStruct may be the way to go:
require 'ostruct' photo = OpenStruct.new(:title => "Bad Wolf Bay") photo.title # => "Bad Wolf Bay"
Just be aware that OpenStruct objects are so forgiving they may not flag a typo the way you might expect:
photo.tiitle # => nil
Object
If all you need is an object with a few custom methods, you can use plain old Object. You can then adorn the blank object with singleton methods until it has the desired behavior:
flickr = Object.new def flickr.people() {'rosetyler' => OpenStruct.new(:nsid => 123)} end flickr.people # => {"rosetyler"=>#}
If you are using ActiveSupport, you may find it convenient to use #returning to build your quickie objects:
flickr = returning(Object.new) do |f| def f.people() {'rosetyler' => OpenStruct.new(:nsid => 123)} end end flickr.people['rosetyler'].nsid # => 123
Singleton Classes
If you want to do more extensive customization of your one-shot object, an alternative to singleton method definition is to use a singleton class definition:
people = returning(Object.new) do |p| class < < p attr_accessor :by_name def find_by_username(name) @by_name[name] end end p.by_name = {'rosetyler' => OpenStruct.new(:nsid => 123)} end people.find_by_username('rosetyler') # => #
Here we’re customizing the singleton class (or “eigenclass”) associated with an individual object.
All together now
Here’s some code I wrote yesterday which uses all three of these techniques to fake out a small subset of the FlickrFu library API:
def make_fake_flickr returning(Object.new) do |f| def f.people() @people ||= {'rosetyler' => OpenStruct.new(:nsid => 123)} end def f.photosets() @photosets ||= {123 => [OpenStruct.new(:title => "Bad Wolf Bay")]} end class < < f.people def find_by_username(name) self[name] end end class << f.photosets def get_list(attrs) self[attrs[:user_id]] end end end end
Note how in this example I’m tacking extra methods onto Hashes as well as plain Objects.
Conclusion
While not quite as easy as building literal arrays and hashes, Ruby lets you create quick single-use objects with little hassle. If you are only going to use an object in one place, consider eschewing a class definition and just instantiating a singleton.
Neat.
For what it's worth, I will often create a module (usually not anonymous), and then extend my object with it. Just another approach to have in your toolchest, with its own set of tradeoffs. e.g:
module FakeFlickrMethods
def people() @people ||= {'rosetyler' => OpenStruct.new(:nsid => 123)} end
def photosets() @photosets ||= {123 => [OpenStruct.new(:title => “Bad Wolf Bay”)]} end
module People; def find_by_username(name) self[name] end end
module Photosets; def get_list(attrs) self[attrs[:user_id]] end end
end
def make_fake_flickr
returning(Object.new) do |f|
f.extend FakeFlickrMethods
f.people.extend FakeFlickrMethods::People
f.photosets.extend FakeFlickrMethods::Photosets
end
end
Although, in this case, it really doesn't make much sense to use
(o = Object.new).extend FakerModule
vso = FakerClass.new
. I generally use this technique either with an anonymous module or when I only want to partially fake out an object that has a little behavior of its own already (e.g. an instance of OpenStruct, Hash, or even the normal production class).Neat.
For what it's worth, I will often create a module (usually not anonymous), and then extend my object with it. Just another approach to have in your toolchest, with its own set of tradeoffs. e.g:
module FakeFlickrMethods
def people() @people ||= {'rosetyler' => OpenStruct.new(:nsid => 123)} end
def photosets() @photosets ||= {123 => [OpenStruct.new(:title => “Bad Wolf Bay”)]} end
module People; def find_by_username(name) self[name] end end
module Photosets; def get_list(attrs) self[attrs[:user_id]] end end
end
def make_fake_flickr
returning(Object.new) do |f|
f.extend FakeFlickrMethods
f.people.extend FakeFlickrMethods::People
f.photosets.extend FakeFlickrMethods::Photosets
end
end
Although, in this case, it really doesn't make much sense to use
(o = Object.new).extend FakerModule
vso = FakerClass.new
. I generally use this technique either with an anonymous module or when I only want to partially fake out an object that has a little behavior of its own already (e.g. an instance of OpenStruct, Hash, or even the normal production class).#returning was deprecated in Rails (v3.0.9). Use #tap instead.
flickr = Object.new.tap do |f| def f.people() {‘rosetyler’ => OpenStruct.new(:nsid => 123)} endendflickr.people[‘rosetyler’].nsid # => 123