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.
Well, why cookie
?
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
.
How is this related to following a user?
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>
Clicking the link
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.
Why a Cookie?
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.
Transfer from Cookie to Database
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.