Rails: Using Devise with single table inheritance

gjb picture gjb · Apr 17, 2011 · Viewed 8.4k times · Source

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:

class Account < ActiveRecord::Base
  devise :database_authenticatable, :registerable
end

class User < Account
end

class Company < Account
end

I have the following routes:

devise_for :account, :user, :company

Users register at /user/sign_up and companies register at /company/sign_up. All users log in using a single form at /account/sign_in (Account is the parent class).

However, logging in via this form only seems to authenticate them for the Account scope. Subsequent requests to actions such as /user/edit or /company/edit direct the user to the login screen for the corresponding scope.

How can I get Devise to recognise the account 'type' and authenticate them for the relevant scope?

Any suggestions much appreciated.

Answer

user2205763 picture user2205763 · Sep 25, 2013

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.