i'm working on a Ruby on Rails application and using devise to authenticate user.
First of all, i have these models:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :lockable
end
class Admin < User
end
class Partner < User
end
My devise_for on routes:
devise_for :users, :controllers => {
:registrations => "registrations",
:passwords => "passwords"
}
What i want to do? I want do a separate devise for each model, with different views and different strategy, for example: The user login with his registration number and password, the admin and the partner login with email and password (i think that admin and partner devise can be the same, because thier login stratagy is the same).
What is the best way to do this?
Configure routes:
devise_for :users
devise_for :admins
devise_for :partners
Generate devise views
rails generate devise:views user
rails generate devise:views admin
rails generate devise:views partner
Don't forget, add type field in users table for your STI
class AddTypeToUser < ActiveRecord::Migration
def up
add_column :users, :type, :string
end
def down
remove_column :users, :type
end
end
Related
I'm trying to use before_action :authenticate_admin! to determine if the current user is an admin or not. I am using devise with single table inheritance (STI).
Here is my User model;
class User < ApplicationRecord
def admin?
false
end
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :lockable
end
And my Admin model;
class Users::Admin < User
def devise_scope
:admin
end
def admin?
true
end
end
Just to test out devise, I have this in my routes file;
authenticated :user, ->(u) { u.admin? } do
resources :teams
end
And indeed, if I am signed in as a different User type (so not Admin), I can't get to that resource.
But this fails to work;
class Teams < ApplicationController
before_action :authenticate_admin!
def index
end
end
I get the error;
undefined method `authenticate_admin!'
Quite new to Rails so I don't understand how to resolve this.
UPDATE
I had to put this in the routes file;
devise_for :admins, class_name: 'Users::Admin'
Previously I only had;
devise_for :users, controllers: {sessions: 'users/sessions'}
I'm trying to make an example app to learn about namespace/scopes/modules
Normally I would user current_user helper but I have Client::Addresses nested in behind and would like to grab say the user's city and just display it on their edit page (devise registration/edit screen)
<%= current_user.?? %>
Using the line below. I also added inverse_of as my understanding it'll reverse the relationship as well but no avail.
<%= #user.addresses.cacity %>
I think this is pretty close #user.id works but adding the rest error reads. Looks like I also dealt with strong params just not sure. I'm doing this to practice namespacing:scopes/modules:
undefined method `cacity' for #<ActiveRecord::Associations::CollectionProxy []>
It would be great to do something like.
<%= current_user.addresses.cacity %>
Here's some additional information with what I got so far, let me know if additional info is needed.
routes.rb
Rails.application.routes.draw do
namespace :client do
resources :subscriptions
end
# Security Devise Setup
devise_for :admins
devise_for :users
# Main Pages
root 'website/page#index'
# Client Sections
resources :users do
scope module: "client" do
root :to => 'dashboard#index'
resources :addresses
end
end
namespace :admin do
root :to => 'panel#index'
end
end
user.rb
class User < ActiveRecord::Base
include Gravtastic
gravtastic
# Devise Settings
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
# Model Relationships
has_many :addresses, :inverse_of => :user, class_name: 'Client::Address'
end
client/address.rb
class Client::Address < ActiveRecord::Base
belongs_to :user, :inverse_of => :addresses
end
#user.addresses
is a collection, and you send a (instance) method cacity to a collection, which, as errors states, do not respond to it.
#user.addresses.first.cacity would work.
You could limit the relation to has_one:
has_one :address #...
Which will allow you to use the following:
#user.address.cacity
My rails app has a few cab operators and they have a few cabs, and they are related as follows:
class Operator < ActiveRecord::Base
has_many :cabs
end
I wish to add authentication system so as to create admins for each operator. I am using Devise. Since I need to create path as: operator/:operator_id/admins/sign_up, I generated the Admin model, as:
rails generate devise Admin
Then I modified my routes so as to obtain the above mentioned path:
scope "operators/:operator_id" do
devise_for :admins
end
After a few more modifications, I was able to add new admins and associate them to appropriate operators. However, I want to ensure that an admin only has access to the cabs of the operator to which the admin is associated. Adding the following to the cabs_controller doesn't work:
before_action :authenticate_admin!
as a signed_in admin has access to all other operator's cabs. I want to make sure that:
1. If there is no current_admin, my app asks to sign_in or sign_up
2. If there already is a current__admin signed_in, he/she has access to only the cabs associated to that operator to which the current_admin is assigned.
I am new to Devise. Please advise how I should proceed. Thanks!
Devise is for Authentication, your problem is an Authorization problem which Devise cannot help. You can look into Cancan https://github.com/ryanb/cancan :)
You don't need to scope your devise for admins. I'm not sure if it would solve any issue with access. It also makes your code harder to maintain.
I would advise you to focus on relationships instead
class Operator < ActiveRecord::Base
has_many :cabs
has_many :admins
end
class Operator < ActiveRecord::Base
has_many :cabs, through: operator
belongs_to :operator
end
Normally the way I handle such cases later is to write special in the cab model :
def is_admin?(admin)
admin.cabs.include?(#cab)
# you can put more logic here if you have different levels of access
end
and later in the controller when you want to restrict access:
if #cab.is_admin?(current_admin)
# do the stuff
else
# go away message
end
or for instance if you list cabs in index:
def index
current_admin.cabs.page(params[:page]) # this is assuming you're using pagination with something like Kaminari
end
current_admin from divise, you may want to also run :authenticate_admin! before filter to make sure you have you admin and it's not nil
Of course you can put it in price message and run it as before filter.
The most simple solution is to use STI and have Admin < User, then a couple of
devise_for :users
devise_for :admins
If you do not want to use STI, some custom overridings may do the trick, if you add a role method to your users table for exemple.
#routes
devise_for :users
namespace :admin do
devise_for :admins,
singular: :admin,
plural: :admins,
class_name: 'User',
only: [:sessions],
path: '/',
module: :devise
end
#lib/overrides/devise
require "devise/strategies/authenticatable"
require "devise/strategies/database_authenticatable"
module Devise
module Strategies
class DatabaseAuthenticatable
def authentication_hash
if mapping.name == :admin
#authentication_hash.update(role: :admin) #will add a where(role: :admin) clause
else
#authentication_hash
end
end
end
end
end
class Admin::ApplicationController < ApplicationController
skip_before_filter :authenticate_user!
before_filter :authenticate_admin!
end
You can use role base Authentication.
Generate User model
class User < ActiveRecord::Base
enum role: [:operator, :admin]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :operator
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
generate migration to add role
rails g AddRoleToUser role:integer
Please take a look ENUM
This might be a stupid question, but my Sign In view looks like total crap and isn't roping in any of the CSS styles from the other pages.
For whatever reason, I cannot find this view to even edit it. The only folders in my views are posts, pages, and layout. Does anyone know how I can go about editing what the sign in view looks like?
my route.rb:
Projectmadrone::Application.routes.draw do
mount RailsAdmin::Engine => '/admin', :as => 'rails_admin'
devise_for :users
devise_for :users do get '/users/sign_out' => 'devise/sessions#destroy' end
resources :posts
user model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :posts
end
You need to run the generator for devise views, this will copy them to your app (they live in the gem by default):
rails g devise:views
There's more info about configuring the views here: https://github.com/plataformatec/devise#configuring-views
Make sure you use and leverage and style the views provided for you for users to register and edit their accounts. See app/views/devise/registrations Don't write your own. Use these provided ones.
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.
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.
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.
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
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.