Reputation: 683
I have read the posts here, here, and here, but I'm still having trouble with implementing Single Table Inheritance.
Ideally I would like to have two registration paths (one for clients and one for providers) with the common fields name, email, password, and confirm_password, and the provider registration having an extra radiobutton field to specify a provider type. I am doing the registration through devise. Upon clicking submit on the registration form a user would then be redirected to a second form which is totally different for clients and providers (I have been doing this using the edit page for a resource).
As it stands, everything works if I am just doing it through User, but as soon as I add single table inheritance the registration forms tell me they are missing the requirements of the second forms.
Here is my config/routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => {:sessions => "sessions"}, :skip=> :registrations
devise_for :clients, :providers, :skip=> :sessions
resources :clients
resources :providers
root :to=>'pages#home'
match '/home', to: 'pages#home', via: 'get'
end
My models look as follows:
User:
class User < ActiveRecord::Base
before_save {self.email = email.downcase}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :name, presence: true, length: {maximum: 50}
validates :email, presence: true, :email => {:ban_disposable_email => true, :message => I18n.t('validations.errors.models.user.invalid_email')}, uniqueness: { case_sensitive: false }
validates :password, presence: true, length: {minimum: 6},:if=>:password_validation_required?
LOGO_TYPES = ['image/jpeg', 'image/png', 'image/gif']
has_attached_file :avatar, :styles => {:medium => "300x300>",:square=>"200x200>", :thumb => "100x100>" }, :default_url => '/assets/missing_:style.png'
validates_attachment_content_type :avatar, :content_type => LOGO_TYPES
def password_validation_required?
[email protected]?
end
end
Client:
class Client < User
validates :industry, presence: true
validates :city, presence: true
validates :state, presence: true
validates :description, presence: true, length: {minimum: 50, maximum: 300}
end
Provider:
class Provider < User
validates :ptype, presence: true
validates :city, presence: true
validates :state, presence: true
validates :education, presence: true
validates :biography, presence:true, length: {minimum: 50, maximum: 300}
validates_format_of :linkedin, :with => URI::regexp(%w(http https))
validates :resume, presence: true
has_many :disciplines
end
and here are my controllers:
class SessionsController < Devise::SessionsController
def create
rtn = super
sign_in(resource.type.underscore, resource.type.constantize.send(:find,resource.id)) unless resource.type.nil?
rtn
end
end
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
if resource.is_a?(User)
if current_user.is_a?(Client)
edit_client_path(current_user.id)
elsif current_user.is_a?(Provider)
edit_provider_path(current_user.id)
end
else
super
end
end
end
class ClientsController < ApplicationController
def show
@client = Client.find(params[:id])
end
def edit
@client = Client.find(params[:id])
end
def update
@client = Client.find(params[:id])
if @client.update_attributes(client_params_edit)
flash[:success] = "Profile Updated"
redirect_to @client
else
flash[:failure] = "Profile Information Invalid"
render 'edit'
end
end
def client_params_edit
params.require(:client).permit(:avatar,:industry,:city,:website, :description)
end
end
the provider controller is quite similar.
Finally, here is my schema.rb
:
ActiveRecord::Schema.define(version: 20140628213816) do
create_table "disciplines", force: true do |t|
t.integer "years"
t.string "description"
t.integer "user_id"
end
create_table "users", force: true do |t|
t.string "name"
t.string "email"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.string "password_digest"
t.string "industry"
t.string "city"
t.string "state"
t.string "website"
t.string "description"
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "type"
t.string "ptype"
t.string "education"
t.string "resume_file_name"
t.string "resume_content_type"
t.integer "resume_file_size"
t.datetime "resume_updated_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
Upvotes: 9
Views: 1884
Reputation: 1576
You need to specify which model should be instantiated inside your custom registrations controller (that one which inherits from Devise::RegistrationsController
).
You have to override the protected method called resource_class
to somewhat like this:
def resource_class
# for example you pass type inside params[:user]
klass = params[:user].try(:[], :type) || 'Client'
# we don't want wrong class to be instantiated
raise ArgumentError, 'wrong user class' unless ['Client', 'Provider'].include?(klass)
# transform string to class
klass.constantize
end
Also you might want to override sign_up_params
to specify allowed params based on user type too.
Upvotes: 2
Reputation: 2195
Just a thought. Have you considered allowing registration as a user and holding the type parameter back until later in the workflow.
i.e.
Registration page: Creates User (with a parameter that decides which Type the user will end up being)
Second page (to which you are automatically redirected upon creating user, or even logging in as user having not gone through part 2): Adds the appropriate required information and changes type from User to your appropriate STI type upon submit.
Other option would be to swap your first "submit" button for a button which simply reveals the relevant extra fields (and the real submit button) via JS.
Upvotes: 0