A great user experience to allow your users to use a section of your website, a little preview of what is that we're offering, without being forced to hand over a user's email address. This is debatable to an extent.

I want to create an app that allows a potentially converted user to create a post then if they wishes to continue, they will be asked to provide an email address to become a registered member. This approach will be in the form of "try before you buy" sort-of-thing; create your beautiful post and if you want to save, you create an account. That way, they're not forced to register before start writing a content; again, this is debatable.

Publicising a section of your application will generate user retention. I've learnt this from my many failed startups and I'm here, today, to show you how to do this in the new Rails 6, beta3. Before we get into this simple application, we'll discuss two new features of Rails 6: Action Text and Action Storage.

Active Storage

Active Storage takes the pain away when performing uploading or storing of objects. In conjunction with uploads, no more you'd have to create new table columns when associating your objects. Many are familiar with using Paperclip for objects upload; now with Rails 6, when starting out with a new application, you need not to install a separate gem just to achieve an object upload. If you are coming from Rails 5 or below, you now have the option to switch from your current gem, ie Paperclip and start using the new Active Storage.

Action Text

Action Text is, basically, a replacement for a rich text editor platform that you could use throughout your application in the place of the traditional <textarea>. Action Text is used in conjunction with Active Storage for storing object: documents, images, files, etc., hence the phrase a rich text editor.

With those out the way, we'll now create our user registration flow when creating a new post. We'll make use of Action Text, Active Storage and Devise for authentication - we'll need to create a user, right? Our project will be very simple. For this application, we'll make use of Rails 6.0.0.beta3 with Ruby 2.5.0. If you haven't had rails 6 already installed, you could update your gem with

$ gem update rails

At the time of writing, you would have updated your rails gem to version 6.0.0.beta3. Proceed to install rails. Unlike me, I was faced with a readline error when trying to access the rails console, rails c. Add readline to your Gemfile and bundle install

# Gemfile

group :development do
  gem 'rb-readline'
 [..]
end

Navigate your your app's installation directory and rails s or rails s -p 3001 for port 3001. Everything should be ok at this point, if not, correct all errors before proceeding. Our application will be very basic. We'll then generate a post scaffold so we can have something quickly at our disposal:

$ rails g scaffold post title:string

Noticed how we haven't added a body or content column to our scaffold? This is because we'll make use of Action Text and Active Storage:

# Install Active Storage
$ rails active_storage:install

# Install Action Text
$ rails action_text:install

Installation process may change in future releases. After the installation, you'll see two migration files. Feel free to explore then migrate the database with rails db:migrate. Restart/start your rails app then navigate to /posts/. Everything should still works as it normally did at the beginning.

Creating An Action Text

Action Text uses the Trix editor. When displaying a rich content, our table columns live inside the Action Text record. For reference, have a look at the migration file generated. To add a content column to our post model, we simply reference it as has_rich_text

# post.rb

class Post < ApplicationRecord
  has_rich_text :content
  
  # For images, files etc
  # Uncomment for file uploads
  # Ensure to whilelist ":uploads" in your posts_controller.rb
  # has_many_attached :uploads
end

...then

# posts_controller.rb

[..]
# Whitelist :content
def post_params
  # Whitelist ":uploads" here if needed. It's just like any other attribute
  params.require(:post).permit(:title, :content)
end

Then in our _form.html.erb we display the rich_text_area as follows

<div class="field">
  <%= form.rich_text_area :content %>
</div>

Give your page a refresh and you'd now see both title and content form fields. From here you can drag and drop images into the content area, if you've uncommented code in post.rb.  Add a title and save your post. Awesome! Now, let's look at adding a user flow where a guest could create a content then register before being able to save what they've written.

Devise

Devise is used for our authentication to create a new user and manage our users. Add gem 'devise' to your Gemfile and bundle install. Once that's done, install Devise, ran the migration then generate the controllers – we'll need this later

# Generate a user model
$ rails g devise user

# Generate users controllers
$ rails g devise:controllers users

We'll implement custom logics inside the generated controllers: /users/registrations_controller.rb and /users/sessions_controller.rb. Before we do, update your routes.rb as follows:

