Rails dom_id helper without exposing the primary id
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, :votesoutputs<turbo-frame id="votes_message_a1b2c3">
Want to read me more?
-
Don't expose primary id's with Rails' dom_id
Rails' dom_id is a great, little helper. A big downside is it exposes your primary id's. -
Basic Autocomplete Without JavaScript using Datalist
Add an autocomplete feature with just a few lines of code using HTML's native datalist attribute. -
Keep Active Record models clean with Decorators
Learn about the Decorator patterns using Ruby's SimpleDelegator class to clean up Active Record objects.
Over to you…
What did you like about this article? Learned something knew? Found something is missing or even broken? 🫣 Let me (and others) know!
Comments are powered by Chirp Form
{{comment}}