Changing Ruby classes at runtime with class_eval
Tags: ruby, rails, webdev, tutorial
Published: 2020-07-10 20:09:08

One of Ruby's most interesting - and useful! - features is being able to change how classes and objects behave while your app is running.

If you use Rails, then certainly you have used many runtime defined methods. Let's look at an example. Imagine a one to many association between two models: **Author** and **Post**.

If the author is already instantiated, then we can get the posts for this author by invoking 

```ruby
@author = Author.find(1)
@author.posts
=> #<ActiveRecord::Associations::CollectionProxy [...]>
```

But where does the `posts` method come from? This method is actually defined during runtime by the `has_many` method.

```ruby
# app/models/author.rb

class Author < ApplicationRecord
  has_many :posts
end
```

When reading the author model file, Ruby will invoke the `has_many` methods passing the argument `:posts` to it. Under the hood, `has_many` will change the class that invoked it and add the association methods.

This is possible thanks to the magic of `class_eval`, a Ruby method designed to change the behavior of classes during runtime.

It's a remarkable mean to provide shared functionality to classes in an elegant way that feels almost like you're just changing a configuration file.

# How class_eval works

There are two ways of using `class_eval`: passing it a block or passing it a string with the changes we'd like to execute to the class. Let's look at them separately.

## Passing a block

When passing a block to `class_eval`, it will evaluate that block within the context of the class that is invoking it.

When passing blocks, it is very common to use `define_method` for defining methods during runtime. It is a method itself that

1. Accepts the name of the new method as the argument
2. Accepts a block to define the new method's body
3. Yields the new method's regular arguments and block arguments to the body

```ruby
define_method("name_of_new_method") do |arg1, arg2, &block|
  # Define the behavior of the new method.
  # arg1, arg2 and &block are accessible here in the body.
end
```

Now, let's analyze an example where we want to add a `sing` method to our classes. When invoked, it will define a method with the music genre for that class that just prints a string.

```ruby
# Define the method sing. This method can be put
# wherever is convenient (e.g.: a module or a parent class)

def sing(genre)
  # "Open" the class for changes
  class_eval do

    # Use the define_method method to define "rock"
    # in runtime
    define_method(genre) do |band|
      puts "I love #{band}!"
    end
  end
end

class Person
  sing :rock
end

# After evaluating the class, the new rock method exists
Person.new.rock("AC/DC")
=> I love AC/DC!
```

We have our `rock` method working!

If we wanted to add methods to a class that do not depend on the argument passed to `sing`, then a simple def inside the `class_eval` block is another option.

```ruby
def sing
  class_eval do
    # This def is evaluate from the context of the class
    # so this will be defined in any class that invokes
    # sing

    def rock(band)
      puts "I love #{band}!"
    end
  end
end

class Person
  # Notice the absence of the argument
  sing
end

Person.new.rock("Queen")
=> I love Queen!
```

## Passing a string

The second manner of using `class_eval` is by passing it three arguments: a string with the Ruby that will be evaluated in the class, the file where it should be evaluated and the line where it should be evaluated.

These two last arguments will typically always be the same, but they can be changed to achieve other types of behavior.

The Ruby methods `__FILE__` and `__LINE__` are commonly used here and they return the current file and line being evaluated, respectively.

Let's use this case as an example of how we could write `has_many` using `class_eval`. Notice that this is not the real implementation that Rails uses, but the general idea is similar.

The usual manner to use the string version of `class_eval` is to define a Ruby string with a HEREDOC and interpolate whatever data you need.

```ruby
def has_many(resource)
  # Turn current class name into name_id
  # E.g.: NewsLetter -> news_letter_id
  # Evaluated before opening class for modification

  id_attribute_name = "#{self.name.underscore}_id"

  class_eval(<<~ASSOCIATION_METHODS, __FILE__, __LINE__ + 1)
    def #{resource}
      # Turn the argument (e.g.: :posts) into the class
      # name Post
      #{resource.to_s.singularize.camelize}.where(#{id_attribute_name}: self.id)
    end
  ASSOCIATION_METHODS
end
```

Now let's use this in a class and understand what the HEREDOC is evaluated to.

```ruby
class Author
  # Using our version of has_many
  has_many :posts
end

# In this case, the resource argument is :posts.
# The string resulting from the HEREDOC is the one below.
# Notice that this will get evaluated within the context
# of the Author class and then will define
# the instance method posts, allowing us to
# invoke
# author.posts 

def posts
  Post.where(author_id: self.id)
end
```

It's important to point out that many changes can be applied in a single invocation of `class_eval`. For instance, the real implementation of `has_many` in Rails would also provide the class with a setter. Both getter and setter can be defined at the same time.

```ruby
@author = Author.find(1)
@author.posts = [...]
```

# Conclusion

Using `class_eval` enables developers to define behavior in runtime. This technique can be used to create methods with arguments and options passed in at a class level which results in an elegant organization of code.

Have you used this before? Let me know some use cases in the comments!