Simple user signups with Rails

Basic design patterns to help ease the pain

Andrew Haines
Fiat Insight
3 min readApr 30, 2018

--

Most of our Rails apps include some type of authentication account for end users. That’s easy enough to do, especially with a gem like Devise. Since Rails provides so much room to maneuver, however, we try hard to maintain a simple design matrix for envisioning on-boarding, and for automating much of the logic we lean on for deeper app functionality. It’s not rocket science, but it can make running and maintaining your app, and the new user experience, much easier.

Separate signup concerns

The first thing to remember is that signups don’t have to do all the work. We favor separating user authentication from payment accounts whenever possible. That keeps concerns separated and models and controllers cleaner. We typically use the User class to handle all the signup logic, and something like an associated PaymentProfile or Customer (maybe even via an Account or Organization) to do other things. That’s a lot easier to maintain, especially when you start mixing in third-party services like the Stripe API to do other, fancy payment things.

Forget passwords

No matter, even simple signups can quickly get complicated. One way to reduce overhead is to minimize the amount of information required to create a login. Requiring only a couple of fields for signup — like an email, and maybe a name — and automating password creation helps with this.

You can use a before_validation callback to create a password, then deliver it to the user via Postmark.

before_validation :assign_password, on: :create
after_create :send_welcome_email
def assign_password
if !self.password
o = [(‘a’..’z’), (‘A’..’Z’), (‘0’..’9')].map { |i| i.to_a }.flatten
new_password = (0…10).map { o[rand(o.length)] }.join
self.password = new_password
self.password_confirmation = new_password
end
end
def send_welcome_email
client = Postmark::ApiClient.new("#{Rails.application.secrets.POSTMARK_API_KEY}")
client.deliver_with_template(
{:from=>"from@yourdomaincom",
:to=>self.email,
:template_id=>12345,
:template_model=>
{"name"=>self.first_name,
"action_url"=>"https://yourdomain.com/auth/secret/new?email=#{self.email}",
"login_url"=>"https://yourdomain.com/auth/login",
"email"=>self.email,
"password"=>self.password,
"support_email"=>"support@yourdomain.com"}}
)
end

Of course, this is a baseline. You can do a lot more for security with expiring passwords, tokens, etc. In this case, the user would be given an option to set a new password immediately from a link in the welcome email, tapping into the default Devise routing.

Tokenize everything

Creating a token for a record is a low-cost idea that affords a lot of downstream flexibility. You can pass tokens around to identify things more securely, both within your app and outside of it.

Tokenize your new User by calling include Tokenable on the model and writing a concern:

module Tokenable
extend ActiveSupport::Concern
included do
after_create :create_token
end
def create_token
o = [('a'..'z'), ('A'..'Z'), ('0'..'9')].map { |i| i.to_a }.flatten
token = (0...50).map { o[rand(o.length)] }.join
self.update_attributes(token: token)
end
end

That’ll fire every time a new user is created. Now you can more securely tie things to a user in other methods and classes throughout your app with User.find_by(token: params[:user_token]).

Extend signups to MailChimp

Another highly-desired workflow is subscribing new users to a MailChimp list. You can do that instantly with a callback on the same User class using the Gibbon gem.

after_commit :sign_up_on_mailchimp, on: :createdef sign_up_on_mailchimp
begin
gibbon = Gibbon::Request.new(api_key: "#{Rails.application.secrets.MAILCHIMP_API_KEY}")
gibbon.lists("abcde12345").members.create(body: {email_address: "#{self.email}", status: "subscribed", merge_fields: {FNAME: "#{self.first_name}", LNAME: "#{self.last_name}"}})
rescue Gibbon::MailChimpError => e
puts "Houston, we have a problem: #{e.message} - #{e.raw_body}"
end
end

Put things into background jobs

Any of these functions can be pushed to a background job, instead. We prefer using Sidekiq and Active Job. Jobs make models slimmer by compressing larger functions into fewer lines of code. They can also make finding and working on functions a little easier, especially if you carry this design pattern forward to other parts of your app. And, of course, they improve user-side performance by removing the wait time required to process a bunch of requests.

Instead of the MailChimp callback, above, you could instead write:

after_commit -> { Subscription::CreateMailchimpSubscriptionJob.set(wait: 10.seconds).perform_later(self) }, on: :create

This could refer to a job at app/jobs/subscription/create_mailchimp_subscription_job.rb:

class Subscription::CreateMailchimpSubscriptionJob < ApplicationJob
queue_as :default
def perform(new_user)
# Functional code here...
end
end

Need a partner to help you build a streamlined, fast, and secure subscription app? Let us know how we can work with you, today: https://fiatinsight.com/contact

--

--