How to solve strong parameters' contradiction when editing an object? - ruby-on-rails

The problem I'm facing most likely has something to do with strong parameters. The thing is when I try to edit some user information and update it, an error appears which is not related with this form. That kind of error's supposed to pop up only when signing up or logging in.
For example, here is my database. And then I click 'Edit'.
After editing some information and submitting it, the error pops up.
controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to [#user, #task], notice: "Thank you for signing up!"
else
render "new"
end
end
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to #user
else
render 'edit'
end
end
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to {|format| format.js }
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
end
views/user/edit.html
<h1>Editing user</h1>
<%= form_for :user, url: #user, method: :patch do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#user.errors.count, "error") %> prohibited
this task from being saved:
</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<%= f.label :first_name %><br>
<%= f.text_field :first_name %>
</p>
<p>
<%= f.label :last_name %><br>
<%= f.text_field :last_name %>
</p>
<p>
<%= f.submit %>
</p>
<%= link_to 'Back to List', users_path %>
<% end %>
models/user.rb
class User < ActiveRecord::Base
has_secure_password
EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :first_name, presence: true,
length: {maximum: 20}
validates :last_name, presence: true,
length: {maximum: 40}
validates :email, presence: true,
format: {with: EMAIL_REGEX},
uniqueness: {case_sensitive: false}
validates :password,
length: {within: 6..40}
has_many :tasks
end
What do I do? Can anybody help?

The problem is not with the strong params. It is due to validation you have on password.
This
validates :password,length: {within: 6..40}
should be
validates :password,length: {within: 6..40}, on: :create

Yes, the problem is in strong params. You should never permit password and password_confirmation
def user_params
params.require(:user).permit(:first_name, :last_name, :email)
end
Just remove password and password_confirmation from user_params. You wouldn't want to mass assign those values.
And this would be a very bad practice to store a plain password in database. You should store encrypted or hashed password in database; it would enhance the security and integrity of your application.
To get more information about storing an encrypted or hashed password, please take a look at this and this links.

Related

Form Error Messages Not Generating in Rails App

Hi I have a simple app that I am building and am having trouble getting the error messages to appear when someone inputs invalid information or no information at all into a field.
The form I am using is to sign up a new user, the code associated with the user and form are below;
users_controller.rb
Class UsersController < ApplicationController
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#country = Country.all
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to #user
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password)
end
end
user.rb
class User < ApplicationRecord
before_save { self.email = email.downcase }
validates :first_name, presence: true, length: { maximum: 25 }
validates :first_name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
has_many :trips
has_many :countries, through: :trips
end
new.html.erb
<div class="container">
<h1 class="text-center">Sign up</h1>
<div class="row">
<div class="col-md-6 offset-md-3 ">
<%=form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :first_name %>
<%= f.text_field :first_name, class: "form-control" %>
<%= f.label :last_name %>
<%= f.text_field :last_name, class: "form-control" %>
<%= f.label :email %>
<%= f.email_field :email, class: "form-control" %>
<%= f.label :password %>
<%= f.password_field :password, class: "form-control" %>
<%= f.submit "Create an account", class: 'form-control btn btn-primary' %>
<% end %>
</div>
</div>
</div>
_error_messages.html.erb
<% if #user.errors.any? %>
<div class="alert alert-danger">
The form contains <%= pluralize(#user.errors.count, "error") %>.
</div>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% else %>
<h3>test</h3>
<% end %>
When I load the form I do see the "Test" string, which I put in my _error_messages.html.erb for visibility. However when I enter data in the signup page, it reloads the page (as it should rather than sending it to the user page when all fields are valid). However, the "Test" string still appears at the top rather than the error messages that should.
My assumption is I need some sort of session or something to remember what the errors were, as right now it reloads a completely new page with nothing in memory, however, I cannot find the solution to this anywhere at the moment.
Any help would be much appreciated!
As I said, you need to change
redirect_to '/signup'
to
render 'new'
From the Guides
The render method is used so that the #user object is passed back
to the new template when it is rendered. This rendering is done within
the same request as the form submission, whereas the redirect_to will
tell the browser to issue another request.
That said, so as the redirect_to issues a new request to the browser, the values of #user is lost, in other words #user is a new instance that is instantiated again. That is why <% if #user.errors.any? %> always returns false as if there are no errors in #user.

"Password confirmation" message not showing up when both password fields are empty

Working through the railstutorial.org . Currently on the Update Profile page part of it. When leaving the Password and Password Confirmation fields empty, only the Password is too short error comes up, though in the tutorial screenshot Password confirmation can't be blank message is present. But, it does show up when the Password field is filled and Password Confirmation field is left empty.
edit.html.erb :
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
<% end %>
<%= gravatar_for #user %>
change
</div>
</div>
users_controller.rb:
.
.
.
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
.
.
.
user.rb:
class User < ActiveRecord::Base
has_secure_password
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(?:\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }
before_create :create_remember_token
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.digest(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.digest(User.new_remember_token)
end
end
Your User.rb model is out of sync with the one MHartl uses at that point in the tutorial. Specifically, notice that he has an explicit validates :password_confirmation, presence: true in addition to the validates :password, length: { minimum: 6 }. When you call #user.update_attributes, it hits these validators and, in his case, both fail, whereas in your User.rb model there is not the presence validator.
When you have the password field filled in, you're hitting validators defined in has_secure_password rather than in your model, which is why they appear then.
Make sure you include the password and password_confirmation in your allowed parameters in you controller:
private
def user_params
params.required(:user).permit(:name, :email, password,:password_confirmation)
end
EDIT:
As the other had already pointed out you miss the validation of password_confirmation
validates :password_confirmation, presence: true

form_for omit specific fields from #user model

I have a user model: :name, :email, :password
Here is my form:
<%= form_for(#user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.text_field :password %>
<%= f.submit "Save" %>
<% end %>
I want to display the form without :password, when I remove the password attribute and submit the form, the fields do not update in the database. I get a rollback.
You can do something like:
object.attribute = value
object.save(:validate => false)
This will never call your validation
But remember this will never validates your email and name fields as well if they are blank.
Commenting this line in my user model fixed the issue:
validates :password, length: { minimum: 6 }
However, as noted in the comments, it is not sufficient.
A user can edit their own profile, an admin can edit their own and others profiles.
Here is a solution:
def update
#user = User.find_by_id(params[:id])
if current_user?(#user)
if #user.update_attributes(user_params)
flash[:success] = "Your profile updated"
redirect_to #user
else
render 'edit'
end
else
if #user.update_attributes(admin_params)
flash[:success] = "Others profile updated"
redirect_to #user
else
render 'admin_edit_user'
end
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password)
end
def admin_params
params.require(:user).permit(:name, :email, :password) if current_user.admin?
end
If you want to skip validations on a particular form for a particular attribute, you could do the following.
Class User
attr_accessor :skip_password_validation
validate_presence_of :password, unless: -> { skip_password_validation }
end
This creates a virtual attribute that you can set and read but won't get persisted in the database.
Then you can put a hidden field for :skip_password_validation in whatever form you want and it will skip the validations.
<%= f.hidden_field :skip_password_validation, value: true %>
You'll also have to add this param to the user_params in the controller.

Added Bio field to 'edit profile' - created migration but then what to add to model?

I've followed Hartl's rails tutorials to the of Chap 9 and now i'm building my own idea so i can get deep into rails.
Current issue - I've added a Bio filed to user profiles in edit and the text box appear on the Users profile but i can't figure out how to save the text added to the Bio text box. I've generated a migration called "add_bio_to_user_profile" (haven't raked yet) but i'm struggling to figure out what to add to the User.rb model. Does controller come into this to?
Migration "add_bio_to_user_profile"
class AddBioToUserProfile < ActiveRecord::Migration
def change
add_column :user_profiles, :, :string
end
end
Model/User.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
#VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
#format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
end
controllers/user_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
#users = User.paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
end
def new
#user = User.find(params[:id])
end
def new
#user = User.new
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User destroyed."
redirect_to users_url
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
show.html.erb
<% provide(:title, #user.name) %>
<div class="row">
<aside class="span4">
<section>
<h1>
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
</section>
<div>
<textarea rows="3"></textarea>
</div>
</aside>
</div>
edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :bio %>
<textarea rows="3"></textarea>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
<% end %>
<%= gravatar_for #user %>
change
</div>
</div>
Formally, you dont have to do anything. Rails magic starts here. You run the migration, restart server if you are running production env, change the name of textbox to :bio in edit html.erb
<%=f.text_field :bio%>
When this form is submitted, it automatically gets saved in the table. Additionally, you can add validation on this field in model.
In edit.html.erb, make this change
<%= f.label :bio %>
<%=f.text_field :bio%>
and in show.html.erb
<%= gravatar_for #user %>
<%= #user.name %>
</h1>
Bio: <%= #user.bio %>
One change in controller.rb
def user_params
params.require(:user).permit(:name, :email, :bio, :password,
:password_confirmation)
end
You want to add bio field to users table I guess.
Here is the migration.
class AddBioToUserProfile < ActiveRecord::Migration
def change
add_column :users, :bio, :string
end
end
While you are going for a migration, only model comes to play. and in this,
users is your table name and bio is the new column and string is the data-type.

Can't mass-assign protected attributes with ruby-on-rails

I am trying to have a nested form on my users/new page, where it accepts user-attributes and also company-attributes. When you submit the form:
Here's what my error message reads:
ActiveModel::MassAssignmentSecurity::Error in UsersController#create
Can't mass-assign protected attributes: companies
app/controllers/users_controller.rb:12:in `create'
Here's the code for my form:
<%= form_for #user do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.fields_for :companies do |c| %>
<%= c.label :name, "Company Name"%>
<%= c.text_field :name %>
<% end %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<br>
<% if current_page?(signup_path) %>
<%= f.submit "Sign Up", class: "btn btn-large btn-primary" %> Or, <%= link_to "Login", login_path %>
<% else %>
<%= f.submit "Update User", class: "btn btn-large btn-primary" %>
<% end %>
<% end %>
Users Controller:
class UsersController < ApplicationController
def index
#user = User.all
end
def new
#user = User.new
end
def create
#user = User.create(params[:user])
if #user.save
session[:user_id] = #user.id #once user account has been created, a session is not automatically created. This fixes that by setting their session id. This could be put into Controller action to clean up duplication.
flash[:success] = "Your account has been created!"
redirect_to tasks_path
else
render 'new'
end
end
def show
#user = User.find(params[:id])
#tasks = #user.tasks
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = #user.name.possessive + " profile has been updated"
redirect_to #user
else
render 'edit'
end
#if #task.update_attributes params[:task]
#redirect_to users_path
#flash[:success] = "User was successfully updated."
#end
end
def destroy
#user = User.find(params[:id])
unless current_user == #user
#user.destroy
flash[:success] = "The User has been deleted."
end
redirect_to users_path
flash[:error] = "Error. You can't delete yourself!"
end
end
Company Controller
class CompaniesController < ApplicationController
def index
#companies = Company.all
end
def new
#company = Company.new
end
def edit
#company = Company.find(params[:id])
end
def create
#company = Company.create(params[:company])
#if #company.save
#session[:user_id] = #user.id #once user account has been created, a session is not automatically created. This fixes that by setting their session id. This could be put into Controller action to clean up duplication.
#flash[:success] = "Your account has been created!"
#redirect_to tasks_path
#else
#render 'new'
#end
end
def show
#comnpany = Company.find(params[:id])
end
end
User model
class User < ActiveRecord::Base
has_secure_password
attr_accessible :name, :email, :password, :password_confirmation
has_many :tasks, dependent: :destroy
belongs_to :company
accepts_nested_attributes_for :company
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }
#below not needed anymore, due to has_secure_password
#validates :password_confirmation, presence: true
end
Company Model
class Company < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
First for debugging put the bang on the create in the two controllers like so: create! Your log may spit out more.
Then, if that sucked, try it the old fashioned way of Building the two Objects and assigning each one with the params.
I assume also that this is it for attributes, no after saves that should have more to the schema then you are showing.
Lastly, you are missing
def new
#user = User.new
#company = #user.companies.build
end
Print out the params too, just in case it says something wonky but adding this line: as #Beerlington said should work too :company_attributes, maybe companies_attributes... spit balling here.
Add attr_accessible :companies to your user model attr list

Resources