Gradual Engagement with Rails and Devise

Blueprint Ruby on Rails

Last updated Feb 16, 2016

Have you ever wanted to test-drive a website or app, only to surrender after facing the annoying task of providing an email and password? Fortunately, as a Rails developer, you don’t have to subject your users to this tortuous activity. Instead, you can treat ever visitor like registered user and gradually gather information at later stages in the engagement cycle. By overriding some of devise’s default methods, we can treat every visitor like a registered user and initiate engagement after they perform certain actions, also known as lazy registration. This strategy is proven to reduce bounce rates, increase app usage, and will give you much more control of your interaction with users.

Let's Build Tasksnail

This lesson is Part 1 of a 3 part series demonstrating how to build an simple One-Page Task Management App named TaskSnail. 

Lesson Resources

Lesson Checkpoints

1. App Design

To demonstrate gradual engagement, we are going to build a basic task management app with a User model and Task model. We want non-registered users (which we’ll call soft users) to be able to create tasks without registering. After they create a 2 tasks, we will require registration before any additional tasks can be created. If the app is well-designed, our Users should be excited to participate when we trigger the next stage in the engagement cycle. The challenge is tracking their activity, then mapping it to their user account after registration. Let’s get started by building the initial app called TaskSnail.

Create the Initial App

$ rails new tasksnail

Install and Generate the Devise User Model

Make sure to follow all of the recommended Devise configuration settings. 

Add gem 'devise' to the Gemfile and run:

$ bundle
$ rails generate devise:install
$ rails generate devise User
$ rails generate devise:views
$ rake db:migrate

 

Generate the Task Model

$ rails generate scaffold Tasks name:string complete:boolean user:belongs_to
$ rake db:migrate

Adding the user:belongs_to line in our migration will create a user_id column on the Tasks table with the necessary index.

 

Define ActiveRecord Relationships

Now that our resources have been generated, we just need to define the ActiveRecord relationships in the model files.

models/user.rb

has_many :tasks, dependent: :destroy

models/task.rb

belongs_to :user

2. Overriding the current_user Method in Devise

We can override Devise methods by redefining them in our Application Controller. Our goal here is have a User object that is only tied a random key in the cookie.

First, let’s override the authenticate_user! method. This helper method will go into our controllers to authenticate users before they can perform certain actions. By default, devise will automatically redirect visitors to the sign in page. We can override this method by redefining it in the applcation controller.

controllers/application_controller.rb

def authenticate_user!
  current_user.present?
end

Second, we are going override the current_user method to define our unregistered visitors, or Soft Users. Our method will first check for an already registered user by calling super. If none exists, we will instantiate our User class, then use the where method to search the database for an existing token or initialize a new token if nothing is found. As the visitor uses our app, we can use this token to save and track their activity. We will use the information in this token to trigger different levels of engagement.

def current_user
   super || User.where(soft_token: soft_token).first_or_initialize
end

Third, in order to get our current_user method working, we need to define the soft_token method. We use the session method to store this information until it is ready to be saved to the database.

private

def soft_token
  session[:user_token] ||= SecureRandom.hex(8)
end

3. Create Database Columns for the Soft Token

At this point, our app is going to raise an exception becuase there is no soft_token column in the database. We need to run a migration to add this column to our User model. We also need to add a soft_token to the Task model. This will allow us to merge tasks created by the Soft User when the User decides to become fully registered.

$ rails g migration AddSoftTokenToUsersAndTasks

db/migrate/add_soft_token_to_users_and_tasks.rb

def change
  add_column :users, :soft_token, :string
  add_column :tasks, :soft_token, :string
end

Then run the migration:

$ rake db:migrate

Inspecting Soft Users

A soft_token is now generated when our app receives a new visitor. It is useful to inspect this behavior in the browser (or better yet via testing). One way to to debug issues in development is to add <%= current_user.inspect %> to views/layouts/application.html.erb. When we visit our site, we can see that a soft_token attribute is attached to the current_user.

4. Define a Utility Method to Check User Status

