I currently pass the current user id via data
, but this feels hackish and not very secure.
Is there a better way?
Doesnât seem hackish to me. Data-attributes are a great way for the server to pass necessary information to the client side code.
As far as security goes, everything you do on the client side is inherently insecure. Security should always be enforced server-side. If you donât want to reveal user ids to the client use a unique random token instead.
In my code, a user locks a record in the UI, and this triggers the generation of a new html snippet that will be propagated to all viewers of this record to show this record is now locked.
The snippet contains 2 parts, one for the locker (an AJAX link to unlock the record), and one for everybody else (just a image).
Those 2 parts are hidden by default, and itâs the Stimulus controller role to reveal one and remove the other based on the current user identity based on
may_unlock = (current_user_id == object.locker.id
The *Channel.rb knows about current_user
; if only there was a simple (meaning: âsynchronousâ) way for the client javascript code to obtain this value.
CSS might be a better tool for the job if you just need to show/hide elements. Something like:
<head>
âŚ
<style>
[data-visible-to]:not([data-visible-to="<%= current_user.id %>"]) {
display: none;
}
</style>
</head>
<body>
âŚ
<div data-visible-to="<%= record.creator.id %>">
âŚ
</div>
</body>
Thanks @javan Simple and elegant solution; it helped me remove a lot of painful javascript. (after adding the matching [data-invisible-to..]
- see below)
Itâs limited to simple cases though: Iâm afraid it wonât help for
may_edit = current_user_is_admin || (current_user_id == article_author_id)
Here is the complete css code I used to replace showing/hiding with Javascript:
<head>
...
<style>
[data-visible-to]:not([data-visible-to="<%= current_user.id %>"]) {
display: none;
}
[data-invisible-to="<%= current_user.id %>"] {
display: none;
}
</style>
</head>
I expanded the above solution to hide/show elements based on the userâs roles.
<head>
..
<%= csrf_meta_tag %>
<%= render 'layouts/custom_user_styles' %>
</head>
file âviews/layouts/_custom_user_styles.html.erbâ:
<% cache current_user do %>
<!--source: https://discuss.hotwired.dev/t/how-to-pass-current-user-id-to-a-controller/287/3-->
<style>
[data-visible-to-user]:not([data-visible-to-user="<%= current_user.id %>"]) {
display: none;
}
[data-invisible-to-user="<%= current_user.id %>"] {
display: none;
}
<% User::ROLES_SYMBOLS.each do |role| %>
<% if current_user.has_role?(role) %>
[data-invisible-to-role="<%= role %>"] {
display: none;
}
<% else %>
[data-visible-to-role="<%= role %>"] {
display: none;
}
<% end %>
<% end %>
</style>
<% end %>
I think you could improve this further by caching based on the set of roles instead of each individual user
In case if you need to pass user id to more than one controller I would suggest go the Basecamp way
and add it to the <head>
like:
<meta name="current-person-id" content="your-id">
I agree with @Alexandr_K. This way you can keep your views free of user specific attributes, which allows you to cache the page. The meta(s) tag can be read to gather additional information about the current user.
If the user does need to be secure, you can do an ajax call to the server when the Stimulus component connects.
You saved me! Thank you so much!!!
In my case I have multiple devise models. A combo of your solution and this SO answer led me to this solution:
application_controller.rb
class ApplicationController < ActionController::Base
devise_group :viewer, contains: [:admin, :source, :user]
end
<% cache current_viewer do %>
<style>
<% ['Admin', 'User', 'Source', 'NilClass'].each do |role| %>
<% if current_viewer&.class&.name == role %>
.data-invisible-to-<%= role.downcase %> {
display: none !important;
}
<% else %>
.data-visible-to-<%= role.downcase %> {
display: none !important;
}
<% end %>
<% end %>
</style>
<% end %>
Then in my partials I can just do:
<%= turbo_frame_tag dom_id(gallery_image) do %>
<div class="image-viewer">
<div class="gallery_image__actions_container data-visible-to-admin">
...stuff I only want admin to see
</div>
...stuff anyone should see
</div>
<% end %>