I am somewhat new to Rails and working on designing a User model using ActiveRecord. In this model, I have a password attribute which is intended to keep a hash of the user’s password.
I want to remove both reading and setting of this attribute directly. However, I can’t seem to find a way to remove the accessors when using the Rails console. The only viable solution so far has been to explicitly override the accessor methods for password and I don’t really want to override them, I want the accessors gone – or at least the reader.
Here is my model:
class User < ActiveRecord::Base
// various associations
def password_correct?(password)
read_attribute(:password) == hash(password)
end
def password=(password)
write_attribute(:password, hash(password))
end
def password
"get your dirty fingers off this attribute"
end
private
def hash(input)
Digest::SHA2.new(512).update(input).hexdigest
end
end
Any ideas how to achieve this or any shortcomings to this approach?
Based on the above answers, I have done some experimentation to obtain the desired result. I ended up making a “private” password_hash attribute and a virtual accessor called password.
I made some observations in this process:
It seems that ActiveRecord doesn’t have any concept of private attributes. Making the accessor methods private using symbols such as
private :password, :password=is not an option, because Rails throws anNameError: undefined methodwhen instantiating a model, as the model itself does not have these two methods defined (they seem to be inherited fromActiveRecord::Base).Overriding the password_hash accessors with pure nothing is great for preventing the manipulation of the attribute, however it also means that ActiveRecord itself fails when updating the password_hash attribute as it is calling an empty implementation.
So making the accessors private fails because they’re undefined in the actual model. Defining them also fails, because it breaks ActiveRecord. So what can you do?
I did both and a bit more. I made the accessors private, defined them and implemented both by calling
super. This prevents the controller (and the rails console) from accessing them by throwing aNoMethodError, but doesn’t reject ActiveRecord.A side note: Validation issues
One problem I encountered with my approach was broken validation. Enforcing a minimum length on the password_hash was no good as any password (even nothing) results in a 128 character SHA512 hash. So validating the hash made little sense. Instead I added validation to the virtual password accessor and added a
before_save :hash_passwordcallback which checks to see if the virtual accessor attribute has been set and if so, hashes it and writes it to the password_hash attribute.Final implementation
My implementation ended up this way: