MassAssignmentSecurity error while attr_accessible has been called - ruby-on-rails

I'm following along with this tutorial and at one point, it tells me to
... add password and password_confirmation attributes to the User model [...] Unlike the other attributes we’ve seen so far, the password attributes will be virtual—they will only exist temporarily in memory, and will not be persisted to the database.
And
As we’ll see in Section 6.3.4, these virtual attributes are implemented automatically by has_secure_password.
My model looks like this:
class User < ActiveRecord::Base
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
has_secure_password
attr_accessible :email, :is_admin, :name, :password, :password_confirmation
validates :name, presence: true, uniqueness: true
validates :password_digest, presence: true
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
validates :email, presence: true, format: {with: VALID_EMAIL_REGEX}, uniqueness: true
end
So now when I try to create a new user;
User.create(name: "Foo Bar", email: "foo#bar.net", password: "foobar", password_confirmation: "foobar")
I get the following error:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: password, password_confirmation
Why?!

The issues seemed as though the password and password_confirmation columns were missing in the database.
As per the comment, re-migrating and restarting the server would fix this problem.

Related

What does this validation do?

I know this validation checks for the presence of an e-mail before rendering an object as valid (in this case an user)
validates :email, presence: true
But this one I have no idea what it does
validates :login, :email, presence: true
It validates both :login and :email using the PresenceValidator, same as writing:
validates :login, presence: true
validates :email, presence: true
It add a presence validation to email and login.
validates takes a list of attributes you want to validate and a hash containing the validations you want to apply. Which is handy if you want to apply the same validations to several attributes.
These are all equivalent:
validate_presence_of :email, presence: true
validate_presence_of :login, presence: true
# or
validates :email, presence: true
validates :login, presence: true
# or
validates :login, :email, presence: true

Update action with empty password

In Michael Hartl's tutorial, the following is stated.
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
end
In case you’re worried that Listing 9.10 might allow new users to sign up with empty passwords, recall from Section 6.3.3 that has_secure_password includes a separate presence validation that specifically catches nil passwords.
My question is, how is the has_secure_password validation working if it allows the test to pass? I do not understand, clearly the has_secure_password validation is not "catching" this rule to bypass an empty password.
Further, how does rails know not to set and save the empty passwords to the user? please help me.
You can check the documentation here , it explains everything in details,
According to the source code:
def has_secure_password
.....
if options.fetch(:validations, true)
include ActiveModel::Validations
# This ensures the model has a password by checking whether the password_digest
# is present, so that this works with both new and existing records. However,
# when there is an error, the message is added to the password attribute instead
# so that the error message will make sense to the end-user.
validate do |record|
record.errors.add(:password, :blank) unless record.password_digest.present?
end
validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
validates_confirmation_of :password, allow_blank: true
end
..........
end
if you did not call has_secure_password(validations: false) the three types of validations will be added. i think the reason of the passing test is :
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
The :allow_nil option skips the validation when the value being
validated is nil.
To Add
how does rails know not to set and save the empty passwords to the
user?
I think it's because the params[:user][:password] is blank and not nil

Ruby on Rails: How to ignore the first validation of the first saved object?

My code require a user to have a sponsor id except the first user. My code is
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save {email.downcase!}
validates :first_name, :presence =>true, length: {maximum: 50}
validates :last_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 }, uniqueness: {case_sensitive: false }
has_secure_password
validates :password, presence: true, length: {minimum: 6}, allow_nil:true
.
.
.
class User < ActiveRecord::Base
belongs_to :sponsor, class_name: "User"
validates :sponsor, presence:true
validate :sponsor_id_valid
def sponsor_id_valid
errors.add(:sponsor_id, "is not exist") if User.exists? sponsor_id: sponsor_id
end
#Validate that the user isV exist in the data
def validate_sponsor_id
errors.add(:sponsor_id,"is not exist") if User.find(self.sponsor_id).blank?
end
end
I require that the User need to have sponsor_id for all user exept the User.first. I tried unless method but it didn't give me a desired outcome. The way I test the code is that I create a test code which give the first user to have a sponsor_id to be nil and the test failed. Can anyone help me please?
Thanks
def sponsor_id_valid
if self.sponsor_id.blank? && self != User.first
errors.add(:sponsor_id, "is not exist")
end
end
EDIT: to take account of the case where there isn't an existing first user yet, (which we want to pass) the logic could be changed to:
if self.sponsor_id.blank? && (first_user = User.first) && (self != first_user)
#add a validation error

How can I update particular fields of model with validation in Ruby on Rails?

There is an AcviteRecord Model named User like this:
class User < ActiveRecord::Base
validates :name, :presence => true
validates :email, :presence => true, :uniqueness => true
validates :plain_password, :presence => true, :confirmation => true
validates :plain_password_confirmation, :presence => true
#...other codes
end
It requires that the update of name and email and the update of password are separated.
When only update name and password, using update or update_attributes will cause password validation which is not needed. But using update_attribute will save name and email without validation.
Are there any ways to update particular fields of model with validation without causing the other fields' validation?
Give it a try, might help
class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, :uniqueness => true
validates :plain_password, length: { in: 4..255, allow_nil: true }, confirmation: true
validates :plain_password_confirmation, presence: true, if: -> (user){ user.plain_password.present? }
# ......
# ......
end
Apart from this you should reconsider about saving plain_password ;)
You can adjust your validations to only run on create. Requiring confirmation ensures changes on edit are applied.
validates :plain_password,
confirmation: true,
presence: {
on: :create },
length: {
minimum: 8,
allow_blank: true }
validates :plain_password_confirmation,
presence: {
on: :create }
I am assuming you are hashing your passwords, so this would accompany code similar to:
attr_accessor :plain_password
before_save :prepare_password
def encrypted_password( bcrypt_computational_cost = Rails.env.test? ? 1 : 10)
BCrypt::Password.create plain_password, cost: bcrypt_computational_cost
end
private #===========================================================================================================
# Sets this users password hash to the encrypted password, if the password is not blank.
def prepare_password
self.password_hash = encrypted_password if plain_password.present?
end
A better way to handle this is to not include the fields in the rest of the edit form, but rather provide a link to "Change my password". This link would direct to a new form (perhaps in a modal window) which will require the new password, confirmation of the new password, and the old password, to prevent account hijacking.
In your case you can use has_secure_password The password presence is only validated on creation.

Validate before attr_writer is performed in Rails

I have the following validations for my passwords:
validates :password, presence: true, length: {minimum: 5}, confirmation: true
validates :password_confirmation, presence: true, if: :password_changed?
And I also have attr_writer to hash the password & password confirmation:
def password=(val)
self[:password] = my_hash_func(value.to_s)
end
My problem is that when I create a new user, if that password is empty, or has less than 5 chars, when it hits the attr_writer it creates a value (even though it should not be valid).
What is the proper approach to avoid your attr_writers to bypass validation?
Your attr_writer is called before validations takes place and your my_hash_func probably hashed empty password to some value which is of greater length than 5. You could have hash_password function, which would be called before model is saved.
class SomeClass
validates :password, presence: true, length: { minimum: 5 }, confirmation: true, if: :password_changed?
validates :password_confirmation, presence: true, if: :password_changed?
before_save :hash_password, if: :password_changed?
protected
def hash_password
self.password = my_hash_func(password)
end
end
You need :password_changed? checks because you only want to validate and hash password if it has been changed.

Resources