Rails.application.routes.draw do
  # Add this line below
  # to use our custom controllers:
  devise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations' }
  resources :posts
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Scenarios

  • Guest user composes a post/article then:
  • Either signs in or
  • They register

Let's take a look at a scenario when a user registers.

# /users/registrations_controller.rb

# POST /resource
def create
  # super
  build_resource(sign_up_params)

  resource.save
  yield resource if block_given?
  if resource.persisted?
    if resource.active_for_authentication?
      set_flash_message! :notice, :signed_up
      sign_up(resource_name, resource)

      if session[:post]
        # Save post
        Post.new(session[:post]).save

        session[:post] = nil
        return redirect_to :posts
      end

      respond_with resource, location: after_sign_up_path_for(resource)
    else
      set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
      expire_data_after_sign_in!
      respond_with resource, location: after_inactive_sign_up_path_for(resource)
    end
  else
    clean_up_passwords resource
    set_minimum_password_length
    respond_with resource
  end
end

We've overwritten the default behaviour of Devise's create method and introduced a "check for a session variable" of post. This is not yet mentioned; we'll get to it. If a session variable is present, we create the post from our stored session. After, clear the session then redirect the logged in user to the posts route. If no session, continue with the normal Devise user registration flow.

# /users/sessions_controller.rb

# POST /resource/sign_in
def create
  # super
  self.resource = warden.authenticate!(auth_options)
  set_flash_message!(:notice, :signed_in)
  sign_in(resource_name, resource)

  if current_user && session[:post]
    # Save post
    Post.new(session[:post]).save
    # Clear the current session
    session[:post] = nil
    return redirect_to :posts
  end

  respond_with resource, location: after_sign_in_path_for(resource)
end

Same principle as our registrations_controller.rb implementation. Checks for a logged in user, a session and if both are true, create a post object in our database. Now to put it together, make the last edit to our PostsController

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # New line added:
  before_action :create_user_then_save_post, only: :create

  [...]

  private
    [...]
    
    def create_user_then_save_post
      return if current_user

      temporarily_save_post_in_session(post_params)

      # New user has the option to signin or register
      redirect_to :new_user_session
    end

    def temporarily_save_post_in_session(post_params)
      session[:post] = post_params
    end
end

Two new methods introduced, create_user_then_save_post and temporarily_save_post_in_session. Before a new post is created, the first method that executes is the create_user_then_save_post. If a user is not logged in, we save the post params in a session then redirect the user to sign in. There, they can either sign in or register. Give it a try! There's a slight issue, though. Have you seen it? 👀

Associating A User To A Post

Previously when we created a new post, it's not being assigned to a user. This was ok to demonstrate a user registration flow. Now, we need to associate a post with a user and vice-versa.

$ rails g migration add_user_id_to_post user_id:Integer

As I write this article, I'm experiencing a hang running any rails commands. The solution was to stop spring with bin/spring stop then run the command again. Your migration file should look something like:

class AddUserIdToPost < ActiveRecord::Migration[6.0]
  def change
    add_column :posts, :user_id, :Integer
  end
end

Now migrate the database and restart your rails app. In both sessions_controller.rb and registrations_controller.rb:

# Replace
Post.new(session[:post]).save

# With
current_user.posts.create(session[:post])

Now edit your user.rb:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :posts, dependent: :destroy
end

...and post.rb:

# post.rb

class Post < ApplicationRecord
  has_rich_text :content
  belongs_to :user
  
  # For images, files etc
  # Uncomment for file uploads
  # Ensure to whilelist ":uploads" in your posts_controller.rb
  # has_many_attached :uploads
end

Create a new post, register/sign in, and you should see your post saved in the database.

To confirm that a post is now being associated to a user, inside your rails console, run:

$ User.first.posts

# Or
Posts.last.user

Congratulations 👏🏻 This way I'm sure you'll see more user registration for your application – "try before you hand over any details" 🤗

There are a lot of things to learn about Rails 6, most of which I'm just learning myself. As I find things that are useful, I'll create more blog posts like this. Found this useful? Why not subscribe below or follow me on Twitter.

I've provided this repo for you to try out on your local machine.

Until next time, 👋🏻