Home

Dynamic Attributes on Instances in Ruby

Although it's not usually the right choice, when you want to create individual attribute behavior on instances, Ruby has your back.

If you've been around Ruby at all, you know about its getter and setter methods, available through the attr_accessor method.

It's a handy feature that lets us easily get and set values quickly on one particular attribute. Take a class that has a name attribute, like so:

class A
attr_accessor :name
end

I can get and set the name easily:

a = A.new
a.name # the getter
# => nil

a.name = 'Floyd, III' # the setter
# => 'Floyd, III'

a.name # the getter, once again
# => 'Floyd, III'

But what if we have an instance where not just our values, but our attributes will differ from instance to instance?

In almost every case I'd say, "You need a new class." If the attributes are different, then the instances should be treated differently (i.e. Duck Typing).

But when you find that instance where the purpose of your class is to be fluid, we can create individual attr_accessor behaviors on each instance. We do so by implementing [define_singleton_method].

In this example, our class receives a hash of attributes on instantiation. It then loops through those attributes and creates singleton getter and setter methods on the instance.

class Item
def initialize(attributes = {})
attributes.each do |attr, value|
# Setter
define_singleton_method("#{attr}=") { |val| attributes[attr] = val }
# Getter
define_singleton_method(attr) { attributes[attr] }
end
end
end

With this approach, any attribute passed to the class on instantiation will get its own getter and setter methods. Given the above, here's what we'd get:

a = Item.new(name: 'A')
a.name
# => 'A'

a.name = 'B'
# => 'B'

a.name
# => 'B'

Here we see if we create an object with name in the attributes hash, then we have getter and setter methods on name. But now, what if we create a new instance, b, with an age attribute but no name?

b = Item.new(age: 22)
b.age
# => 22

b.age = 23
# => 23

b.age
# => 23

b.name
# => undefined method `name' for #<Item: ...>

And that's perhaps the biggest issue with this approach and why you should avoid it without good reason—that attempting to access an attribute you didn't set will cause an error. Of course, it's Ruby, so you could protect yourself against that if necessary.

Notice that there are major limitations of this example class. This was merely to demonstrate one particular way of creating singleton attr_accessor behavior.

And again, I would encourage you to seriously consider if this approach is a good design for your program. 99.9% of the time the answer is going to be no (it's not a good idea). But Ruby's got your back when that 0.1% chance turns up.

Let's Connect

Keep Reading

Search and Replace Multiple Lines in Multiple Files in Ruby

There are many tools for searching and replacing, but most don't support changing multiple lines in one process.

Jul 02, 2018

Dynamic Pages in Middleman using Contentful

How to configure and use the Contentful Middleman gem to generate pages in Middleman driven by a content management system.

May 06, 2018

Use Slack For Rails Error Notification

Post a message to one of your Slack channels when your Rails app encounters a 500 error.

Dec 22, 2015