Dismiss

Subscribe to the newsletter

November 27, 2020

How to use the Delegate Pattern in Ruby on Rails

When working with more complex associations in a Ruby on Rails app you may find yourself in an object chaining problem upon rendering data to a view or helper. The delegate pattern is a handy built in feature to Ruby on Rails to help with this issue.

What is delegate exactly?

Taking a deeper look at the Ruby on Rails API documentation you can see delegate defined as a Ruby class method used to easily expose contained objects' public methods as your own.

That sounds more complicated than it is and luckily it's pretty simple to configure.

In the example you'll find in this tutorial I used a User model and a Profile model to demonstrate this pattern. You'll need something similar to follow along but pretty much anything with a relationship in tact should suffice.

Side note: I reference my Rails application template called Kickoff Tailwind in the video. This template actually creates the User model automatically for us thanks to the handy Devise gem.

If you don't have the template or don't want to bother with it you can run this to get a basic user model added to your app:

rails generate model User first_name:string last_name:string email:string

I ran the following to generate our profile model:

rails generate Profile tagline user:references
rails db:migrate

With both models created we need some dummy data. I'll leverage the rails console to create some by hand.

rails console

Because my template I mentioned prior leverages Devise I'll go ahead and create the necessary object data we need to create a new user.

# if you're using my template/devise
> User.create({name: "Andy Leverenz", email: "[email protected]", password: "password", password_confirmation: "password"})

# if you're not using my template/devise
> User.create({first_name: "Andy", last_name: "Leverenz", email: "[email protected]"})

And now some Profile data:

> Profile.create(tagline: "My awesome profile", user_id: User.first.id)

Here we reference the User we just created and/or the first user created in your database. You can more conveniently run that as follows and the built in ActiveRecord magic should account for things automatically.

> Profile.create(tagline: "My awesome profile", user: User.first)

Relationships

With our models and data created we need to ensure our models are set up for success.

User model

Our User model should be taken care of thanks to our generator when it was first created. My file looks like this. Again I used my template so your mileage may vary if you aren't using it.

# app/models/user.rb
class User < ApplicationRecord
  has_person_name
  has_one :profile

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

Each user would only ever have one profile in this app per account so we'll establish that with the line that reads has_one :profile.

Profile model

The Profile model is quite simple:

class Profile < ApplicationRecord
  belongs_to :user
end

Because a User has_one :profile a Profile needs to belong_to a `User.

Adding delegation

At this point if we were to code away in our app using and say render some data on a Profile view we would need to access it like this @profile.user.name. That chaining effect isn't a huge deal but it would be nice to not have to have the .user in the middle and/or refer to the name as something completely different.

We can extend the profile model to include these things using delegate.

In the User model's case we can delegate some methods to the profile.


class User < ApplicationRecord
  has_person_name
  has_one :profile

  delegate :username, to: :profile
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  def username
    "#{first_name}_#{last_name}"
  end
end

Here I create a new username class method (a public one) that consists of the first_name and last_name attributes of the User table on the database layer. That method we can then pass to the delegate method and assign it through to the Profile model.

class Profile < ApplicationRecord
  belongs_to :user

  delegate :username, :email, to: :user, allow_nil: true, prefix: :user
end

On the Profile side we use the delegate method to pass any class methods to the User model that we want access from our User model inside our Profile model.

The delegate method allows you to optionally pass allow_nil and a prefix as well.

Ultimately this allows us to query for data in custom ways.

rails c
> profile = Profile.first
> profile.email #= "[email protected]"
> profile.username #= "Andy_Leverenz"

Pretty handy!

As an application scales I can see this pattern being useful in terms of scoping and productivity.

I haven't seen it widely in use myself but I think it may be due to these invented naming conventions that aren't always obvious from one developer to the next on a team. The next developer that comes along might be looking for a database column named after some custom public method you made for delegation purposes and spend a lot of time trying to pin point its origin.

It has it's pros and cons but looks really appealing for some applications. You be the judge!

Leave a reply

Sign in or Sign up to leave a response.

0 responses

Est. reading time: 5 minutes
Stats: 523 views

Categories

Collection

Part of the Ruby on Rails collection