We’ve seen in a previous article how making your object ActiveModel-compatible allows it to play natively with Rails’ URL helpers. What about a more complicated case: you have a custom in-memory object that doesn’t have a single primary key but instead relies on several attributes for identity. How can this plug into the Rails helper ecosystem?
The to_key method is what you’re looking for. It drives bothto_param
we’ve seen before, anddom_id
which is realy useful if working with Hotwire.
Event groups

Consider a page where we display events grouped by day and by source, one row for each group. There is also a show page that shows more detailed metadata for the group. In our database we only have an individual events table and we build up the list of groups with a GROUP BY
clause.
We might model this with an in-memory EventGroup
object. Any event group can be uniquely described by the combination of source + date so we might want URLs that look like /event_groups/slack-2024-01-01
. Now we could do some work to manually link to URLs like this but by making our object ActiveModel-compatible, we can get all this for free from the framework. We just need to tell it what our composite primary key consists of:
class EventGroup
def initialize(source, date, events)
@source = source
@date = date
@events = events
end
# There is no database row with an id that can
# reference this group but the group of events can
# consistently be re-created as long as we have all
# of the parameters that were used to clump them
# together in the GROUP BY clause. The combination
# of these effectively make a primary key.
def to_key
[source, date]
end
end
In the view, this object can now be used with url and dom helpers:
<tr id="<%= dom_id(group) %>">
...
<td>
<%= link_to "Details", group %>
</td>
</tr>
generates the following markup:
<tr id="event_group_slack_20">
...
<td>
<a href="/event_groups/slack-2024-01-01">Details</a>
</td>
</tr>
ActiveModel compatibility
to_key
is part of ActiveModel. You get it for free if you include ActiveModel::Conversion
into your object (which defaults to [id]
). As demonstrated above, you can also define the method yourself without including the module..
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.