Skip to content

Instantly share code, notes, and snippets.

@jasper502
Last active December 30, 2022 18:07
Show Gist options
  • Save jasper502/065a63fde266fac1fbf5 to your computer and use it in GitHub Desktop.
Save jasper502/065a63fde266fac1fbf5 to your computer and use it in GitHub Desktop.
Devise with additional user fields, nested models / attributes and custom routes
The idea here is that I have a single User login that can eventually be tied to multiple Companies via the Role.
You signup via Devise on the custom /register route and fillout the Company information etc. The custom registration controller creates the Role during the user creation and sets a few other attributes.
It all worked but now it's broken and I have no idea what I did to break it.
When I try to create a new user / company the additional user fields (name_first & name_last) always fail validation regardless if they are in fact valid. The nested Company fields do not validate at all. If I enter the email and password field only the form works but only creates the User record.
To me it seems like the custom registration controller is not being processed at all.
*UPDATE*
Added my new routes file - I switched my routes to "user/..." from "devise/..." and that seemed to do the trick.
Also changed the logic in my regisitration controller so that the Role and Company updates are after the User is saved.
class Company < ActiveRecord::Base
has_many :roles, :dependent => :destroy, :inverse_of => :user
has_many :users, :through => :roles
has_one :user, :inverse_of => :company
validates :name, :presence => { :message => "^Company Name Required" }
validates :address1, :presence => { :message => "^Company Address Required" }
validates :city, :presence => { :message => "^City Required" }
validates :province, :presence => { :message => "^Province Required" }
validates :postal, :presence => { :message => "^Postal Code Required" }
end
<%= form_for(resource, :html => {:class => "form-signin" }, as: resource_name, url: user_registration_path) do |f| %>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 text-center">
<h3>Account Owner</h3>
</div>
<div class="col-md-6 text-center">
<h3>Company Information</h3>
</div>
</div>
<div class="row">
<div class="col-md-6">
<fieldset>
<div class="form-group">
<%= f.label "First Name" %>
<%= f.text_field :name_first, class: "form-control", placeholder: "First Name", autocomplete: "on" , autofocus: true %>
</div>
<div class="form-group">
<%= f.label "Last Name" %>
<%= f.text_field :name_last, class: "form-control", placeholder: "Last Name", autocomplete: "on" %>
</div>
<div class="form-group">
<%= f.label "Phone Number" %>
<%= f.text_field :phone, class: "form-control", placeholder: "Phone Number", autocomplete: "on" %>
</div>
<div class="form-group">
<%= f.label "Email" %>
<%= f.email_field :email, class: "form-control", placeholder: "Email", autocomplete: "on" %>
</div>
<div class="form-group">
<%= f.label "Password" %>
<%= f.password_field :password, class: "form-control", placeholder: "Password", autocomplete: "on" %>
</div>
<div class="form-group">
<%= f.label "Confirm Password" %>
<%= f.password_field :password_confirmation, class: "form-control", placeholder: "Password Confirmation", autocomplete: "on" %>
</div>
</fieldset>
</div>
<div class="col-md-6">
<fieldset>
<%= f.fields_for :roles, resource.roles.build do |r| %>
<%= r.fields_for :company, resource.roles.build.build_company do |c| %>
<div class="form-group">
<%= c.label "Company Name" %>
<%= c.text_field :name, class: "form-control", placeholder: "Company Name", autocomplete: "on" %>
</div>
<div class="form-group">
<%= c.label "Address" %>
<%= c.text_field :address1, class: "form-control", placeholder: "Address", autocomplete: "on" %>
</div>
<div class="form-group">
<%= c.label "Address Continued (if required)" %>
<%= c.text_field :address2, class: "form-control", placeholder: "Address Continued", autocomplete: "on" %>
</div>
<div class="form-group">
<%= c.label "City" %>
<%= c.text_field :city, class: "form-control", placeholder: "City", autocomplete: "on" %>
</div>
<div class="form-group">
<%= c.label "Province" %>
<%= c.text_field :province, class: "form-control", placeholder: "Province", autocomplete: "on" %>
</div>
<div class="form-group">
<%= c.label "Postal Code" %>
<%= c.text_field :postal, class: "form-control", placeholder: "Postal Code", autocomplete: "on" %>
</div>
<% end %>
<% end %>
</fieldset>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<input class="btn btn-lg btn-primary btn-block" type="submit" value="Register">
</div>
</div>
</div>
<% end %>
def create
build_resource(sign_up_params)
resource_saved = resource.save
yield resource if block_given?
if resource_saved
# Update the role to an owner and make default
resource_saved.roles.last.assign_attributes(role: "owner", active: 1, default_role: 1)
resource_saved = resource_saved.save
# Update the company to show the owner - not sure I need this
resource_saved.roles.last.company.update_columns( user_id: resource.id )
resource_saved = resource_saved.save
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
@validatable = devise_mapping.validatable?
if @validatable
@minimum_password_length = resource_class.password_length.min
end
respond_with resource
end
end
class Role < ActiveRecord::Base
belongs_to :user, :inverse_of => :roles
validates_presence_of :user, :message => "user validation"
belongs_to :company, :inverse_of => :roles
validates_presence_of :company, :message => "company validation"
accepts_nested_attributes_for :company
end
devise_for :users, :controllers => { :registrations => 'users/registrations' }, skip: [:sessions, :passwords, :confirmations, :registrations, :unlocks]
devise_scope :user do
# authentication
# unauthenticated :user do
# root 'index#landing'
# end
# authenticated :user do
# root to: :dashboard
# end
get '/signin', to: 'devise/sessions#new', as: 'new_user_session'
post '/signin', to: 'devise/sessions#create', as: 'user_session'
get '/signout', to: 'devise/sessions#destroy'
delete '/signout', to: 'devise/sessions#destroy', as: 'destroy_user_session'
# registrations
get '/register', to: 'users/registrations#new', as: 'new_user_registration'
post '/register', to: 'users/registrations#create', as: 'user_registration'
# user accounts
scope '/account' do
# confirmation
get '/verification', to: 'users/confirmations#verification_sent', as: 'user_verification_sent'
get '/confirm', to: 'users/confirmations#show', as: 'user_confirmation'
get '/confirm/resend', to: 'users/confirmations#new', as: 'new_user_confirmation'
post '/confirm', to: 'users/confirmations#create'
# passwords
get '/reset-password', to: 'users/passwords#new', as: 'new_user_password'
get '/reset-password/change', to: 'users/passwords#edit', as: 'edit_user_password'
put '/reset-password', to: 'users/passwords#update', as: 'user_password'
post '/reset-password', to: 'users/passwords#create'
# unlocks
post '/unlock', to: 'users/unlocks#create', as: 'user_unlock'
get '/unlock/new', to: 'users/unlocks#new', as: 'new_user_unlock'
get '/unlock', to: 'users/unlocks#show'
# settings & cancellation
# get '/cancel', to: 'users/registrations#cancel', as: 'cancel_user_registration'
get '/profile', to: 'users/registrations#edit', as: 'edit_user_registration'
put '/profile', to: 'users/registrations#update'
# account deletion
# delete '', to: 'users/registrations#destroy'
end
end
Rails.application.routes.draw do
devise_for :users, :skip => [:sessions, :registrations], :controllers => { :registrations => 'users/registrations', :sessions => 'users/sessions' }
as :user do
#REGISTRATON
#resource :registration, only: [:new, :create, :edit, :update]
get "/register/cancel" => "devise/registrations#cancel", as: :cancel_user_registration
post "/register/" => "devise/registrations#create", as: :user_registration
get "/register/" => "devise/registrations#new", as: :new_user_registration
get "/account/profile" => "devise/registrations#edit", as: :edit_user_registration
patch "/account/profile" => "devise/registrations#update"
put "/account/profile" => "devise/registrations#update"
#SESSIONS
get '/signin' => 'devise/sessions#new', :as => :new_user_session
post '/signin' => 'devise/sessions#create', :as => :user_session
delete '/signout' => 'devise/sessions#destroy', :as => :destroy_user_session
end
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
root 'index#landing'
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :timeoutable
# http://stackoverflow.com/questions/14417201/how-to-automatically-keep-user-remembered-in-devise
def remember_me
true
end
has_many :roles, :dependent => :destroy, :inverse_of => :user
has_many :companies, :through => :roles
belongs_to :user, :inverse_of => :company
accepts_nested_attributes_for :roles, :limit => 1, :allow_destroy => true
validates :name_first, presence: { message: "^First Name Required" }
validates :name_last, presence: { message: "^Last Name Required" }
validates :phone, numericality: { only_integer: true, message: "^Only Numbers Allowed in Phone Number" }
validates :phone, length: { is: 10, message: "^Phone number must be 10 digits long i.e. 4035551234" }
end
#class RegistrationsController < Devise::RegistrationsController
class Users::RegistrationsController < Devise::RegistrationsController
# POST /resource
### CUSTOM ###
def create
build_resource(sign_up_params)
# The last role should be the one created in the form
# We set the values here so they get saved and they aren't passed in the form
resource.roles.last.assign_attributes(role: "owner", active: 1, default_role: 1)
resource_saved = resource.save
resource.roles.last.company.update_columns( user_id: resource.id )
yield resource if block_given?
if resource_saved
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
@validatable = devise_mapping.validatable?
if @validatable
@minimum_password_length = resource_class.password_length.min
end
respond_with resource
end
end
protected
### CUSTOM ###
def sign_up_params
#devise_parameter_sanitizer.sanitize(:sign_up)
params.require(:user).permit(:email, :password, :password_confirmation, :name_first, :name_last, :phone, roles_attributes: [ company_attributes: [ :id, :name, :address1, :address2, :city, :province, :postal ] ] )
end
def account_update_params
params.require(:user).permit(:email, :password, :password_confirmation, :current_password, :name_first, :name_last, :phone, :mobile, :title, :location, :timezone )
end
def after_update_path_for(resource)
edit_user_registration_path
end
#def set_minimum_password_length
# if devise_mapping.validatable?
# @minimum_password_length = resource_class.password_length.min
# end
#end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment