Prevent user from accessing edit view directly (turbo-frame only view)

In a regular Rails 7 application, if you build an #index action that lists Tweets, and you want the user to be able to edit the tweets inline, you could do something like this:

# _tweet.html.erb
<%= turbo_frame_tag(tweet) do %>
  <p><%= tweet.text %></p>
  <%= link_to "Edit this tweet", edit_tweet_path(tweet) %>
<% end %>

And then in tweets/edit.html.erb:

<%= turbo_frame_tag(tweet) do %>
  <%= render "form", tweet: @tweet %>
<% end %>

Even tough it works, I’d like to prevent users from hitting the URL /tweets/1/edit directly, so that the edit.html.erb is only accessible via the Turbo frame request.

However, these edit request (which is not a turbo_stream form submissions) is actually just a regular HTML request (it prints Processing by TweetsController#edit as HTML in the console), so by doing this:

def edit
  respond_to {|format| format.turbo_stream }
end

I can’t prevent users from accessing /tweets/1/edit directly, since it’s not a turbo stream.

In the good ol days of Rails UJS, this could be easily achieved by restricting the controller to respond to format.js, like so:

def edit
  respond_to {|format| format.js }
end

Or only providing a edit.js.erb file in the views folder, because then HTML requests wouldn’t be served if the user requested /tweets/1/edit` directly (as it would be an HTML request).

Any way to achieve the same using Hotwire & Turbo?

Have you tried implementing an authorization mechanism. It seems to me that your issue cannot be solved by turbo per se. But, you need to implement some role based authorization in your controller.

So, using something like pundit you can authorise the resource.

def edit
   authorise @tweet
end

I was wondering the same thing recently.

Especially for show if you want to inline the show view and display it nicely in the current view (ideally with an updated URL) when clicking show more but restrict from accessing an entire /[:id] route that would need to be maintained in terms of CSS etc. as a separate view.

I wonder if checking for turbo_frame_request? in the controller and then if not the case redirecting to the index could do the trick.