DEV Community

Andy Leverenz
Andy Leverenz

Posted on • Originally published at web-crunch.com on

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 intact 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
Enter fullscreen mode Exit fullscreen mode

I ran the following to generate our profile model:

rails generate Profile tagline user:references
rails db:migrate
Enter fullscreen mode Exit fullscreen mode

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

rails console
Enter fullscreen mode Exit fullscreen mode

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: "andy@web-crunch.com", password: "password", password_confirmation: "password"})

# if you're not using my template/devise
> User.create({first_name: "Andy", last_name: "Leverenz", email: "andy@web-crunch.com"})
Enter fullscreen mode Exit fullscreen mode

And now some Profile data:

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

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)
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

`ruby
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.

`ruby
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.

`bash
rails c

profile = Profile.first
profile.email #= "andy@web-crunch.com"
profile.username #= "Andy_Leverenz"
`

Pretty handy!

As an application scale, 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 pinpoint its origin.

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

Shameless plug!

Are you new to Ruby on Rails? Would you like an in-depth jumpstart to your learning experience? I made a comprehensive course to assist with that.

Find out more at hellorails.io

Top comments (0)