Mar 2020
Experimenting with remote: true
For asynchronous Rails on the front-end, we have a nice remote: true
helper that we place inside links and forms.
This allows us to respond to requests with javascript - which is helpful if your app has any single-page-application (SPA)-like behaviors.
SPA-like behavior basically means that a user clicks various links and buttons and the site changes its content without changing its URL.
Setting remote: true
is static — meaning, true
is always true
. This post explores potential use cases for setting the boolean value for remote
dynamically, by using a helper method.
Example: A Social Network for Cats
On our homepage, we have a list of cats. When you click on a cat’s name, you want to see their picture appear without going to a new page.
The list part of our view might look something like this:
<% @cats.each do |cat| %>
<%= link_to cat.name, cat, remote: true %>
<% end %>
The key ingredient is remote: true
which is a Rails helper that renders out to a typical <a>
tag with a data-remote: true
attribute in our HTML.
This data-remote
attribute tells Rails (via Rails Unobtrusive Javascript) to handle this link a bit differently than usual. Instead of redirecting to the link address and loading the associated view, we want to respond with Javascript and stay put.
We’ll put our javascript in a js.erb
file that matches the controller action’s name. Our javascript will just replace the main #cat-image
element with the clicked cat’s image.
Privacy
Well, it turns out, we don’t want to show images to anyone, just other cats who have logged in to the site.
Luckily, Devise gives us a few handy helpers we can use in our views and controllers.
Ordinarily, we’d use a Devise helper as a before_action
in the cats
controller:
class CatsController < ApplicationController
before_action: :authenticate_user!, only: [:show]
def show
end
end
And our relevant route looks like this:
get "cats/:id", to: "cats#show", as: "cat"
Which means that our earlier link_to
:
<%= link_to cat.name, cat, remote: true %>
is shorthand for:
<%= link_to cat.name, cat_path(cat), remote: true %>
takes us to the show
action of the cats
controller.
Complication
Devise’s authenticate_user!
helper is designed to work nicely for HTML
requests, so a user will be redirected to the signup/login page if trying to hit a protected route (i.e. stuff that only logged-in users should see)
But we’re using remote: true
, so we’re responding with Javascript
(not HTML
), so Devise is a bit stuck when a redirect is necessary.
We’re at a fork in the road with at least a few options.
Option 1
We change a few Devise configs so that it responds to JS. Or maybe we have to rewrite authenticate_user!
. As we go down this path, things start to feel a bit hacky.
Option 2
We wrap the link_to
s in a conditional. Something like:
<% if user_signed_in? %>
<%= link_to cat.name, cat, remote: true %>
<% else %>
<%= link_to cat.name, new_user_registration_path %>
<% end %>
This is our first glimpse of user_signed_in?
which is what one might guess - a devise helper that returns a boolean answering its own question.
This way, we allow the user to see the cat picture if she’s logged in — otherwise, we provide a regular non-remote link to the sign-up page.
Option 3
This the option we will choose!
Instead of what we saw in Option 2, we have something like this:
<%= link_to cat.name, cat, remote: user_signed_in? %>
Instead of having a static boolean for remote
, we have a devise helper method. We could use any helper method available to us in our views here, but for this example, we’ll use Devise.
Our CatsController
still looks like this:
class CatsController < ApplicationController
before_action: :authenticate_user!, only: [:show]
def show
end
end
When user_signed_in? == true :
An async request is made, and everything goes smooth. The user is signed in, and authenticate_user!
helper doesn’t try to redirect anywhere.
When user_signed_in? == false :
A regular HTML
request is made (because remote: false
), and Devise is ready for this because of our before_action
. It redirects to the signup/login page.
The end
I chose Option 3 because it’s 1 line of code, and doesn’t require tinkering with various configs.
It also raises some interesting possibilities when deciding whether to request something asynchronously depending on the result of a helper method.