Err the Blog Atom Feed Icon
Err the Blog
Rubyisms and Railities
  • “Strut Your Structs”
    – Chris on September 26, 2006

    I am sitting in the airport right now, waiting for my twice delayed flight to board. The security man took my Scope. He does not want my teeth to be clean or my breath to be fresh. So be it.

    Here’re two classes you may or may not have seen before. Unlike, say, a small bottle of refreshing mouth wash, you can definitely take these guys on an airplane.

    Struct

    Struct is like Ruby on Rails for classes. Scaffolding. It lets you quickly cobble together a new class with some gettable-settable attributes. You can then instantiate your struct’d class and go nuts. You don’t even need to require anything—Struct is right there, ready and willing, all the time.

    When I was little we had a black lab named Tucker. He was a dog with four legs and two eyeballs.

    >> Animal = Struct.new(:name, :legs, :eyeballs)
    => Animal
    >> tucker = Animal.new('dog', 4, 2)
    => #<struct Animal name="dog", legs=4, eyeballs=2>
    >> tucker.legs
    => 4
    >> tucker.legs = 3
    => 3
    >> tucker.legs
    => 3
    

    Wow, that’s quick. We didn’t have to set up a class definition or fumble with attr_accessor. But there’s more.

    >> tucker[0]
    => "dog"
    >> tucker['name']
    => "dog"
    >> tucker[:name]
    => "dog"
    >> tucker[:name] = 'doggy'
    => "doggy"
    >> tucker.name
    => "doggy"
    

    It’s like Array and Hash got married and had a little classling. Okay, that’s not true, but it sure looks that way from the outside. I wonder if that means Struct objects are enumerable?

    >> tucker.is_a? Enumerable
    => true
    >> tucker.methods.grep /each/
    => ["each_with_index", "each", "each_pair"]
    

    How unexpected. And it’s ordered, too—the order you passed your keys when creating your struct’d class is maintained. In this case the order is name, legs, then eyeballs. Always.

    >> tucker.each_pair { |k,v| puts "key: #{k}, value: #{v}" }
    key: name, value: dog
    key: legs, value: 4
    key: eyeballs, value: 2
    => #<struct Animal name="dog", legs=4, eyeballs=2>
    >> tucker.each { |v| puts v }
    dog
    4
    2
    => #<struct Animal name="dog", legs=4, eyeballs=2>
    

    For reference:

    >> (tucker.methods & Enumerable.instance_methods).sort
    => ["all?", "any?", "collect", "detect", "each_with_index", "entries",
    "find", "find_all", "grep", "include?", "inject", "map", "max", "member?",
    "min", "partition", "reject", "select", "sort", "sort_by", "to_a", "zip"]
    

    Since a struct’d class is not actually a Hash or an Array, there are differences. A Struct instantiated object has no keys—it has members.

    >> tucker.members
    => ["name", "legs", "eyeballs"]
    >> tucker.values
    => ["doggy", 4, 2]
    

    Also: you can not add new members arbitrarily as you can with an Array or a Hash. However many members you instantiate your struct’d class with is however many you’re stuck with. As a result: no merge, update, or concat (+). This is a class, after all. Not a fancy faux-primitive.

    If you need to turn a Struct object into a Hash, here’s an easy way:

    >> hash = Hash[*tucker.members.zip(tucker.values).flatten]
    => {"name"=>"doggy", "eyeballs"=>2, "legs"=>4}
    

    Maybe wrap that in a to_hash if you’d like. For to_a, just use values.

    It’s worth noting that since everything in Ruby is an object, we can inherit straight from a call to Struct.new. Maybe we want to add some questions?

    class Primate < Struct.new(:name, :legs, :eyeballs)
      def legs?
        (legs && legs.to_i > 0) ? true : false
      end
    end
    

    Imagine the possibilities. Fake-out ActiveRecord objects in your tests! Mock hashes that are super indifferent! Create resources which can only be described as active! Amaze your friends!

    OpenStruct

    OpenStruct is a bit more liberal than Struct, with less structure. Like that hippie cousin everyone has. (in my family it’s me)

    Usage: pass a hash to OpenStruct.new, receive an object with full-on getters and setters. Because this behavior is a bit suspect we must explicitly require OpenStruct (ostruct) from the standard library. Like YAML.

    >> require 'ostruct'
    => true
    >> scope = OpenStruct.new(:brand => 'Scope', :owner => 'P&G', :flavor => 'Cinnamon Ice')
    => #<OpenStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    >> scope.brand
    => "Scope"
    >> scope.brand = 'Scoped'
    => "Scoped"
    >> scope.brand
    => "Scoped"
    

    Unlike Struct, we can add new members to an OpenStruct object whenever we’d like.

    >> scope.tasty
    => nil
    >> scope.tasty = true
    => true
    >> scope.tasty
    => true
    

    This is accomplished through some method_missing-like magic. Unfortunately, this means all your NoMethodError exceptions go away.

    >> scope.something_fake
    => nil
    

    Not the biggest deal. That’s how hashes work when you try to access a non-existent key, after all. And there’s always good ol’ respond_to:

    >> scope.respond_to? :something_fake
    => false
    

    So that’s all pretty cool. I’ve been playing with this class in my code lately. Mainly in tests to fake out ActiveRecord stuffs, as I suggested with Struct. There is one major disadvantage to OpenStruct: it’s not enumerable. Less structure, see. You can not each, you can not members, you can’t even values. Well. I mean. Out of the box you can’t.

    Enum-o-Struct

    What we’re going to do is make OpenStruct enumerable by adding the each method, the members method, and including the Enumerable module.

    require 'ostruct'
    
    class EnumoStruct < OpenStruct
      include Enumerable
    
      def members
        methods(false).grep(/=/).map { |m| m[0...-1] }
      end
    
      def each
        members.each do |method|
          yield send(method)
        end
        self
      end
    end
    

    Cryptic? Hardly.

    We want an each method. How are we going to enumerate over an OpenStruct? Well, we could always just pass the return value of each getter-method to whatever block is passed to our each method. If that’s our approach, it looks like we need a way to find all the members. All the getters.

    While an OpenStruct object may have a ton of arbitrary methods, it has setters which end in an equal sign and almost always correspond to a getter. The members method here finds all methods ending in = on an object by grabbing an array of methods excluding those defined on parent/ancestor classes (methods(false)) and creating an array of methods which match a regular expression, using grep. It then strips the last character, the =, from each found method, returning our array of members.

    The members from our scope example: [“flavor”, “owner”, “brand”]

    The Enumerable module depends on your class implementing each to work its magic. The concept is simple: go through each of the member methods and call the method, passing its value into the block passed to each using yield. In this case, send(method) is the same as, for example, self.flavor or self.brand.

    Notice we return self in our each method: it’s polite for each to return self so that calls to it may be chained.

    In action:

    >> require 'enumostruct'
    => true
    >> scope = EnumoStruct.new(:brand => 'Scope', :owner => 'P&G', :flavor => 'Cinnamon Ice')
    => #<EnumoStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    >> scope.each { |v| puts "value: #{v}" }
    value: Cinnamon Ice
    value: P&G
    value: Scope
    => #<EnumoStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    

    Oh that’s delicious. Remember, now we have all the Enumerable methods.

    >> scope.map { |v| v }
    => ["Cinnamon Ice", "P&G", "Scope"]
    >> scope.detect { |v| v =~ /&/ }
    => "P&G"
    

    Hey, I liked that each_pair method that Struct gives you. Let’s add it before we go.

    class EnumoStruct < OpenStruct
      def each_pair
        members.each do |method|
          yield method, send(method)
        end
        self
      end
    end
    
    >> scope.each_pair { |k,v| puts "key: #{k}, value: #{v}" }
    key: flavor, value: Cinnamon Ice
    key: owner, value: P&G
    key: brand, value: Scope
    => #<EnumoStruct brand="Scope", owner="P&G", flavor="Cinnamon Ice">
    

    Same as each, really. The only difference is we pass the name of the current member into the block as the first argument. Cake.

    Finally, let’s be really cheap. Let’s add [] and []= to our Enum-o-Struct. Are you in?

    class EnumoStruct < OpenStruct
      def [](member)
        send(member)
      end
    
      def []=(member, value)
        send("#{member}=", value)
      end
    end
    
    >> scope['owner']
    => "P&G"
    >> scope[:website] = 'http://www.getclose.com'
    => "http://www.getclose.com"
    >> scope.website
    => "http://www.getclose.com"
    >> scope[:negative]
    => nil
    

    Now don’t go too crazy, okay? Always monkeypytch with caution.

    Bye!

    Ah, we’ve accomplished so much. My only hope is you can truly find a use for one of these humble classes. They are not flammable and pose no threat to your fellow passengers.

  • Michael Houghton, about 7 hours later:

    And I bet your Tucker would have done better on Dancing With The Stars.

    Great article.

  • evan, about 9 hours later:

    I see the fruits of our experimentation :)

  • Johannes, about 15 hours later:

    I like your blog. So many good articles. Here I find things I don’t knew -> Struct or OpenStruct.

    Thank you.

  • Stephan, about 16 hours later:

    Hi,

    cool, I knew both before, but adding methods to them never occured to me…

    Anyway, I’d expect members to return something here:

    irb(main):001:0> require 'ostruct'
    => true
    irb(main):002:0>
    irb(main):003:0*
    irb(main):004:0* class EnumoStruct < OpenStruct
    irb(main):005:1>   include Enumerable
    irb(main):006:1>
    irb(main):007:1*   def members
    irb(main):008:2>     methods(false).grep(/=/).map { |m| m[0...-1] }
    irb(main):009:2>   end
    irb(main):010:1>
    irb(main):011:1*   def each
    irb(main):012:2>     members.each do |method|
    irb(main):013:3*       yield send(method)
    irb(main):014:3>     end
    irb(main):015:2>     self
    irb(main):016:2>   end
    irb(main):017:1> end
    => nil
    irb(main):018:0> o = OpenStruct.new
    => #<openstruct>
    irb(main):019:0> o.foo= nil
    => nil
    irb(main):020:0> o.bar= :barbaz
    => :barbaz
    irb(main):021:0> o.members
    => nil

    Any ideas why it doesn’t?

  • Chris, 1 day later:

    Stephan: Use o = EnumoStruct.new. You’re defining a new class and then instantiating the old one – you need to use the EnumoStruct class you just created.

  • Jay Fields, 1 day later:

    OpenStruct is cool, but watch for gotchas with freeze and using attribute names that are already defined methods.

  • Stephan, 1 day later:

    Oh dear, apparently I’m too used to opening existing classes already…

  • Niko, 6 months later:

    I had problems getting the members-thing to work:

    p.neu => ” asdasd fasd fasdfasdfasdf adsfasdfasdfasdfasdfasdfasd fadf ads fa” p.methods(false) => []

    And then I discovered marshal_dump:

    p.marshal_dump => {:bla=>”lasdkh”, :neu=>”adsfasdfasdfasdfasdfasdfasd fadf ads fa”} p.marshal_dump.each{|a,b| puts ”#{a}—#{b}”} bla—lasdkh neu—adsfasdfasdfasdfasdfasdfasd fadf ads fa

    That does it for me.

  • andrew, about 1 year later:

    how about this instead

    class OpenStruct def table; @table; end end

    so…

    o = OpenStruct.new o.a = 1 o.b = 2 o.table.each_pair do etc…

  • Mark, about 1 year later:

    Hmm, if I place all those methods in one class the two [] methods return nil which makes me think where the enumerable is included makes a big difference. Has any one got all these methods to work in one class?

    Cheers

  • Ten people have commented.
    Chime in.
    Sorry, no more comments :(
This is Err, the weblog of PJ Hyett and Chris Wanstrath.
All original content copyright ©2006-2008 the aforementioned.