Nithin Bekal About

Gradual engagement with Rails

06 Jun 2015

When a visitor reaches your website, you might want to allow them to use some of the functionality of your app without creating an account. This is often useful in e-commerce applications where you might want to allow users to add items to cart and only prompt them to create an account at checkout.

This UX technique is called gradual engagement. Not requiring signup right away increases the chances of visitors trying it out and is a great way to increase conversions. In this post, we will walk through adding lazy registration to a simple app.

Our app allows users to submit short “posts” on the site. So let’s start with a simple PostsController where users can submit posts. Right now, it looks like this:

class PostsController < ApplicationController
  before_action :authenticate_user!

  def create
    @post = current_user.posts.build(post_params)
    if post.save
      redirect_to @post, notice: 'Post saved'
    else
      render 'new'
    end
  end
end

Before the post is created, the authenticate_user! method prompts the user to login if the aren’t already.

To keep track of posts created by guest users, we create a Guest model, with a token field which will contain a randomly generated token that we will store in the session. The Post and Guest models look like this:

# app/models/post.rb
class Post < ActiveRecord::Base
  belongs_to :user
  belongs_to :guest
end

# app/models/guest.rb
class Guest < ActiveRecord::Base
  has_many :posts
end

We can now rewrite posts#create method so that we associate Guest to each post.

class PostsController < ApplicationController
  def create
    @post = Post.new(post_params)
    @post.user  = current_user
    @post.guest = guest_user

    if post.save
      redirect_after_save
    else
      render 'new'
    end
  end

  private
  def redirect_after_save
    if user_signed_in?
      redirect_to @post, notice: 'Post saved'
    else
      redirect_to register_path, notice: 'Please create an account to continue'
    end
  end
end

Let’s review the changes we’ve made here:

  • There is no authenticate_user! filter, so visitors will be allowed to submit posts even if they aren’t signed in.
  • Instead of current_user.posts.new(post_params), we’re instantiating a Post and then assigning the user. This automatically sets user to nil if a user isn’t signed in.
  • In the @post.guest = guest_user line, we’re assigning a guest user record. Note that we haven’t defined guest_user method yet, and will be defining it in ApplicationController next.
  • After saving a post, we’re redirecting to registration page if a user isn’t signed in. For signed in users, we’re redirecting to the post page as we were before.

We called a guest_user method in posts#create, so let’s go ahead and define it in ApplicationController. This looks for a Guest record based on a guest token. This token is created when the method is called for the first time and stored in the session.

# app/controllers/application_controller.rb
class ApplicationController
  # other stuff ...

  def guest_user
    Guest.where(token: guest_token).first_or_create
  end

  private
  def guest_token
    session[:guest_token] ||= SecureRandom.uuid
  end
end

We now have the guest records, and we need to associate them to the user once they have completed registration. I’ll quickly walk through how we could do this with Devise, but you could alter this easily to work with whatever authentication system you’re using.

In Devise you’ll need to override the default registrations controller, and call an setup_account method after the user account is created.

# config/routes.rb
devise_for :users, controllers: { registrations: 'registrations' }

# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  after_action :setup_account, only: :create

  private

  def setup_account
    return unless resource.persisted?
    guest_user.posts.each do |post|
      post.user = resource
      post.save
    end
  end
end

In the above example, the setup_account method gets called even if there’s a validation error when saving a user account, so we need the return unless resource.persisted? line to skip the setup in case of validation errors.

Now that you’ve added lazy registration to your app, there are a few more things you might want to look into:

  • Sometimes an existing user might create a post without signing in, so you will need to check after login if the user has created any posts and associate those posts as well.

  • Over time, a lot of guest accounts and posts might accumulate in the database. If you don’t want to keep them, you might want to write a cron job that periodically checks and deletes old records.

I’ll not cover those things and end this tutorial here. Hopefully this has given you ideas about how you could improve the experience of the users of your web apps.

Further reading

Hi, I’m Nithin! This is my blog about programming. Ruby is my programming language of choice and the topic of most of my articles here, but I occasionally also write about Elixir, and sometimes about the books I read. You can use the atom feed if you wish to subscribe to this blog or follow me on Mastodon.