Simple user signups with Rails
Basic design patterns to help ease the pain
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_emaildef 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
enddef 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