how can I update my user without the required password, password confirmation and current password?
I'm trying to update the user outside devise controller, my form is working with this helper:
module ContentHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
and my form for editing:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, multipart: true }) do |f| %>
<%= devise_error_messages! %>
<%= f.file_field :personal_file, placeholder: "Upload file" %>
<%= f.submit %>
<% end %>
With this its appearing that I can't have my password blank and redirecting to user edit page:
Password is too short (minimum is 8 characters)
Password can't be blank
Current password can't be blank
You can just write up your own controller that updates User as you wish. Something like
class UserController < ApplicationController
before_filter :authenticate_user!
def update
current_user.update(user_params)
end
private
def user_params
params.require(:user).permit(:personal_file)
end
end
would do the trick. You don't need to think of the current_user as some magic devise thing but instead use it as any other model you have.
If you anyway wish to use Devise controller to do this, you should see this answer.
Related
I have Devise 4.2.1 and Rails 5.0.1. I'm trying to use Devise sign_in in a controller other than registrations. There were a bunch of methods that weren't defined, so I had to add them as helpers:
module ApplicationHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
# ^the source of the error^
end
def resource_class
devise_mapping.to
end
end
But now I'm getting the following error:
NoMethodError in Static#show
undefined method 'resource=' for (class)
I don't have "resource=" written anywhere in my code, certainly not on the line the error points to (marked above). Where is my server getting that error from?
Also, I think I'm just using the standard sign_in code:
<%
build_resource({})
set_minimum_password_length
yield resource if block_given?
respond_with self.resource
%>
<% form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
...
I believe that your question has 2 answers: Why this error is happening, and how to have a sign_in partial to use on all your controllers:
Creating the sign_in partial
First of all, the sign_in method belongs to sessions, not registrations be careful to not replace the wrong controller :)
And you just added a few more code than needed for what you want. To create a partial sign_in to use anywhere, just use the default Devise form in it:
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "off" %>
<% if devise_mapping.rememberable? -%>
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
<% end -%>
<%= f.submit "Log in" %>
<% end %>
That part with build_resource({}) isn't needed, as it'll be handled by Devise itself when you submit the form. And since build_resource isn't needed, you just need the following code on your ApplicationHelper:
module ApplicationHelper
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
end
This should be enough to have a sign_in form in any controller view. If you need more control over the login flow, you can check the Configuring Controller section on Devise Readme for more information on how to create a customizable controller.
Why your code throws a NoMethodError error
Your code throws an NoMethodError because you haven't defined a setter for the #resource instance variable. It should be either:
def resource=(value)
#resource = value
end
or with attr_writer :resource
I recommend this question for more info on the topic:
Why use Ruby's attr_accessor, attr_reader and attr_writer?
I'm using Devise, Omniauth-twitter and Omniauth-facebook for my rails app authentication and i had to make my own controller for editing user parameters without needing a password for users with providers like facebook and twitter.
And instead of routing the user to his profile by his user id, I used the devise helper current_user to show and edit the current_user parameters
My question is.. is it safe to do that ?
I'm a beginner.. so when something is done that easy i worry about security vulnerabilities. Here's my code.
profile_controller.rb
class ProfileController < ApplicationController
before_action :authenticate_user!
def show
#user = current_user
end
def edit
#profile = current_user
end
def update
#profile = current_user
if #profile.update(profile_params)
redirect_to profile_path(#profile)
else
render 'edit'
end
end
private
def profile_params
params.require(:user).permit(:username,:first_name,:last_name,:gender)
end
end
routes.rb
get'profile' => 'profile#show'
get'profile/edit' => 'profile#edit'
patch'profile/edit' => 'profile#update'
edit.html.erb
<%= form_for #profile, url: {action: "edit"} do |f| %>
<%= f.text_field :username, autofocus: true %>
<%= f.text_field :first_name, autofocus: true %>
<%= f.text_field :last_name, autofocus: true %>
<%= f.text_field :gender, autofocus: true %>
<%= f.submit "Sign up" %>
<% end %>
Well if you are using Devise you could make user of their existing views rather than, you trying to implement them on your own. But, I don't see any security threats with your current approach it's just that it is a waste of time.
Take a look at the devise documentation and check the Configuring Views section,
https://github.com/plataformatec/devise
I am trying to create a separate view for changing user password i don't how to do that.When I start I realize that it need different methods and may be some validations in model.
I need help how can I do this. I have no idea.What I need to include in controller , model and view.
I am also implementing "Enter old password to create new password".
I would recommend you to follow the RESTful principles.
Create a PasswordsController in your project with the actions edit and update.
Then create the edit.html.erb view with the form for the password change.
The validations in your model depend on your requirements.
Here is an example of this above:
Controller:
class PasswordsController < ApplicationController
before_action :set_user
before_action :check_current_password, only: :update
def edit
end
def update
if #user.update password_params
flash[:success] = 'Password changed'
redirect_to user_path # Your user's profile for example
else
flash[:danger] = 'Error'
render :edit
end
end
private
def password_params
params.require(:user).permit(:password, :password_confirmation)
end
def set_user
#user = current_user # Your current user
end
def check_current_password
unless #user.authenticate(params[:current_password])
raise # I would recommend you to work with exceptions here because you interrupt the process.
# You can make it also a bit more specific if you define an exception class and just catch them.
end
rescue
flash[:danger] = 'Current password incorrect!'
redirect_to password_path(current_user) # Redirect back to the change page
end
end
View:
<!-- HTML skeleton is in application.html.erb -->
<%= form_for #user, url: password_path, method: :patch do |f| %>
<%= text_field_tag :current_password %>
<%= f.text_field :password %>
<%= f.text_field :password_confirmation %>
<%= f.submit 'Change password' %>
<% end %>
Assuming you have installed bcrypt gem and your user model has a field called password_digest, your model should like this.
Model:
class User < ApplicationRecord
has_secure_password
end
This is a very simple implementation of a password change. I haven't tested it but it's just here to give you an idea how it works.
For more, see https://gist.github.com/thebucknerlife/10090014#steps
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.
I have in the navbar link for sign up/in. I am using Devise.
My problem is that I don't know how to set up variable instance, that would be accessible for the whole application (across to all controllers and actions).
For sign up, I did following:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :load_registration
def load_registration
#user = User.new
end
end
Which seems to be working. When I click on the link, then will pop up a window where will be displayed a form for sign up (if the user is not logged in). But how to do the same for log in?
It sounds like you're looking for current_user if a user is logged in. If so, your ApplicationController would change to:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :load_registration
def load_registration
#user = current_user || User.new
end
end
This way, if current_user is available, #user would be set to it, otherwise a new instance of User would be set to #user.
However, this defeats part of what devise does for you, as you should generally use current_user in your app as a helper for who is currently logged in.
I'm not sure what you are asking about but if you want to link to the registration or login page of Devise, you don't need any instance var. It depends on your resource but should be something like:
<% unless current_user %>
<%= link_to "Login", new_user_session_path %>
<%= link_to "Register", new_user_registration_path %>
<% end %>
Run rake routesto learn about your Devise paths.
If you want to put the login or register forms just in the navbar, you can do it with
<%= form_for(resource_name, resource, :url => session_path(resource_name)) do |f| %>
or
<%= form_for(resource_name, resource, :url => registration_path(resource_name)) do |f| %>
And include Devise helpers in your application helpers:
def resource_name
:user
end
def resource
#resource ||= User.new
end