Refactor `has_secure_password` to extract dedicated attribute module

Follow up of #26764 and #35700.

And add test case for #35700.
This commit is contained in:
Ryuta Kamizono 2019-04-05 01:43:41 +09:00
parent dc45130c44
commit 50fba828d5
3 changed files with 59 additions and 36 deletions

View File

@ -69,42 +69,7 @@ module ActiveModel
raise
end
mod = Module.new do
attr_reader attribute
define_method("#{attribute}=") do |unencrypted_password|
if unencrypted_password.nil?
self.send("#{attribute}_digest=", nil)
elsif !unencrypted_password.empty?
instance_variable_set("@#{attribute}", unencrypted_password)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
end
end
define_method("#{attribute}_confirmation=") do |unencrypted_password|
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
end
# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate_password('notright') # => false
# user.authenticate_password('mUc3m00RsqyRe') # => user
define_method("authenticate_#{attribute}") do |unencrypted_password|
attribute_digest = send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end
alias_method :authenticate, :authenticate_password if attribute == :password
end
include mod
include InstanceMethodsOnActivation.new(attribute)
if validations
include ActiveModel::Validations
@ -122,5 +87,42 @@ module ActiveModel
end
end
end
class InstanceMethodsOnActivation < Module
def initialize(attribute)
attr_reader attribute
define_method("#{attribute}=") do |unencrypted_password|
if unencrypted_password.nil?
self.send("#{attribute}_digest=", nil)
elsif !unencrypted_password.empty?
instance_variable_set("@#{attribute}", unencrypted_password)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
end
end
define_method("#{attribute}_confirmation=") do |unencrypted_password|
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
end
# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate_password('notright') # => false
# user.authenticate_password('mUc3m00RsqyRe') # => user
define_method("authenticate_#{attribute}") do |unencrypted_password|
attribute_digest = send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end
alias_method :authenticate, :authenticate_password if attribute == :password
end
end
end
end

View File

@ -184,6 +184,20 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_nil @existing_user.password_digest
end
test "override secure password attribute" do
assert_nil @user.password_called
@user.password = "secret"
assert_equal "secret", @user.password
assert_equal 1, @user.password_called
@user.password = "terces"
assert_equal "terces", @user.password
assert_equal 2, @user.password_called
end
test "authenticate" do
@user.password = "secret"
@user.recovery_password = "42password"

View File

@ -10,4 +10,11 @@ class User
has_secure_password :recovery_password, validations: false
attr_accessor :password_digest, :recovery_password_digest
attr_accessor :password_called
def password=(unencrypted_password)
self.password_called ||= 0
self.password_called += 1
super
end
end