I am using Devise with multiple models (three to be exact) and each role has some different interactions. For example, after the user model signs up I override a devise method to redirect them to a specific welcome path, where with the employer model I take them to a credit card form, etc.
As a result, I need to namespace everything. Namespacing the views and controllers are not tough, however, I was wondering if there is a way to namespace the controllers without having to specify EVERY devise controller.
For example, is there a way to basically do this:
devise_for :employers, :controller => "employers"
Instead of having to do this:
devise_for :employers, :controllers => {
:registrations => "employers/registrations",
:sessions => "employers/sessions",
:confirmations => "employers/confirmations",
:passwords => "employers/passwords",
:unlocks => "employers/unlocks",
:mailer => "employers/mailer"
}
Might seem trivial but when you have three models to maintain it could get a bit much.
Take a look at the following answer from Devise within namespace. Simply namespacing in the routes.rb will not produce the desired results. You'll have to generate controllers for each action you want. For sessions for example, You will have to create a new controller called Sessions in the controller Employer namespace:
bundle exec rails g controller employer/sessions
then subclass the new session controller from devise session controller to bring in all the Devise methods required to properly handle sessions:
class Employer::SessionsController < Devise::SessionsController
end
and change your 'devise_for :employers, :controller => "employers"' line in config/routes.rb to the following:
devise_for :employers, :controllers => { :sessions => "employer/sessions" }
Finally, as an optional step, you can generate views to app/views/employer/sessions directory. You can do this my setting "config.scoped_views = true" inside config/initializers/devise.rb and running the following to generate views scoped to employers:
rails generate devise:views users
This should generate templates at app/views/employer/sessions/new. Otherwise, the new session controller will just use the default view templates.
Hope this helps!
Will it work by just saying: devise_for :employers, :path => "employers" ?
Related
i've seen a bunch of posts on how to rename already-declared routes in devise. I want to expand devise to have my own route check for and idle session. I am implementing a simple js check every 1 minute that I want to hit 'check_active' in the devise sessions controller. I tried this but no luck:
devise_scope :sessions do
get 'check_active'
end
Is there way to expand devise with a custom route (not rename an already-existing one) ?
UPDATE - almost there, i did this
# already had this in routes
devise_for :users, :controllers =>
{ registrations: 'registrations',
confirmations: 'confirmations',
sessions: 'sessions',
passwords: 'passwords',
omniauth_callbacks: "omniauth_callbacks"}
# added this
devise_scope :sessions do
get '/check_active' => 'sessions#check_active'
end
I have a js firing, i have it get '/check_active' as rake routes shows this:
check_active GET /check_active(.:format)
But when it fires, the controller 404s with
AbstractController::ActionNotFound (Could not find devise mapping for path "/check_active".
This may happen for two reasons:
1) You forgot to wrap your route inside the scope block. For example:
devise_scope :user do
get "/some/route" => "some_devise_controller"
end
2) You are testing a Devise controller bypassing the router.
If so, you can explicitly tell Devise which mapping to use:
#request.env["devise.mapping"] = Devise.mappings[:user]
):
If you are overwriting Devise's default controllers, it is not any different from any other controller to add your own route.
After you create your devise controllers to overwrite, do the following:
Under sessions_controller declare a method
# app/controllers/devise/sessions_controller.rb
def check_active
# do what you want to do
end
And in your router:
# config/routes.rb
devise_scope :sessions do
get 'check_active', to: "devise/sessions#check_active"
end
I was trying the same thing and realized that the scope should be for user and not sessions, also ensure that it has to be singular.
devise_scope :user do
get '/check_active' => 'sessions#check_active'
end
Edit: Adding link to help docs for better understanding
I have a Rails 4.1.8 app which was developed by a third party; it already uses Devise with a User model. I want to use Alchemy just to manage a few mostly-static pages like "about us" etc.
So I followed the directions for installing Alchemy CMS 3.1.0-rc1 into the existing app, and set Alchemy.user_class_name in the initializer. I mounted Alchemy under the /p/ path for now to avoid collisions with my existing paths.
Everything seems to be working fine, except when I try to view an Alchemy page while logged out, my application.html.erb throws the following error:
undefined method `new_user_session_path' for
#<#<Class:0x007fe4c6833ee0>:0x007fe4cb5b8940>
This happens because my app uses new_user_session_path in application.html.erb, to show a Login link for the guest user. In the regular app, it works fine, and also works fine when I view an Alchemy page when logged in.
I'm not familiar enough with Devise and Alchemy to figure out where the problem exists. I'm guessing it's one of two things:
when there is no logged-in User, the app is creating a "guest" User (for access to other methods on the User model), and Devise doesn't know about this user so it doesn't create the new_user_session_path helper.
I have some problem in my routing, and because Alchemy is a mountable engine, there is maybe some logic in my application controller that isn't getting called.
I'd prefer to not post my entire routes.rb or application controller, but here's the relevant devise section from the former.
devise_for :users, :path => "auth", :path_names => { :sign_in => 'login', :sign_out => 'logout',
:password => 'secret', :confirmation => 'verification', :registration => 'register' },
:controllers => {
:registrations => "authentication",
:passwords => "passwords",
:omniauth_callbacks => "omniauth_callbacks",
:sessions => "sessions"
}
devise_scope :user do
# several get/post definitions here to change various urls
end
I don't think it's #2, because even if I define a devise_scope for a custom path like:
devise_scope :user do
get 'login', to: 'devise/sessions#new'
end
I get the same problem: it works in the main app, and when users are logged in, but not on Alchemy pages with guest User.
Common rails routing proxy problem. Since the Alchemy views don't know your main apps routes, you need to use the 'main_app' routing proxy object.
So calling 'main_app.new_user_session_path' should fix your problem.
Read more about routes in engines in this Rails guide: http://guides.rubyonrails.org/engines.html#routes
So I've been chewing on this problem for a few days now and couldn't come up with an eloquent solution. There are a few resources online but they are not very comprehensive.
Here is my situation:
Specs
Rails app that requires authentication of three different user types
(Admin, Manager, Client)
They have very different attributes
When they sign in they are led to very different views
Would like to maintain a single login form
Each user type is restricted to their own controller and it's actions
Clients can sign themselves up via homepage
Admins and Managers can only be created from the Admin portal
Currently, I have Devise set up with a User class, and a polymorphic relationship between User and Admin, Manager, Client. I tried using a single table inheritance, but due to the fact that each user type has varying attributes I wanted to avoid a large single table with many null values.
Proposed solutions
Polymorphic Relationship. User model, with Admin, Manager and Client
inheriting. The question here is how can I restrict each model to
their respective controller?
User model, with a has_one relationship
with a ClientProfile and ManagerProfile to handle the extra
attributes. Then use declarative_authorization or CanCanCan to
restrict authorizations. This helps keep the app DRY by keeping only
one User model, but view logic then gets complicated.
Of the two solutions, which one seems more scalable, clean and secure? If there are any other suggestions for general app architecture that would be better? Thanks!
This is how I have my app set up form multiple user types on ROR
#config/routes.rb
AppName::Application.routes.draw do
devise_for :users, :controllers => {
registrations: 'users/registrations',
:sessions => "users/sessions",
:passwords => 'users/passwords',
:confirmations => 'users/confirmations'
}
authenticate :user do
namespace :users do
....
get '/' => 'dashboards#index'
root :to => 'dashboards#index'
end
end
devise_for :admins, :controllers => {
:sessions => "admins/sessions",
:passwords => 'admins/passwords',
:confirmations => 'admins/confirmations'
}
authenticate :admin do
namespace :admins do
....
get '/dashboard' => 'dashboards#index
root :to => 'dashboards#index'
end
end
root 'pages#index'
end
Now that you have your routes in place you can create your controllers
I have
#app/controllers/user_controller.rb
class UserController < ApplicationController
before_filter :authenticate_user!
layout 'users/default'
before_filter :check_user_active
private
def check_user_active
unless current_user.active
flash[:notice]= t(:user_not_active)
sign_out current_user
redirect_to new_user_session_path
end
end
end
# app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
layout 'users/login'
def create
end
def destroy
end
end
Last but not least is the views
just put everything in its name space like #app/views/users/sessions/new.html.haml
I am working with OmniAuth to use Facebook Connect in my Devise based rails app. One of the routes it creates is:
user_omniauth_callback /users/auth/:action/callback(.:format) {:action=>/facebook/, :controller=>"devise/omniauth_callbacks"}
I'd like to modify this route to a custom URL. Where would be the right place to do that?
the problem is by default, the route it creates is http://foo/users/auth/:action/callback.format. I want to have something more custom like http://foo/prefix_path/users/auth/:action/callback.format. I tried making my routes file look like the following:
scope "/mypath" do
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
end
but it still generates the wrong route:
user_omniauth_callback /users/auth/:action/callback(.:format) {:action=>/facebook/, :controller=>"users/omniauth_callbacks"}
I'm not exactly sure what you are asking, I assume you want to have your own custom code for the callback.
You can extend the devise controller such as:
class MyOmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#Custom Code here
end
end
Then you can register this new controller in your routes.rb
devise_for :users, :controllers => {:omniauth_callbacks => "my_omniauth_callbacks"}
EDIT:
devise can also take a 'path' option in the devise_for so changing the route:
devise_for :users, :controllers => {:omniauth_callbacks => "my_omniauth_callbacks"}, :path => "path_prefix/users"
If you are unsatisfied with omniauthable in devise itself, then you may consider implementing omniauth as separate gem and then just tie it with device.
To modify routes, you may use :match as well and map those routes to omniauth_callbacks url. Didn't get why you want to
I'd like to modify this route to a custom URL.
Decribe what you want to make different that what is available.
I've been developing the CMS backend for a website for a few weeks now. The idea is to craft everything in the backend first so that it can manage the database and information that will be displayed on the main website.
As of now, I currently have all my code setup in the normal rails MVC structure. So the users admin is /users and videos is /videos.
My plans are to take the code for this and move it to a /admin directory. So the two controllers above would need to be accessed by /admin/users and /admin/videos. I'm not sure how todo the ruote (adding the /admin as a prefix) nor am I sure about how to manage the logic. What I'm thinking of doing is setting up an additional 'middle' controller that somehow gets nested between the ApplicationControler and the targetted controller when the /admin directory is accessed. This way, any additional flags and overloaded methods can be spawned for the /admin section only (I believe I could use a filter too for this).
If that were to work, then the next issue would be separating the views logic (but that would just be renaming folders and so on).
Either I do it that way or I have two rails instances that share the MVC code between them (and I guess the database too), but I fear that would cause lots of duplication errors.
Any ideas as to how I should go about doing this?
Many thanks!
If you don't mind having two controllers for each resource, you could have a separate "admin" namespace. I like it this way, since the admin section is completely different from the public one. Admin controllers implement all CRUD actions, whereas the public ones implement only show and index actions.
routes.rb:
map.namespace :admin do |admin|
admin.resources :users
admin.resources :videos
end
map.resources :videos, :only => [:index, :show]
Your controllers could be something like:
class VideosController < PublicController; end
class Admin::VideosController < Admin::AdminController; end
class PublicController < ApplicationController
layout 'public'
before_filter :load_public_menu
end
class Admin::AdminController < ApplicationController
layout 'admin'
before_filter :login_required, :load_admin_menu
end
Namespaced controllers and views have their own subdirectory inside the app/controllers and app/views directories. If you use the form_for helper, you need to modify its parameters:
form_for [:admin, #video] do |f|
You can do this without an extra controller, relatively easily in config/routes.rb:
# non-admin routes
# your args could include :only => [:index,:show] for the non-admin routes
# if you wanted these to be read-only
map.resources :users, ...your args..., :requirements => { :is_admin => false }
map.resources :videos, ...your args..., :requirements => { :is_admin => false }
# admin routes
map.resources :users, ...your args..., :path_prefix => '/admin', \
:name_prefix => 'admin_', :requirements => { :is_admin => true }
map.resources :videos, ...your args..., :path_prefix => '/admin', \
:name_prefix => 'admin_', :requirements => { :is_admin => true }
What :requirements actually does here, because I gave it a constant and not a regex, is just to add params[:is_admin] when accessed via this route. So you can check this value in your controller, and render different views, or you can just check it in the view if the two views are similar. It's important to include the requirement with false on the non-admin versions otherwise people can just use /users/?is_admin=true.
The :name_prefix edits the route names, so you have e.g. admin_video_path(123) as well as video_path(123).
Tested on Rails 2.3.5, other versions may differ. For more about the options available on RESTful routes, see the ActionController::Resources docs.