Eric / Brooklyn

Random 5

Operator as Methods

Web Accounts

Honeypot Bots

Stripe and Tap

Git Save

Whitespace Problems

Ruby Exits

Appending in Javascript

Sendgrid Ban

Clean URLs

Integer Division

Multi-tab Websockets

Bad Content

JS Data Chutzpah

Responsive tables

Concerns

Cookies

Emoji Bits

Git

Ruby vs. Ruby

Extending Devise

Rails UJS

ENV Variables

See All

Browser Cookies and Query Strings

July 2020

A Little Backstory

Browser cookies are called cookies because Lou Montulli said so in 1994.

Who is Lou Montulli? He created the first live Fishcam on the internet, among other things like creating the Browser that you’re looking at right now.

There is some inconclusive discussion about this, so let’s pick our favorite theory — the name comes from Fortune Cookies — a little message that is embedded inside a Sugar Wafer. And your Browser is the Sugar Wafer.

Bad Cookies

Cookies are used by surveillance networks like Facebook to track you so they can add to their million page file on you. This file is used to addict you to their morphine drip of algorithmically-induced psycho-drama.

There are some regulations that now exist that tell you this is happening, and force you to click somewhere. Trouble is these interfaces are mostly created by the same dark UX artists of the previous paragraph, so the results of these well-intentioned regulations are unclear.

Good Cookies

Not all cookies are bad. They can remember our passwords or keep us logged in.

They can also hold onto less-essential user data, without the need to log in. This could be some sort of browser preference — maybe you want dark mode, or maybe you want your weather site to remember your zip code.

Another Scenario

At Bounce, we want to show a realistic view of the website when a user is logged out. This is helpful so a user doesn’t have to learn a new UX once she creates an account.

An example of this would be placing a Follow button on a teacher’s profile page, regardless of whether the visitor is logged in or not. (Here’s an example.)

If you are logged in, great, clicking Follow works just as intended. If you aren’t logged in, clicking Follow needs to work a little differently.

Conditional Buttons

We’ll just wrap our buttons in a Devise helper in our views:

<% # this is on a profile page %>

<% if user_signed_in? %>
	<% button_to "Follow", follow_path %>
<% else %>
	<% link_to "Follow", sign_up_path %>
<% end %>

The text for both buttons is Follow, even though we see the second link actually goes to the sign_up_path. This is because, even if you’re logged out, we want to be clear that you can Follow a user from her profile page.

As a quick aside, the functioning Follow button is a Rails button_to, because the default request will be a POST (and we want to post information to our database), whereas the non-functioning Follow button is simply a redirect to a Sign Up page (via link_to).

Now this is okay, but not good. A logged-out user clicks Follow and then is taken to a Sign Up page, but we forgot about the Follow, which was the intended action.

Query Strings

Query strings are stringified key-value pairs that are appended to URLs. It’s the part that comes after the ? in a URL that looks like: google.com/signup?robot=true&satisfied=5.

It’s worth noting that the query strings don’t affect our routing.

These key-value pairs become un-stringified via Rails params helper (available in our Controller or Views). So in the above URL, we know that params[:robot] is true, and params[:satisfied] is 5.

They’re useful because we can attach information to URL. Getting back to our earlier use case, we wanted a way to remember that a logged-out user intended to follow someone.

Our button for the logged out user looked something like:

<% link_to "Follow", sign_in_path %>

which renders into HTML as:

<a href=“https://bouncechats.com/sign_in”>Follow</a>

If we could turn the above link into something like:

<a href=“https://bouncechats.com/sign_in?follow=4”>Follow</a>

where 4 is the id of the user-to-be-followed, we will be able to carry through this information after the user signs up.

To do this, we just need to update our link_to on the teacher profile page to look like this:

<% link_to "Follow", sign_in_path(follow: @user.id) %>

Assuming we have the @user instance variable in our view, this will create render to:

<a href=“https://bouncechats.com/sign_in?follow=4”>Follow</a>

To recap: We’re logged out, and we click a Follow button withe a URL that looks like https://bouncechats.com/sign_in?follow=4.

Now, we’re sitting on the sign up page, with the query string above.

Remember our earlier Rails params helper — this gives us access to the query string as a parameter. In this view, we can say:

<% if params[:follow].present? %>
	<% cookies[:follow] = {value: params[:follow], expires: 10.days } %>
<% end %>

If the sign-up page notices that you arrive on the page with follow-intent (i.e. params[:follow].present?), let’s save a cookie with the same name, who we want to follow, and let it expire at some point later on.

Ideally we’d save this to the database right away, but you don’t even have an account yet.

A cookie allows us to temporarily save this information as you move page-to-page while logged out.

After you complete the sign-up form, we want to Follow that person right away, as when you arrive on your dashboard, you should see her.

We’ll take care of this using an after_action in our inherited Users::RegistrationsController.

This Controller set-up is a bit nontraditional, but essentially, we’re wrangling back some control of a UsersController by inheriting from a Devise class.

class Users::RegistrationsController < Devise::RegistrationsController
	after_action :follow_after_signup, only: :create

	private

  def follow_after_signup
    new_follow = cookies[:follow]

    return unless user_signed_in?

    return if cookies[:follow].blank?

    u = User.find(new_follow)
    if !current_user.following?(u) && !current_user?(u)
      current_user.follow(u)
    end
    cookies.delete :follow
  end
end

Right after you sign up, we find the user from your cookies.

Then we make sure that you’re logged in. This may seem redundant at first — if this is an after_action on the create action of the Users::RegistrationsController, wouldn’t we expect already be logged in?

Unfortunately, this isn’t guaranteed — if a validation fails during registration, the after_action in the controller will still run. This is different from an after_create in the model (which only runs on successful creates).

So you could arrive here and still be logged out.

After we ensure you’re logged in, we check to make sure that a) you aren’t following this other user already, and b) you aren’t trying to follow yourself. (.following?(user) is an instance method we created in the User model, and current_user?(user) is a Devise helper method)

If everything’s alright, we follow the intended user (.follow(user) is an instance method in the User model). And then delete the cookie to clean things up.

What if we already have an account?

So maybe we logged out, or we’re using a different browser. We arrive on the profile page and click Follow.

We’re redirect to the sign up page, but we already have an account?

No stress — we just click over to the Login page, and we’re still good to go. This is because the cookie was saved when we first arrived on the sign-up page, and it will persist if we move pages, in this case by moving the the login page.

We just need to make sure we run follow_after_signup in our Users::SessionsController, which handles logging in and out. The easiest way to share this code will be using a Concern with ActiveSupport .

Conclusion

Some cookies are good and others are bad. That’s the end of this post.