Quickie Objects in Ruby

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.

5 comments

  1. 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 vs o = 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).

  2. 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 vs o = 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).

  3. #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

Leave a Reply

Your email address will not be published. Required fields are marked *