Single Table Inheritance with Devise in Rails 4
Asked Answered
O

2

9

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
Outpoint answered 30/6, 2014 at 3:42 Comment(3)
Did you ever get STI w/ Devise to work?Polypropylene
It seems, that you should specify permitted params. Their names might have not been derived properly.Pulmonate
Nick ended up being exactly correct about specifying permitted params, although I had been finished with the project for nearly a year :). Still, if you add that as an answer I will accept it!Outpoint
E
2

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.

Endocrinology answered 31/8, 2016 at 18:29 Comment(0)
B
0

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.

Betake answered 18/11, 2015 at 23:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.