An Introduction to Polymorphism in Ruby on Rails
Tags: ruby, rails
Published: 2022-06-01 11:11:03

If you have ever spent time building an Object-Oriented Program (OOP), you have likely used polymorphism in your application or, at the very least, heard the term.

It’s the kind of word you’d expect to see in a science or computer science textbook. You may have spent time researching polymorphism and even implemented it in your application without clearly understanding the concept.

This article will give you a greater understanding of polymorphism, specifically in Ruby on Rails. To accomplish this, we’ll dive into:

- The use of polymorphism in the real world
- Polymorphism in programming by way of OOP
- How you can incorporate it into your Rails application to help maintain high-quality code

Let's get going!

## Polymorphism in the Real World

There are several ways to define polymorphism in different contexts. A useful definition, regardless of context, is _'an object's ability to display more than one form'_. In fact, if we break down the word itself, poly means ‘many’ and morph means ‘form’.

In the real world, a basic example of this definition could be a woman who is also a police officer, sister, someone’s child, someone’s mother, etc. Each role determines her behavior and contributes to the person she is.

### Polymorphism and Genetics

Outside computer programming, polymorphism is a term commonly associated with biology and genetics. In this context, polymorphism is more specifically defined as genetic variations that result in several distinct forms or types of individuals within a species.

Think of jaguars. Jaguars can have multiple gene variations, which can affect their fur coloring. Most jaguars have tawny coloring with black circles. However, they can have lighter or darker circles due to an altered gene, and some can have black fur coloring.

Different pigmentation within the same species of birds is another example of polymorphism. Consider the Gouldian finch, which has obvious distinctions in its coloring between individuals.

### Monomorphism vs. Polymorphism

If we turn our attention to monomorphism, we can further understand polymorphism. Sticking with biology, monomorphism can be defined as _'a species with just one form'_, that maintains that same form during the various phases of its development.

Penguins are monomorphic. It is difficult, even for experts, to distinguish between the sexes. Gene differences in a penguin are minimal. Therefore, the physical attributes of penguins are almost indistinguishable, especially in terms of their size and black and white coloring.

Behavioral cues are often the easiest way to discern between the sexes in monomorphic species.

Let's turn our attention to polymorphism in programming, specifically.

## Polymorphism in OOP

If we consider our initial definition of polymorphism — _the ability of an object to display more than one form_ — we can seamlessly relate it to OOP.

In OOP, we can use the same method to produce different results by passing in separate objects. We could use conditionals to achieve this. However, this can create chunky code and may veer us away from DRY principles. Polymorphism is essential in creating clean and logical OOP applications.

Let's look at two examples of how polymorphism can be implemented in an OOP language like Ruby: through inheritance and duck-typing.

### Polymorphism and Inheritance in Ruby

Inheritance is where a child class inherits the properties of a parent class.

Below is an example of how we can implement polymorphism with inheritance:

```ruby
class Instrument
  def instrument_example
    puts "Saxophone"
  end
end

class Stringed < Instrument
  def instrument_example
    puts "Guitar"
  end
end

class Percussion < Instrument
  def instrument_example
    puts "Drums"
  end
end

all_instruments = [Instrument.new, Stringed.new, Percussion.new]

all_instruments.each do |instrument|
  instrument.instrument_example
end

# Output

# Saxophone
# Guitar
# Drums
```

The above code has two child classes — `Stringed` and `Percussion` — inherited from the parent class `Instrument`. This example is polymorphic, as we are calling a method: `instrument_example` — and it outputs multiple forms: `Saxophone`, `Guitar`, and `Drums`.

This example of achieving polymorphism through inheritance is essentially overriding a method, but helps provide a clearer understanding of polymorphism in an OOP language.

### Duck-Typing and Polymorphism in Ruby

A more practical example of polymorphism in OOP is through duck-typing, as referenced below.

```ruby
class Guitar
  def brand
    'Gibson'
  end
end

  class Drums
    def brand
      'Pearl'
    end
  end

  class Bass
    def brand
      'Fender'
    end
  end

  class Keyboard
    def brand
      'Casio'
    end
  end

  all_instruments = [Guitar.new, Drums.new, Bass.new, Keyboard.new]

  all_instruments.each do |instrument|
    puts instrument.brand
  end
  # Output

  # Gibson
  # Pearl
  # Fender
  # Casio
```

