instance_variable_set method in Ruby

How to Dynamically Create Instance Variables in Ruby

This post shows one way to dynamically initialize multiple instance variables in a Ruby class using metaprogramming. If you need to pass multiple, separate pieces of data to a constructor (and cannot refactor the code for some reason), it's a pretty good technique to reduce all the repetitive code.

3 min read
Disclaimer: The point of the post is not that we should all be creating instance variables dynamically from now on, but just to show one way to do it for those rare cases when you actually do need it.

Update: This post got to the front-page of the Hackernews for a whole day, and a bunch of interesting discussion followed. Check it out!

Reading the Rails codebase is a very effective way I've found to learn Ruby, especially its advanced concepts, including metaprogramming. Rails makes an abundant use of metaprogramming, and I always learn something new when reading the Rails source code.

Late into last night, while browsing the code for ActionView framework in Rails, I came across an interesting pattern to dynamically initialize multiple instance variables. It uses a little metaprogramming, but nothing scary. Here's the highly simplified version:

# actionview/lib/action_view/base.rb

module ActionView
  class Base
    def initialize(assigns)
      assign(assigns)
    end

    def assign(assigns)
      assigns.each { |key, value| instance_variable_set("@#{key}", value) }
    end
  end
end

Apparently, Rails loves the instance_variable_set method and uses it a lot. Let's take a deeper look to understand the problem it's solving, and the solution.

Problem

Imagine that you have a class constructor with too many arguments. You want to assign each argument to an instance variable of that class.

class Person
  def initialize(name, age, address, data, ...)
    @name = name
    @age = age
    @address = address
    @data = data
    
    # remaining assignments
  end
end

You'd like to avoid all the repetitive typing to assign all the instance variables.

If you were programming in TypeScript, you could use parameter properties shorthand to assign multiple properties:

class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // No body necessary
  }
}
const a = new Params(1, 2, 3);
console.log(a.x);  // 1

No such shorthand exists in Ruby (and I'm glad that Ruby doesn't have the feature bloat like TypeScript and C#), so let's examine how you could use Ruby's metaprogramming to initialize these instance variables on the fly.

💡
Before using any advanced techniques, I suggest you try to refactor the code so you don't need to pass so many parameters. For example, you could introduce a parameter object, or preserve the whole object. Resort to metaprogramming only when you can't do it in a simpler way.

Solution

Use the Object#instance_variable_set method provided by Ruby to initialize instance variables. From the documentation,

instance_variable_set sets the instance variable named by symbol to the given object. This may circumvent the encapsulation intended by the author of the class, so it should be used with care. The variable does not have to exist prior to this call. If the instance variable name is passed as a string, that string is converted to a symbol.

Let's add a method that will initialize instance variables using the above method.

class Person
  def initialize(assigns)
    assign(assigns)
  end

  private
    def assign(attributes)
      attributes.each do |key, value|
        instance_variable_set("@#{key}", value)
      end
    end
end

ak = Person.new(name: 'Akshay', age: 31)
puts ak.instance_variable_get("@name")  # Akshay

The assign method takes a Hash of variable names and their values. Then, for each key value pair, it calls the instance_variable_set method, passing the stringified key (prepended with the @ character to indicate an instance variable) and its value.

Now, just because you can do it, doesn't mean you should. As I mentioned in the beginning of the post, the point here is not to dynamically create all your instance variables from now on, but to show one way to do so, for those rare occasions when you do need it. In fact, if you scan the Rails codebase, you'll find a few excellent situations where you do need this approach. I'll leave this as an exercise for the readers.

💡
To be honest, I can't think of a situation where I'll be using it in my application code, but it's an interesting pattern, nonetheless. And it doesn't hurt to know all the capacities of this beautiful programming language.

What do you think? Do you have a real-world use case where you might need this in a Rails application?

P.S. If you liked this post, you might enjoy my notes from the excellent book "Metaprogramming Ruby 2".

Metaprogramming in Ruby
Metaprogramming in Ruby enables you to produce elegant, clean, and beautiful programs as well as unreadable, complex code that’s not maintainable. This book teaches you the powerful metaprogramming concepts in Ruby, and how to use them judiciously.