Rails’ URL helpers allow you to pass in an Active Record object and it will automatically infer the proper URL including the id (e.g. link_to "Profile", @user
). How can we get this behavior for custom objects?
Custom objects
Consider a custom Image
class. We want URLs to look like /images/cat.gif
.
Rails relies on the to_param method to generate identifiers in URLs. If we define it on our custom object it can now be used with link_to
just like ActiveRecord objects can.
class Image
attr_reader :filename
def to_param
filename
end
end
<%= link_to "Details", @image %>
<%# will generate <a href="/images/cat.gif">Details</a> %>
Slugs
Because Rails URL-helpers aren’t hard-coded to use ids but instead rely on to_param
it allows us to build some fancier behaviors on top such as replacing identifiers with a prettier “slug”. For example when linking to a tag on a blog, we might want a URL that looks like tags/ruby
rather than tags/1
.
To do this we can override the to_param
method to use the name instead of the id. Keep in mind that you will be doing database lookups using this value so use something deterministic!
class Tag < ApplicationRecord
def to_param
name.downcase
end
end
Now when passing a user object into a link or form helper, it will generate a URL with our nice slug
<%= link_to "More #{@tag.name.titleize} content", @tag %>
<%# will generate <a href="/tags/ruby">More Ruby content</a> %>
This technique is how dedicated slug libraries like FriendlyId work.
ActiveModel compatibility
to_param
is part of ActiveModel
. You get if for free if you include ActiveModel::Conversion (it defaults toid
). You can also just define the method directly on your plain old Ruby objects to make them play nicely with the URL helpers.
Writing your custom Ruby objects to be ActiveModel-compatible, whether or not you include any of the actual modules, allows them to integrate natively with Rails’ controller and view helpers. This gives you an experience that’s just as smooth as working with regular Active Record objects.