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

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] }

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'

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

# => '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)
# => 22

b.age = 23
# => 23

# => 23

# => 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

Add a Console to your Ruby Project

A powerful way to debug Rails applications is in using the Rails console. But even when you're not using Rails for your Ruby project, you can still have a console.

Aug 06, 2018

Bulk Resize Images Using Rake and ImageMagick

Got a set of images you need all to conform to the same size? Hate doing it manually? Me too. Let's write a rake task to solve our challenge.

Feb 15, 2016

How To Reverse a Ruby Hash

It's nice and easy to reverse a ruby array. See how to easily convert a hash as well.

Oct 18, 2014