Rails: Using Devise with single table inheritance

后端 未结 4 1772
春和景丽
春和景丽 2020-12-07 14:51

I am having a problem getting Devise to work the way I\'d like with single table inheritance.

I have two different types of account organised as follows:

<         


        
相关标签:
4条回答
  • 2020-12-07 15:17

    I just ran into the exact scenario (with class names changed) as outlined in the question. Here's my solution (Devise 2.2.3, Rails 3.2.13):

    in config/routes.rb:

    devise_for :accounts, :controllers => { :sessions => 'sessions' }, :skip => :registrations
    devise_for :users, :companies, :skip => :sessions
    

    in app/controllers/sessions_controller.rb:

    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
    

    Note: since your Accounts class will still be :registerable the default links in views/devise/shared/_links.erb will try to be emitted, but new_registration_path(Accounts) won't work (we :skip it in the route drawing) and cause an error. You'll have to generate the devise views and manually remove it.

    Hat-tip to https://groups.google.com/forum/?fromgroups=#!topic/plataformatec-devise/s4Gg3BjhG0E for pointing me in the right direction.

    0 讨论(0)
  • 2020-12-07 15:21

    There is an easy way to handle STI in the routes.

    Let's say you have the following STI models:

    def Account < ActiveRecord::Base
    # put the devise stuff here
    devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :trackable, :validatable
    end
    
    def User < Account
    end
    
    def Company < Account
    

    A method that is often overlooked is that you can specify a block in the authenticated method in your routes.rb file:

    ## config/routes.rb
    
    devise_for :accounts, :skip => :registrations
    devise_for :users, :companies, :skip => :sessions
    
    # routes for all users
    authenticated :account do
    end
    
    # routes only for users
    authenticated :user, lambda {|u| u.type == "User"} do
    end
    
    # routes only for companies
    authenticated :user, lambda {|u| u.type == "Company"} do
    end
    

    To get the various helper methods like "current_user" and "authenticate_user!" ("current_account" and "authenticate_account!" are already defined) without having to define a separate method for each (which quickly becomes unmaintainable as more user types are added), you can define dynamic helper methods in your ApplicationController:

    ## controllers/application_controller.rb
    def ApplicationController < ActionController::Base
      %w(User Company).each do |k| 
        define_method "current_#{k.underscore}" do 
            current_account if current_account.is_a?(k.constantize)
        end 
    
        define_method "authenticate_#{k.underscore}!" do 
        |opts={}| send("current_#{k.underscore}") || not_authorized 
        end 
      end
    end
    

    This is how I solved the rails devise STI problem.

    0 讨论(0)
  • 2020-12-07 15:37

    try to change routes like so:
    devise_for :accounts, :users, :companies
    because Devise uses plural names for it's resources

    Please let me know if it help you

    0 讨论(0)
  • 2020-12-07 15:42

    I don't think this is possible without overriding the sessions controller. Each sign_in page has a specific scope that devise will authenticate against as defined by your routes.

    It may be possible to use the same sign_in page for multiple user scopes by using the devise_scope function in your routes file to force both :users and :companies to use the same sign in page (a how-to can be found here), but I'm pretty certain that you would have to modify your sessions controller to do some custom logic in order to determine which type of user is signing in.

    0 讨论(0)
提交回复
热议问题