November 27, 2020
•Last updated November 5, 2023
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!
Categories
Collection
Part of the Ruby on Rails collection
Products and courses
-
Hello Hotwire
A course on Hotwire + Ruby on Rails.
-
Hello Rails
A course for newcomers to Ruby on Rails.
-
Rails UI
UI templates and components for Rails.