Devise forgot password renders wrong template - ruby-on-rails

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

Related

Devise generated mailer URLs with devise_for in Rails scopes

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.

Devise redirect back on sign up failure with validations

I have a simple Devise registration form with the validatable plugin. It mostly works as intended, if the user forgets to enter a first name it redirects them back with a red validation message.
The problem is it redirects to the same path a successful login would have the user go to (i.e. it redirects them to /user and not back to /user/sign_up). If the user then refreshes the page for whatever reason they get a No route matches [GET] "/user" error.
How can I force a redirect to go back to the original /user/sign_up route on a sign up failure? I know I can hack the create action in the registration controller but when I redirect to the proper route I lose the Devise validation messages.
Update
It appears the problem is the way Devise handles respond_with Rails 4 How Overwrite Devise Respond Path Upon Error. It looks like Devise tries to render a create template at /user but I still can't override it.
First you can create devise controllers using following command -
rails generate devise:controllers users
Then you have to modify your routes for devise
# config/routes.rb
Rails.application.routes.draw do
devise_for :users,
:skip => [:registrations]
devise_scope :user do
get "user/sign_up", to: "users/registrations#new", as: :new_user_registration
post "user/sign_up", to: "users/registrations#create", as: :user_registration
end
end
Hope it's work.

Why does Devise redirect sign_up error to a different page?

I created new rails project with just a generated controller for a home page to test this.
Me devise model is User, so the signup page is http://localhost:3000/users/sign_up.
If I trigger an error, for example, submitting the form without giving password, I get redirected to http://localhost:3000/users.
How can I stay on http://localhost:3000/users/sign_up after making errors?
I read this question and the only answer suggests using redirect_to :back in the controller.
The problem is I am not overriding the controller. If overriding is the only way to solve, please explain how it should be.
This is my routes.rb
Rails.application.routes.draw do
root to: "home#index"
get 'home/index'
devise_for :users
end
Here is the source code of Devise::RegistrationsController. How should I override new or create to achieve what I need?
I strongly recommend that you don't do this. The default behavior of Rails applications (following the route naming conventions) is that way you described. But, if you really need to to this, here's how.
First, go to your routes.rband:
devise_scope :user do
post 'users/sign_up', to: 'devise/registrations#create'
end
EDIT
Then, generate the devise views so you can override the default behavior of the forms.
rails g devise:views
Then, open your app/views/devise/registrations/new.html file and edit the form_for line so it looks like this:
<%= form_for(resource, as: resource_name, url: users_sign_up_path) do |f| %>

Signout from a controller in rails 4

I want to signout from a controller. My controller looks like
def update
if #attendance.update_attribute(:logout_at, Time.now.localtime)
redirect_to signout_path and return
end
end
And my routes looks like
devise_scope :employees do
get "signout" => "devise/sessions#destroy"
end
devise_for :employees, :controllers => { registrations: 'registrations' }
But It gives error
Unknown action
Could not find devise mapping for path "/signout". 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]
How can I do that? Please Help me out.
Thanks in advance.
You are redirecting, which makes a GET request to devise#sessions#destroy, a route that doesn't exist. The signout route in Devise is a mapped to a DELETE request. Instead of redirecting you should directly call the sign_out method that Devise makes available to you. After that be sure to redirect the user somewhere, maybe the login page.
A side note, in Rails 4 you can call update(attribute: value) directly. You don't need to call return either.
def update
#attendance.update(logout_at: Time.now.localtime)
sign_out
redirect_to login_path
end
I removed the if statement that wrapped the update call. By using one you are implying that there maybe a reason the save will not happen because of validation error, for example, and you need to provide feedback to the user. But in this case it's more likely to be an exception since there is no data input by the user. You can handle that at the application level.

Devise users/sign_in and users/show action trouble

I user devise 3.4.0 under rails 4.1.0.
I want to add user detail page, so I made this route
get 'users/:id' => 'users#show', as: 'user'
But after this, when I access /users/sign_in path, it try to find the user show page.
How to write the right route?
What you did will actually "override" the devise routes (and i think this is the problem you are facing)
If you want to add another route in the scope of devise routes, you have to do something like :
devise_scope :user do
get '/users/:id' => 'users#show'
end
after
devise_for :users
Let me know if it solves the problem !

Resources