Eric / NYC / Milky Way

Visual

Blog Index

Random 5

Stripe and Tap

Multi-tab Websockets

Ruby vs. Ruby

Cookies

Web Accounts

Emoji Bits

Honeypot Bots

Bad Content

JS Data Chutzpah

Sendgrid Ban

Clean URLs

Git

Concerns

Rails UJS

Extending Devise

ENV Variables

See All

Dynamically Set Rails UJS

March 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_tos 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.