At this point, it is going to be useful to have some utility methods to test whether a user is a Soft User or Fully Registered. To keep things simple we are going to check is a user is either a "Soft User" or “Fully Registered” based on their email address. Since our full users must provide a valid email address, we can check if this attribute is empty to distinguish them.

models/user.rb

def soft_user?
  self.email.empty?
end

Now if we call current_user.soft_user? and it returns true, we know the user is a soft_user. A false return means the user is fully registered. This logic is the most basic aspect of gradual engagement.

5. Assign the soft_token in the Tasks Controller

In TaskSnail, we want to allow Soft Users to create a couple tasks before registering. We will need to pass their soft_token to the tasks controller.

With our utility method, we check to see if the current_user is a soft_user?. If this returns true, we are going to initialize a task, then assign the soft token value to both the Task obeject and the User object. This will allow us to match and merge the Soft User to this task when they decide to register in the future. If soft_user? returns false, we will simply create a new task like normal.

controllers/tasks_controller.rb

def create
  @user = current_user
  @task = Task.new(task_params)

  if @user.soft_user?
    @task.soft_token = @user.soft_token
  end

  @task.save
  respond_with(@task)
end

6. Updating Permitted Parameters with Devise

Now it’s time to start handling the transition from Soft User to Fully Registered User.We are going to pass the soft_token from the view to the create action in the tasks_controller, so we will need to make sure is permitted through Strong Parameters. In devise, we can permit the parameter updating the Registrations Controller.

Override the Devise Registrations Controller

Run the following command to generate the Devise controllers we need to override:

$ rails generate devise:controllers users

Update Routes

We need to tell the router to use the controller we’re overriding, as opposed to the default devise controller.

config/routes.rb

devise_for :users, controllers: { registrations: “users/registrations” }

controllers/users/registrations_controller.rb

Uncomment the following callback at the top of the controller file:

before_filter :configure_sign_up_params, only: [:create]

At the bottom, uncomment:

protected

def configure_sign_up_params
  devise_parameter_sanitizer.for(:sign_up) << :soft_token
end

views/devise/registrations/new.html.erb

Finally, we need to send the soft_token as a parameter through the view. Inside the form_for block, add:

<%= f.hidden_field :soft_token, value: current_user.soft_token %>

7. Merging Soft Users & Tasks after Registration

So a Soft User messed around with our app, created some tasks, and wants to become a full blown member. Sweet!

The only problem is that if they use the existing new User registration path, none of their tasks are going to go with them. We need a way to merge the Soft User’s associated tasks when they go fill out the full registration form. To overcome this, we are going to make additional changes to the registrations_controller.

Let's create a merge_tasks method that will find all Task objects with a soft_token that matches the current user. Next, we can loop through the matching tasks and assign them to current_user and save each task to the database.

controllers/users/registrations_controller.rb

def merge_tasks
  tasks = Task.where(soft_token: current_user.soft_token)
  tasks.map do |task|
    task.user = current_user
    task.user_id = current_user.id
    task.save!
  end
end

Now we need to call the merge_tasks method after the user registers. We can do this by overriding the after_sign_up_path_for method in devise. Make sure to call super afterwards to ensure the devise’s default behavior continues after we merge the tasks.

def after_sign_up_path_for(resource)
  merge_tasks
  super(resource)
end

8. Implementing Gradual Triggers

That’s pretty much it! Every visitor to your site is now tracked via the soft_token and will have their data merged when they register. Now it’s up to your own creative genius on how you want to implement Gradual Engagement in your app.

To get started, one simple way is to require registration after two tasks have been created.

models/user.rb

def needs_engagement?
  tasks = Task.where(soft_token: self.soft_token)
    if self.soft_user? && tasks.count >= 2
  end
end

views/tasks/new.html.erb

<% if current_user.needs_engagement? %>
<%= link_to 'You Must Register!', new_user_registration_path %>
  <% else %>
  <% render 'form' %>
<% end %>

This will prevent non-registered users from creating more than two associated tasks in the app. If they try to create a third task, the form will be replaced by a "You Must Register" link. You can apply this same type of logic to different steps in the engagement process, such as collecting additional User information based on their participation frequency.

Comments