Though each class method is named `brand`, we don't override the method (unlike in polymorphic inheritance). Instead of inheriting from a parent class, here we have four independent classes, each with its own method. Duck-typing is useful, as we can just iterate through the classes to get each method's output (as opposed to calling each method separately).

Again, duck-typing is polymorphic as we call a method — `brand` — and generate an output that takes multiple forms: `Gibson`, `Pearl`, `Fender`, and `Casio`. Of course, duck-typing and polymorphism aren’t essential in producing this outcome. However, it’s very useful to implement clean and logical code.

## Polymorphism in Ruby on Rails

Polymorphism works well in Ruby on Rails as an Active Record association. If models essentially do the same thing, we can turn them into one single model to create a polymorphic relationship.

Sticking with the instrument theme, let's consider an application where users can post, comment, and review instruments. Examine the Entity-Relationship Diagram (ERD) below:

![first erd - no Polymorphic association](https://blog.appsignal.com/images/blog/2022-05/erd-1.png)

In this example, we have an ERD for an application where a user can post an instrument with its details. A user can also provide a comment about that posted instrument.

Other users can then provide a rating of the instrument and rate comments to determine their usefulness or validity. These Active Record associations work just fine and serve the purpose of our application.

What if we wanted to add other associations to our application? We would need to add and repeat duplicate associations.

For example, if we wanted to add a `user_rating` model to rate the trustworthiness of a user, we would need to create a separate table with its own associations. This would mean adding a new relationship between the `user` and `user_rating` models. The ERD would then look something like this:

![second erd - example of adding another model with no Polymorphic association](https://blog.appsignal.com/images/blog/2022-05/erd-2.png)

Now we have three models essentially doing the same thing: rating an object, but in different contexts. These associations are ripe for a polymorphic association.

Let's take a look at the ERD with the rating models as polymorphic:

![third erd - example of a Polymorphic association](https://blog.appsignal.com/images/blog/2022-05/erd-3.png)

As we have a `rating` column, I named the model `reviews` as opposed to `ratings` to avoid confusion. Here, the non-review models still have associations with the other models, but the separate rating models have been merged into a single review model.

`reviewable_type` and `reviewable_id` now take on the same role as the separate rating models by representing which model the review is associated with.

The `reviewable_type` column stores the model class name (`user`, `instrument_post`, or `comment`), and the `reviewable_id` stores the corresponding ID of that model.

We can now use these two columns to link the rating integer with a specific user, post, or comment via Active Record queries and/or conditional statements. The foreign key `user_id` remains in the review model, as this allows us to track which user left a review.

Right now, the term ‘-able’ in our polymorphic model may seem strange, but its purpose will soon be made clear when we do some Rails magic.

The review model is considered polymorphic as we have one model or object that can represent and take on multiple forms: user, comment, and instrument post reviews.

## Implementing Polymorphism in Ruby on Rails

Time to implement polymorphism in a Rails application! If we act as though we have already created our `user`, `instrument_post`, and `comment` models, we can get started on incorporating our polymorphic model: `reviews`.

Firstly, create a table and generate the model from the terminal, like so:

```shell
rails g model Review user:belongs_to reviewable:references{polymorphic}
```

This builds the migration file:

```ruby
class CreateReviews < ActiveRecord::Migration[7.0]
  def change
    create_table :reviews do |t|
      t.belongs_to :user, null: false, foreign_key: true
      t.references :reviewable, polymorphic: true, null: false
      t.integer :rating

      t.timestamps
    end
  end
end
```

The `schema.rb` file updates after the migration is run. The polymorphic option transforms the `reviewable` column into the `reviewable_type` and `reviewable_id` columns:

```ruby
  create_table "reviews", force: :cascade do |t|
    t.integer "user_id", null: false
    t.string "reviewable_type", null: false
    t.integer "reviewable_id", null: false
    t.integer "rating"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["reviewable_type", "reviewable_id"], name: "index_reviews_on_reviewable"
    t.index ["user_id"], name: "index_reviews_on_user_id"
  end
```

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

class Review < ApplicationRecord
  belongs_to :user
  belongs_to :reviewable, polymorphic: true
end
```

Remember when we mentioned the term ‘-able’ above? It is a Rails naming convention for designating our polymorphic association, giving us the ability to make a user, instrument post, and comment 'reviewable'.

For this Rails magic to work, we need to ensure that our other models are associated correctly with our polymorphic model.

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

class User < ApplicationRecord
  has_many :instrument_posts
  has_many :comments
  # alias association for user who submitted the review
  has_many :submitted_reviews, class_name: "Review", foreign_key: :user_id
  # association for user, instrument_post and comment that has the review
  has_many :reviews, as: :reviewable
end
```

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

class InstrumentPost < ApplicationRecord
  belongs_to :user
  has_many :reviews, as: :reviewable
end
```

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

class Comment < ApplicationRecord
  belongs_to :user
  has_many :reviews, as: :reviewable
end
```

The `user`, `instrument_post`, and `comment` models can now be reviewed and given ratings.

If we have already created at least two users, a comment, and an instrument post, we can then create and access the reviews through various ways with Active Record Queries, like so:

```ruby
# A user (User 2) leaving a review for another user (User 1)
# The reviewable_id is the id of the user to which the review is given
# The user_id is the id of the user who created the user review
user_1 = User.first
user_1.reviews.create(user_id: 2, rating: 2)
Review.where(reviewable_type: "User").first # id: 1, user_id: 2, reviewable_type: "User", reviewable_id: 1, rating: 2
# or
user_1.reviews.first # id: 1, user_id: 2, reviewable_type: "User", reviewable_id: 1, rating: 2
```

```ruby
# User 2 reviewing a instrument posted by User 1
# The reviewable_id is the id of the instrument_post
# The user_id is the id of the user who created the instrument_post review
post = InstrumentPost.first
post.reviews.create(user_id: 2, rating: 4)
post.reviews.first # id: 2, user_id: 2, reviewable_type: "InstrumentPost", reviewable_id: 1, rating: 4
post.reviews.first.reviewable_type # "InstrumentPost"
```

```ruby
# User 2 reviewing a comment created by User 1
# The reviewable_id is the id of the comment
# The user_id is the id of the user who created the comment review
comment = Comment.first
comment.reviews.create(user_id: 2, rating: 5)
comment.reviews.first # id: 3, user_id: 2, reviewable_type: "Comment", reviewable_id: 1, rating: 5
comment.reviews.first.rating # 5

# We can then find the user that created the reviewed comment by associating the value of the reviewable_id to the comment id
Comment.where(id: 1) # id: 1, user_id: 1, content: "Comment created by user id 1"
# In this scenario the comment id and user_id just happen to be the same
```

```ruby
user_1 = User.first
user_2 = User.last

# Reviews User 1 has given (none)
user_1.submitted_reviews # []

# Reviews User 2 has given
user_2.submitted_reviews
# id: 1, user_id: 2, reviewable_type: "User", reviewable_id: 1, rating: 2
# id: 2, user_id: 2, reviewable_type: "InstrumentPost", reviewable_id: 1, rating: 4
# id: 3, user_id: 2, reviewable_type: "Comment", reviewable_id: 1, rating: 5

# Counting the total amount of reviews User 2 has given
user_2.submitted_reviews.count # 3
```

There are many other ways to interact with the `reviews` model, depending on what data you need to render. It is important to share how the parent models can create a review. As long as the review is associated with a user and a reviewable model, Active Record automatically links the `reviewable_id` and `reviewable_type` with the associated model.

Without polymorphism in our Rails examples, there would be many more tables, unnecessary duplicate columns, `belongs_to`, and `has_many` associations in our models. Polymorphism has lessened the need to join tables, permitting easier and quicker Active Record queries and associations.

## Wrap Up: Use Polymorphism for Clean and Logical Ruby Code

In this post, we explored polymorphism in two distinct environments: biology and Ruby programming. In both cases, polymorphism is the ability of an object to display more than one form.

We looked at how to implement polymorphism in Ruby through inheritance and duck-typing before diving into the uses of polymorphism in Ruby on Rails specifically.

Polymorphism can help you write clean and logical code. My goal is to help you add this essential OOP concept to your toolbelt for your current, future, and maybe even past applications.

Happy coding!

**P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, [subscribe to our Ruby Magic newsletter and never miss a single post](https://blog.appsignal.com/ruby-magic)!**