Rob Dodson - Home

Ruby Objects and Dot Syntax

— 7 minute read
It looks like you've found one of my older posts 😅 It's possible that some of the information may be out of date (or just plain wrong!) If you've still found the post helpful, but feel like it could use some improvement, let me know on Twitter.

Coming from JavaScript I'm very accustomed to doing something like this:

var person = { name: 'Rob', city: 'San Francisco' };

console.log(person.city); // 'San Francisco'

Using dot syntax to access a Hash is second nature to me. That's why I was surprised when I ran into the following error yesterday while writing some Ruby.

person = {name: 'Rob', city: 'San Francisco'}
=> {:name=>"Rob", :city=>"San Francisco"}

puts person.city

NoMethodError: undefined method `city' for {:name=>"Rob", :city=>"San Francisco"}:Hash

"Hmm, weird," I thought. I know I've seen dot syntax used in Ruby before..what gives?

Dot Syntax and the Ruby Hash Object

As it turns out Ruby does not support dot syntax for the Hash object. If I had wanted to access the city property from my previous example I should have done it using the symbol key like so:

person[:city]
=> "San Francisco"

There are a few data structures that are very similar to Hashes and seeing those used in the wild perhaps threw me off. So I figured I'd write a post about the do's and dont's of dot syntax and how different object types react to it.

Class

The first and most obvious one is the Class object. Really I'm talking about instances of a Class here, for example an instance of class Person might have a city attribute. Here's what that would look like.

class Person
def initialize(name, city)
@name = name
@city = city
end

def name
@name
end

def city
@city
end
end

person = Person.new('Rob', 'San Francisco')
=> #<Person:0x007ff15412a8c0 @name="Rob", @city="San Francisco">

person.city
=> "San Francisco"

Since I've defined methods for both name and city, using dot syntax to access those properties basically means we're calling those methods. The methods just return the instance variables, acting as getters. You can shorten this by using attr_reader or attr_accessor.

class Person
attr_accessor :name, :city
def initialize(name, city)
@name = name
@city = city
end
end

person = Person.new('Rob', 'San Francisco')
=> #<Person:0x007ff15412a8c0 @name="Rob", @city="San Francisco">

person.city
=> "San Francisco"

Struct

The Struct object is another commonly used element which allows dot access to its attributes. Quoting from the documentation:

A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.

Examples speak louder than words so here's our Person again.

Person = Struct.new(:name, :city)
=> Person

person = Person.new('Rob', 'San Francisco')
=> #<struct Person name="Rob", city="San Francisco">

person.city
=> "San Francisco"

As I understand it a Struct is basically sealed after you've given it an initial definition. This means that you can't keep tacking on properties like you can with a Hash

# Continuing from above...

person.age = 28
NoMethodError: undefined method `age=' for #<struct Person name="Rob", city="San Francisco">

person[:age] = 28
NameError: no member 'age' in struct

OpenStruct

Finally we come to the OpenStruct which has both dynamic attributes and dot syntax. The documentation describes it like so:

An OpenStruct is a data structure, similar to a Hash, that allows the definition of arbitrary attributes with their accompanying values.

And again here is our Person from before. Note that OpenStruct needs you to require it.

require 'ostruct'

person = OpenStruct.new
=> #<OpenStruct>

person.name = 'Rob'
=> "Rob"

person.city = 'San Francisco'
=> "San Francisco"

person.city
=> "San Francisco"

If you noticed, we didn't need to define the attributes of our Person before creating an instance of it. This means we could keep adding attributes indefinitely. Want your person to respond to age? Just tack it on.

person.age = 28
=> 28

person.age
=> 28

For the sake of brevity you can pass in a Hash and OpenStruct will covert it for you.

require 'ostruct'

person = OpenStruct.new(name: 'Rob', city: 'San Francisco')
=> #<OpenStruct name="Rob", city="San Francisco">

person.city
=> "San Francisco"

This all seems wonderful but there's one huge caveat which comes from the way OpenStruct finds all of these dynamic attributes. As the documentation describes it:

An OpenStruct utilizes Ruby’s method lookup structure to and find and define the necessary methods for properties. This is accomplished through the method method_missing and define_method.

This should be a consideration if there is a concern about the performance of the objects that are created, as there is much more overhead in the setting of these properties compared to using a Hash or a Struct.

Definitely keep that in mind if you're writing time sensitive code. In those situations you'll want to use a Hash or a Struct instead.

Source:

http://www.ruby-doc.org/core-1.9.3/Class.html

http://www.ruby-doc.org/core-1.9.3/Struct.html

http://ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html

http://stackoverflow.com/questions/9356704/unable-to-use-dot-syntax-for-ruby-hash

http://www.rubyist.net/~slagell/ruby/accessors.html

You should follow me on Twitter here.

Filed under: