How to update only email using devise? - ruby-on-rails

I am trying to update only email using this form, but validation is failing Current password can't be blank, requires password_confirmation, I don't have a clue how to void this requirement
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => {:method => :put, :id=>"email_form"}) do |f| %>
<%= devise_error_messages! %>
<div><%= f.label :email %>
<br/>
<%= f.email_field :email, :autofocus => true %></div>
<div><%= f.submit "Update" %></div>
<% end %>
UPDATE
This is my update action in the overrided controller RegistrationsController < Devise::RegistrationsController
class RegistrationsController < Devise::RegistrationsController
def update
#user = User.find(current_user.id)
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(params[:user])
else
# remove the virtual current_password attribute update_without_password
# doesn't know how to ignore it
params[:user].delete(:current_password)
#user.update_without_password(params[:user])
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
render "edit"
end
end
private
def needs_password?(user, params)
user.email != params[:user][:email] ||
params[:user][:password].present?
end
end

Just point to other controller than devise's own registration controntroller, with your own logic for this update action.
You can also override devise registration's controller (read devise docs).
Example:
# config/routes.rb
resource :profile # Singular resource and profile
# profiles_controller
def show
# render your view with the for
end
def update
current_user.update(params[:user]) # if rails < 4 use update_attributes instead
end
# _form.html.erb
<%= form_for(current_user, url: profile_path, html: { method: 'PUT' }) do |f| %>
...
For the second option, overriding devise's own registration controller, I don't really like this approach because in this case you are not really dealing with registrations but an already registered user account:
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
After your edit:
I see you are using the second option. Take a look a the needs_password? method in the controller

Related

Can you make a form object work for new and edit actions if the form itself is never persisted?

I'm trying to make a form object work for new User and edit User actions. The form object creates or updates a User through it's save method, but the form object itself is never persisted so Rails always tries to make a POST even though I'm specifying different routes in the simple_form_for url.
Is there any way to make it work for both actions?
UsersController.rb:
class Admin::UsersController < AdminController
def new
#user_form = UserForm.new(account_id: current_account.id)
end
def create
#user_form = UserForm.new(user_form_params)
if #user = #user_form.save
flash[:success] = "User created"
redirect_to admin_user_path(#user)
else
render "new"
end
end
def edit
#user_form = UserForm.new(existing_user: #user, account_id: current_account.id)
end
def update
if #user.update(user_form_params)
flash[:success] = "User saved"
redirect_to admin_user_path(#user)
else
render "edit"
end
end
end
UserForm.rb
class UserForm
include ActiveModel::Model
include ActiveModel::Validations::Callbacks
attr_accessor :fname, :lname, :email
def initialize(params = {})
super(params)
#account = Account.find(account_id)
#user = existing_user || user
end
def user
#user ||= User.new do |user|
user.fname = fname
user.lname = lname
user.email = email
end
end
def save
#user.save
#user
end
end
_form.html.erb
<%= simple_form_for #user_form, url: (#user.present? ? admin_user_path(#user) : admin_users_path) do |f| %>
<%= f.input :fname %>
<%= f.input :lname %>
<%= f.input :email %>
<%= f.submit %>
end
The new/create flow works fine, but editing an existing User returns
No route matches [POST] "/admin/users/69"
class UserForm
# ...
def to_model
#user
end
end
<%= simple_form_for #user_form, url: [:admin, #user_form] do |f| %>
<%= f.input :fname %>
<%= f.input :lname %>
<%= f.input :email %>
<%= f.submit %>
end
When you pass a record to form_for (which SimpleForm wraps), form_with or link_to the polymorphic routing helpers call to_model.model_name.route_key or singular_route_key depending on if the model is persisted?. Passing [:admin, #user_form] will cause the polymorphic route helpers to use admin_users_path instead of just users_path.
On normal models to_model just returns self.
https://api.rubyonrails.org/v6.1.4/classes/ActionDispatch/Routing/PolymorphicRoutes.html

Create Devise User from Admin Namespace in Rails 5.1

I have an admin namespaced route for a custom dashboard in my app. I have at least 8 models working, with error free crud operations--all except one. I am using the Devise Gem for user management and a User model. I have the User model in the admin namespace and the only operation I can get to work is changing role and destroy, but I can't create a new user from the dashboard. When I try to create a new user; I get the error "You are already signed in.".
controllers/admin/users_controller.rb
class Admin::UsersController < ApplicationController
before_action :authenticate_user!
before_action :admin_only, :except => :show
def index
#users = User.all
end
def show
#user = User.find(params[:id])
unless current_user.admin?
unless #user == current_user
redirect_to root_path, :alert => 'Access denied.'
end
end
end
def create
#user = User.new
end
def update
#user = User.find(params[:id])
if #user.update_attributes(secure_params)
redirect_to users_path, :notice => 'User updated.'
else
redirect_to admin_users_path, :alert => 'Unable to update user.'
end
end
def destroy
#user = User.find(params[:id])
user.destroy
redirect_to admin_users_path, :notice => 'User deleted.'
end
private
def admin_only
unless current_user.admin?
redirect_to root_path, :alert => 'Access denied.'
end
end
def secure_params
params.require(:user).permit!
end
end
models/user.rb
class User < ApplicationRecord
enum role: [:user, :admin]
def set_default_role
self.role ||= :user
end
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
views/admin/users/new.html.erb
<%= form_for User.new do |f| %>
<div class="form-group">
<%= f.label :first_name %><br/>
<%= f.text_field :first_name, autofocus: true %>
</div>
<div class="form-group">
<%= f.label :last_name %><br/>
<%= f.text_field :last_name %>
</div>
<div class="form-group">
<%= f.label :email %><br/>
<%= f.email_field :email %>
</div>
<div class="form-group">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %><br/>
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="from-group">
<%= f.label :password_confirmation %><br/>
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<div class="form-group" style="margin-top: 1em">
<%= f.submit 'Sign up', class: 'btn btn--primary type--uppercase inner-link' %>
</div>
<% end %>
views/admin/users/new.html.erb (other method)
<%= form_for([:admin, #user]) do |f| %>
This is how all my name spaced forms are setup but this gives me the error First argument in form cannot contain nil or be empty
routes.rb
...
namespace :admin do
get '', to: 'dashboard#index', as: '/'
resources :pages
resources :articles
resources :categories
resources :tags
devise_for :users
resources :users
resources :albums
resources :banners
resources :products do
resources :variations
end
end
...
using the registration#create action is the appropriate architecture for this?
Because that action is built on a different concept, that the user is not logged in and there are a series of checks for that.
views/admin/users/new.html.erb
you are using registration#new controller action, which does not have the #admin valorized.
def new
#admin = Admin.find(params[:admin_id])
....
end
That is why you get the error, also this approach is not correct.
User will need to register with the traditional devise router, then you will also have to create a nested router for admins to create new users.
this is your link to the admin user registration#new
Your current view/controller#action has the #admin = Admin.find(params[:id]) valorized and the view has the following link
link_to new_admin_user_path(:admin, :user)
the new_admin_user_path is for url /admin/:admin_id/users/sign_up(.:format) that you define in your nested routes.
Step 2) decide which controller#action to use
Do you want to use the standard users/registrations#create or use a new action for this?
I believe you should enhance users/registrations#create by generating the controllers actions in your app as described in devise guide
then in devise controller registrations
def new
#admin = Admin.new(:params[:admin_id]) if params[:admin_id].present?
end
your registrations#new and #create will still trigger errors, you will have to read how devise creates this users by reading their amazing sourcecode and rdoc documentation, modify the process accordingly so that admins can create users by using that action otherwise the less DRY alternative is creating a new controller#action and using it to call an existing User method or a method you will create in the User model to create users. In the end it a User is just an entry in your users database table. Just creating in a similar fashion to other users. As the admin is creating the temporary password, encryption/security issues are not anymore that important. The User will have to change the password anyway.

How do I allow logging in from two different locations in my app?

My app allows a user to log in from two different places, the header and the new session page. The new session page logs a user in and redirects them to the correct page, but the home pages just reloads the home page without redirecting the user or logging them in.
This is my sessionscontroller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
session[:user_id] = user.id
redirect_back_or feed_user_path(user)
else
session[:user_id] = nil
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
new.html.erb
<%= form_for(:session, url: sessions_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit "Log in", class: "btn btn-large btn-info" %>
<% end %>
Code in my _header.html.erb
<%= form_for :session, :url => {:controller => "sessions", :action => "new"} do |f| %>
<div class="home-login form-group">
<%= f.label :email %>
<%= f.text_field :email, :placeholder => "Email" %>
</div>
<div class="home-login form-group">
<%= f.label :password %>
<%= f.password_field :password, :placeholder => "Password" %>
</div>
<div class="form-group home-login">
<%= f.submit "Log in", class: "btn-info" %>
</div>
<% end %>
This is what shows up in the terminal
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: AegIbI8c1TIddIBPVWTt/B2CBoCAgbJxL+NWDe782Cc=
session: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
email: ** # **
password: ****
commit: Log in
controller: pages
action: home
EDIT****
This is my application controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
end
Home is a static page in my pages controller
class PagesController < ApplicationController
def home
end
end
EDIT****
This is my SessionsHelper
module SessionsHelper
def signed_in?
!!session[:user_id]
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def current_user=(user) # set current user
#current_user = user # session[:user_id] = user.id
end
def current_user?(user) # get current user
user == current_user
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
end
EDIT****
Routes
sessions POST /sessions(.:format) sessions#create
new_session GET /sessions/new(.:format) sessions#new
session DELETE /sessions/:id(.:format) sessions#destroy
try changing your form_for tag to direct it to the create action with a method of post. something like this:
<%= form_for :session, url: session_path, method: 'post', action: 'create' do |f| %>
There's no difference with where do you put your login form unless you refer to any controller-specific data there. I don't see that, so you should be good to go with just copying the exact same form_for call that actually produces working results. At least I don't see anything that prevents doing this.
And the problem is the action. new. Wrong. It's create. "New" is a display page for the form (and uses GET because of that), the actual form's action (data send path) is "create" (that uses POST). HTTP method is picked from the routes, execute rake routes to verify that they are correct.
A form is not bound to a page it's on. Instead, it has its own path, that should accept input data from the form.
I figured it out. The ERB form was wrapped in an HTML form. That was causing the problem.

Creating form_for for atributes User image and description

I have a rails application with devise, and I added to users a profile image, and a description. What I want to do is to create a page (DIFFERENT of the default registration/edit) where the users, after logged in, can set only this two atributes(image and description).
<%= form_for(:user, html: { method: :put, :multipart => true })) do |f| %>
<div class="form-group">
<%= f.label :Profile_Image %>
<%= f.file_field :image, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :Descrição %>
<%= f.text_area :description, class: "form-control", rows: "10" %>
</div>
<% end %>
I have already tried two different controllers and none of them worked:
def edit
end
def edit
#user = User.find(params[:id])
end
My config -> routes are:
get "edit" => "pages#edit"
post "edit" => "pages#edit"
But when i click submit it does nothing! I am new at rails and I am trying to figure this out for hours... How can I create a page to update only the image and the description? Thanks
You need an update method in your controller. Your edit method allows the form to render, but you need something like this:
def update
current_user.update(user_params)
end
Then you would have another method in your controller called user_params, which would look something like this. I was taught to put it under a private heading.
private
def user_params
params.require(:user).permit(:profile_image, :description)
end
I believe there is a shortcut way of including your params with your update method, but this will do.
Use registration controller devise and you should customize it.
You should have one method with the same name in one controller, you have two edit method. Change one edit method to update method ( reference : Allow users to edit their account )
pages_controller.rb
class PagesController < Devise::RegistrationsController
def edit
#user = current_user
end
def update
#user = current_user
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(devise_parameter_sanitizer.for(:account_update))
else
params[:user].delete(:current_password)
#user.update_with_password(devise_parameter_sanitizer.for(:account_update))
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to after_update_path_for(#user)
else
render "edit"
end
end
private
def needs_password?(user, params)
user.email != params[:user][:email] || params[:user][:password].present?
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_filter :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.for(:account_update) do |u|
u.permit(:description, :image, :email, :password, :password_confirmation)
end
end
end
You have wrong http verb (post), you need PUT/PATCH not POST
devise_scope :user do
get "edit" => "pages#edit"
put "update" => "pages#update"
end
On your view looks like (example and not tested)
<%= form_for(#user, :url => update_pages_path, :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :image, "Profile Image" %>
<%= f.file_field :image, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label description, "Descrição" %>
<%= f.text_area :description, class: "form-control", rows: "10" %>
</div>
<% end %>
<%= f.submit "Save Image" %>

validate presence of not working in form_for tag

This is my first time doing validation on a rails application. I saw many tutorials which made it seem easy. I don't know why I cant get it to work.
Below is my setup.
Controller Admin (action = login)
def login
session[:user_id] = nil
if request.post?
#user = User.authenticate(params[:userId], params[:password])
if true
session[:user_id] = #user.user_id
flash.now[:notice] = "Login Successful"
redirect_to(:controller => "pages", :action => "mainpage")
else
flash.now[:notice] = "Invalid user/password combination"
end
end
end
So first time user comes to admin/login they are just presented with a form below
login.erb.html
<% form_for :user do |f| %>
<p><label for="name">User ID:</label>
<%= f.text_field :userid %>
</p>
<p><label for="password">Password:</label>
<%= f.password_field :password%>
</p>
<p style="padding-left:100px">
<%= submit_tag 'Login' %>
</p>
<% end %>
My User model:
class User < ActiveRecord::Base
validates_presence_of :userid, :password
def self.authenticate(userid, password)
user = self.find_by_userid_and_password(userid, password)
user
end
end
Actual field names for userId and password in my DB: userid password
I am expecting behavior that when user does not enter anything in the fields and just clicks submit. it will tell them that userid and password are required fields. However, this is not happening
From the console I can see the messages:
>> #user = User.new(:userid => "", :password => "dsf")
=> #<User id: nil, userid: "", password: "dsf", created_at: nil, updated_at: nil>
>> #user.save
=> false
>> #user.errors.full_messages
=> ["Userid can't be blank"]
So error is somewhere in my form submit...
UPDATE: validations only happen when u SAVE the object....here I am not saving anything. So in this case I have to do javascript validations?
It's the if true line. Change it to
if #user = User.authenticate(params[:userId], params[:password])
or
#user = User.authenticate(params[:userId], params[:password])
if #user
...
end
I'd also add redirect_to login_path to the failure case.
You can also slim down your auth method:
def self.authenticate(userid, password)
find_by_userid_and_password(userid, password)
end
It turns out, there are several issues here, and I'll try to cover them all. Let's start with your model:
class User < ActiveRecord::Base
validates_presence_of :userid, :password
def self.authenticate(userid, password)
self.find_by_userid_and_password(userid, password)
end
end
The validation doesn't come into play for logging in, only for creating and updating user records. The authentication has been trimmed, because ruby automatically returns the last calculated value in a method.
Next, the login action of your controller:
def login
session[:user_id] = nil
if request.post?
if #user = User.authenticate(params[:userId], params[:password])
session[:user_id] = #user.user_id
flash[:notice] = "Login Successful"
redirect_to(:controller => "pages", :action => "mainpage")
else
flash.now[:error] = "Invalid user/password combination"
end
end
end
Notice we don't use flash.now on a redirect - flash.now is only if you're NOT redirecting, to keep rails from showing the message twice.
Finally, you shouldn't be using form_for, because this is not a restful resource form. You're not creating or editing a user, so use form_tag instead:
<% form_tag url_for(:controller => :users, :action => :login), :method => :post do %>
<%= content_tag(:p, flash[:error]) if flash[:error] %>
<p><label for="name">User ID:</label>
<%= text_field_tag :userid %>
</p>
<p><label for="password">Password:</label>
<%= password_field_tag :password%>
</p>
<p style="padding-left:100px">
<%= submit_tag 'Login' %>
</p>
<% end %>
This will do what you want. This is a great learning exercise, but if you're serious about user authentication in a production application, checkout rails plugins like restful_authentication or clearance that do this for you in a much more sophisticated (and RESTful) way.

Resources