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
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: "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"})
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.
`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)