There are different kinds of users in my system. One kind is, let's say, a designer:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
That is created immediately when the user signs up. So when a user fills out the sign_up form, a Devise User
is created along with this Designer
object with its user_id
set to the new User
that was created. It's easy enough if I have access to the code of the controller. But with Devise, I don't have access to this registration controller.
What's the proper way to create a User
and Designer
upon registration?
In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
@signup = Signup.new
end
def create
@signup = Signup.new(params[:signup])
if @signup.save
sign_in @signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
The referenced signup model is defined as a form object.
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless @user.valid?
delegate_errors_for_account unless @account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
@user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
@account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, @user.errors[:name].first) if @user.errors[:name].present?
errors.add(:email, @user.errors[:email].first) if @user.errors[:email].present?
errors.add(:password, @user.errors[:password].first) if @user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, @account.errors[:name].first) if @account.errors[:name].present?
end
def persist!
@user.save!
@account.save!
create_admin_membership
end
def create_admin_membership
@membership = Membership.create! do |membership|
membership.user = @user
membership.account = @account
membership.admin = true
end
end
end
An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.
In all, I prefer this approach vastly over using accepts_nested_attributes_for
, though there might be even greater ways out there. Let me know if you find one!
===
Edit: Added the referenced models and their associations for better understanding.
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.
If you don't want to change the registration controller, one way is to use the ActiveRecord callbacks
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With