I'm building a Rails application with a Javascript framework, so Rails is serving the backend API.
For now, the app is simply implementing all the devise views and actions.
In order to do so, the Rails app accepts only JSON calls to its /api/ URLs, and requires that Devise is working with JSON calls only, so that I defined them like the following:
Rails.application.routes.draw do
scope :api, module: :api, constraints: { format: 'json' } do
devise_for :users, controllers: {
confirmations: 'devise/confirmations',
registrations: 'devise/registrations',
sessions: 'sessions'
}
resources :users
end
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root to: 'home#index'
get '*path', to: 'home#index'
end
To explain what is going on here:
First part is defining the API URLs including the devise ones and defines the API resources (for now only :users)
Then it defines the root route
Finally forwards any calls to the Javascript router (allowing to manage page reload or external links like the email links).
All is working fine with this, excepted the account confirmation email link.
The confirmation email sent has a link including /api/ (http://localhost:3001/api/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc) while it should be without (Expected URL: http://localhost:3001/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc).
How can I make devise sending the confirmation email (and all the other emails) without the /api/ part?
Update
Looking deeper the devise source code I found that the confirmation mailer view template is using the confirmation_url Rails named route, which is correct.
In my case, I need ALL the devise routes to be limited to the /api/ route, and to the JSON format, excepted few routes to be 2 times defined: a first time out of the /api/ scope in HTML format (which will be forwarded to my JavaScript app), and a second route within the /api/ scope which will be called by the JavaScript app.
Example: Expected account creation confirmation execution stack
Rails receives the request to /users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc
Rails forward the request to the Javascript router
Javascript framework loads the corresponding page
Javascript page loads a service which will call the /api/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc Devise route to confirm the account
You should be able to simply add the two additional routes after the initial API routes. Like this:
Rails.application.routes.draw do
scope :api, module: :api, constraints: { format: 'json' } do
devise_for :users, controllers: {
confirmations: 'devise/confirmations',
registrations: 'devise/registrations',
sessions: 'sessions'
}
resources :users
end
devise_for :users, controllers: {
confirmations: 'devise/confirmations',
registrations: 'devise/registrations'
}
root to: 'home#index'
get '*path', to: 'home#index'
end
It is hard to say for sure without also seeing the front end code, but it is fine to have both routes.
There are two ways you can solve your problem.
Make the links in the emails be of json format (have '.json' at the end)
or
Allow actions for links from emails to be available through html (without format: 'json' constraint).
This example shows how to do it for email confirmation action.
scope :api, module: :api, constraints: { format: 'json' } do
# skip routes generation for `confirmations` controller
devise_for :users, skip: [:confirmations]
# add allowed `confirmations` actions manually (all except for `show`)
as :user do
get 'confirmation/new', to: 'devise/confirmations#new', as: :new_user_confirmation
post 'confirmation', to: 'devise/confirmations#create'
end
end
# add `confirmations#show` action outside of `/api` scope to be available from email link by `html`
as :user do
get 'confirmation', to: 'devise/confirmations#show', as: :user_confirmation
end
If you don't want to use Devise's HTML views and actions, you can customize Devise controllers or write your own. This example shows how to customize Devise confirmations controller:
# app/controllers/confirmations_controller.rb
class ConfirmationsController < Devise::ConfirmationsController
def show
# do whatever you want
end
end
# config/routes.rb
scope :api, module: :api, constraints: { format: 'json' } do
# tell Devise to use your custom confirmations controller
devise_for :users, controllers: {confirmations: "confirmations"}
end
Updated
Also, if you want your email links to have nothing to do with Devise API, you can customize the Devise mailer views to change the email texts and put there the links you need.
The rails generate devise:views command will generate standard Devise views including mailer templates that you can customize.
Related
Rails 6, Devise 4.7.3 I have a forgotten password template in my views as users/passwords/new.html.erb that renders fine, collects an email, and sends that back to the Application. I also have a nice view template at users/passwords/edit that I am expecting to render when the email is sent in. In my case however, the template comes from devise/passwords/edit.html.erb which is not the one that should render.
Why is the devise template rendering and not the one I am expecting to run?
My routes:
devise_scope :user do
get 'sign_in', to: 'users/sessions#new'
get 'sign_up', to: 'users/registrations#new'
get 'forgot_password', to: 'users/passwords#new'
get 'reset_password', to: 'users/passwords#edit'
end
In the end, all I had to do to get MY controllers working was to add the devise_for :users route to point to { passwords: 'users/passwords' }
Since the devise controller is handling the password change it renders the default devise views.
You can either update the devise views with your own design or create your own update_password method to overwrite the default one from devise.
You can read more on how to implement your own update password method here: https://github.com/heartcombo/devise/wiki/How-To:-Allow-users-to-edit-their-password
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
How to hide header and sidebar AdminLTE, just for signup page? For login, it generates automatically when I run this command rails g devise:views users. How about signup page?
Here is my routes.rb :
Rails.application.routes.draw do
devise_for :users, controller: {
sessions: "sessions/registrations"
}
get 'home/index'
root :to => 'home#index'
end
and my application_controller.rb :
class ApplicationController < ActionController::Base
before_action :authenticate_user!
layout 'admin_lte_2'
end
I don't know how you exactly use devise with admin lte but I can see 2 possible solutions
Option 1
You could define a different route for logged in users:
devise_for :users
devise_scope :user do
authenticated :user do
root 'home#index', as: :authenticated_root
end
get 'user', to: 'devise/sessions#new'
end
Unauthenticated users will be redirected to devise/session#new view (or any other of your choice).
If you would like to have another content shown for those users other than registration page, add unauthenticated root:
unauthenticated do
root 'foo#bar', as: :unauthenticated_root
end
Option 2
Create your custom registration page without header and sidebar. I think it's easier to edit view generated by devise (I don't know if it's good practice tho) than create a custom layout.
You will have probably a view:
app/views/devise/registrations/new.html.erb
just edit it so it will suit your needs.
For creating a custom layout for devise look here:
Wiki: Create custom layouts
Wiki: Customize routes
I hope it helps
I am new to Ruby on Rails. I want to have following structure for admin section.
app/controller/admin/admin_controller.rb and all other admin section controller under app/controller/admin/ folder
app/views/layout/admin/admin.html.erb to keep separate html layout for admin section
At the same time i want to use Devise Gem for admin and front end user authentication.
I executed rails g devise:views admin, rails generate devise Admin and rails g controller admin/home index command that created views, model and controller for admin user. Now what routes and other setting i need to add so that ruby could understand that if i type http://localhost:3000/admin/ then i should be redirected to http://localhost:3000/admins/sign_in/ page and after entering correct admin credentials i should redirected to index method of controllers/admin/home_controller.rb
is it also possible to keep singular convention of Devise admin views like admin/sign_in instead of admins/sign_in ?
I have searched a lot but could not get relevant help. Please provide steps to achieve above.
Thanks in advance.
This is how route file looks like
Rails.application.routes.draw do
namespace :admin do
get 'home/index'
end
devise_for :admins
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root to: "home#index"
end
When i type http://localhost:3000/admin/ then i get below error
Your problem is that you do not have root route defined for /admin.
I have the same URL convention routes in one of the apps and routes.rb looks like this:
Rails.application.routes.draw do
# Admin part
devise_for :admins, path: '/admin'
scope module: :admin, path: '/admin', as: 'admin' do
root to: 'home#index'
end
# Redirect app root to client part
root to: redirect(path: '/panel', status: 301)
# Client part
devise_for :clients, path: '/panel'
scope module: :panel, path: '/panel', as: 'panel' do
...
end
end
I feel like my brain left the building when I was learning Rails routing... I can't figure this out.
So I have customized some devise controller, and therefore I have updated the route file like so:
devise_for :users, controllers: {
registrations: "users/registrations",
sessions: "users/sessions",
passwords: "users/passwords"
}
That works GREAT. It gives me paths like this:
new_user_registration GET /users/sign_up(.:format) users/registrations#new
The challenge now is I want to run an A/B test using Google Analytics where I need 2 more pages for the sign up.
So in my controller this is how I would modify:
class Users::RegistrationsController < Devise::RegistrationsController
def new
end
# ADD BELOW
def new_control
end
def new_test
end
end
But I can't figure out how to modify my routing so that I have these 2 new routes in additional to the old new_user_registration_path (note the named path helper for these new ones don't matter so much to me because I never actually use it)
GET /users/sign_up/control(.:format) users/registrations#new_control
GET /users/sign_up/test(.:format) users/registrations#new_test
Note that I want to keep all the other lovely routes the devise_for code has created, e.g., the create and edit actions
You can access specify the route normally as you would do in a rails app. Only thing you need to do is wrap the route inside a device_scope. This also shows up as warning when you try to access the route without adding the device_scope.
So in your case routes should be like:
devise_scope :user do
get 'users/sign_up/control' => 'users/registrations#new_control'
get 'users/sign_up/test' => 'users/registrations#new_test'
end