This article was originally published on Rails Designer
Rails' dom_id
is a useful little helper especially in Rails apps with Turbo. I like to use it as it provides a consistent output for your id-attributes. Not having to think about (and mixing it up) how to structure even an id-attribute is just one of those things I enjoy about Rails.
This is how it is used:
<%= turbo_frame_tag dom_id(message, :votes) do %>
<%= button_to "👍", votes_path, params: {message_id: message, vote: "up"} %>
<%= button_to "👎", votes_path, params: {message_id: message, vote: "down"} %>
<% end %>
As a small aside: turbo_frame_tag
's first arguments takes any representation of a string as an array as the id. So above cóuld be rewritten as turbo_frame_tag message, :votes
And that would both render:
<turbo-frame id="votes_message_1">
Rest of the HTML here
</turbo-frame>
Other ways to use the dom_id helper is:
-
dom_id(Message.find(42))
; this would outputmessage_42
; -
dom_id(Message)
; this would outputmessage
;
It looks for an id on the passed object (and return _new
if none is found). The id is, by default, the primary_key which value is increased incrementally. This might be for business- and security reasons, not what you want.
Now previously my solution was to use the stealth_dom_id gem I released late last year. Recently someone pointed out the issue with it using TurboStream Broadcasts where it would still use the record's id. The turbo-rails gem uses ActionView::RecordIdentifier under the hood; nót the dom_id
view helper. Makes sense.
So after some investigation on the internals I have decided to sunset the stealth_dom_id. My suggestion now is to define the to_key
method on the model.
I use it in an updated version of my sluggable concern (lib/sluggable.rb
) that I copy over to every app I build myself (and for others). The basics look like this:
module Sluggable
extend ActiveSupport::Concern
included do
before_create :set_slug
end
def to_param = slug
private
def set_slug
return if slug
slug = nil
loop do
slug = SecureRandom.hex(4)
break unless self.class.name.constantize.where(slug: slug).exists?
end
self.slug = slug
end
end
This concern assumes the model has a slug
column. It is then used as User.find_by(slug: params[:id])
.
I've now updated this concern to include the to_key
method.
module Sluggable
extend ActiveSupport::Concern
included do
before_create :set_slug
end
+ def to_key = [slug]
+
def to_param = slug
end
Now when you use dom_id (or the shorthand) version in the turbo_frame_tag
(or anywhere in your HTML or TurboStream Broadcasts) it uses the slug value instead of the id:
-
turbo_frame_tag dom_id(message, :votes)
outputs<turbo-frame id="votes_message_a1b2c3">
; -
turbo_frame_tag message, :votes
outputs<turbo-frame id="votes_message_a1b2c3">
Top comments (2)
Obfuscating DB ids is an important topic, thanks for bringing it up!
I'd like to point out what I believe is a big flaw with the slug generation - depending on the use-case and number of records (SecureRandom.hex(4) can produce up to 4,294,967,296 unique values), we can run into collisions and excessive looping which will impact performance.
Perhaps a better solution would:
I hear github.com/peterhellberg/hashids.rb is on the right path.
Yes, while not the topic of the post, that is a good thing to point out. 👍