Merge pull request #1673 from plataformatec/check_attributes_rebased
Check attributes on models
This commit is contained in:
commit
c5cb60a752
|
@ -1,5 +1,15 @@
|
|||
module Devise
|
||||
module Models
|
||||
class MissingAttribute < StandardError
|
||||
def initialize(attributes)
|
||||
@attributes = attributes
|
||||
end
|
||||
|
||||
def message
|
||||
"The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates configuration values for Devise and for the given module.
|
||||
#
|
||||
# Devise::Models.config(Devise::Authenticatable, :stretches, 10)
|
||||
|
@ -39,6 +49,22 @@ module Devise
|
|||
end
|
||||
end
|
||||
|
||||
def self.check_fields!(klass)
|
||||
failed_attributes = []
|
||||
|
||||
klass.devise_modules.each do |mod|
|
||||
instance = klass.new
|
||||
|
||||
const_get(mod.to_s.classify).required_fields(klass).each do |field|
|
||||
failed_attributes << field unless instance.respond_to?(field)
|
||||
end
|
||||
end
|
||||
|
||||
if failed_attributes.any?
|
||||
fail Devise::Models::MissingAttribute.new(failed_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
# Include the chosen devise modules in your model:
|
||||
#
|
||||
# devise :database_authenticatable, :confirmable, :recoverable
|
||||
|
@ -66,7 +92,7 @@ module Devise
|
|||
if class_mod.respond_to?(:available_configs)
|
||||
available_configs = class_mod.available_configs
|
||||
available_configs.each do |config|
|
||||
next unless options.key?(config)
|
||||
next unless options.key?(config)
|
||||
send(:"#{config}=", options.delete(config))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,15 @@ module Devise
|
|||
after_update :send_confirmation_instructions, :if => :reconfirmation_required?
|
||||
end
|
||||
|
||||
def self.required_fields(klass)
|
||||
required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
|
||||
if klass.reconfirmable
|
||||
required_methods << :unconfirmed_email
|
||||
end
|
||||
|
||||
required_methods
|
||||
end
|
||||
|
||||
# Confirm a user by setting it's confirmed_at to actual time. If the user
|
||||
# is already confirmed, add an error to email field. If the user is invalid
|
||||
# add errors
|
||||
|
|
|
@ -27,6 +27,10 @@ module Devise
|
|||
attr_accessor :password_confirmation
|
||||
end
|
||||
|
||||
def self.required_fields(klass)
|
||||
[:encrypted_password] + klass.authentication_keys
|
||||
end
|
||||
|
||||
# Generates password encryption based on the given value.
|
||||
def password=(new_password)
|
||||
@password = new_password
|
||||
|
|
|
@ -24,6 +24,10 @@ module Devise
|
|||
attr_accessor :password_confirmation
|
||||
end
|
||||
|
||||
def self.required_fields(klass)
|
||||
[:password_salt]
|
||||
end
|
||||
|
||||
# Generates password salt.
|
||||
def password=(new_password)
|
||||
self.password_salt = self.class.password_salt if new_password.present?
|
||||
|
@ -69,4 +73,4 @@ module Devise
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,6 +22,10 @@ module Devise
|
|||
|
||||
delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
|
||||
|
||||
def self.required_fields(klass)
|
||||
[:failed_attempts, :unlock_at, :unlock_token]
|
||||
end
|
||||
|
||||
# Lock a user setting its locked_at to actual time.
|
||||
def lock_access!
|
||||
self.locked_at = Time.now.utc
|
||||
|
|
|
@ -24,6 +24,10 @@ module Devise
|
|||
module Recoverable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def self.required_fields(klass)
|
||||
[:reset_password_sent_at, :reset_password_token]
|
||||
end
|
||||
|
||||
# Update password saving the record and clearing token. Returns true if
|
||||
# the passwords are valid and the record was saved, false otherwise.
|
||||
def reset_password!(new_password, new_password_confirmation)
|
||||
|
|
|
@ -41,6 +41,10 @@ module Devise
|
|||
|
||||
attr_accessor :remember_me, :extend_remember_period
|
||||
|
||||
def self.required_fields(klass)
|
||||
[:remember_created_at, :remember_token]
|
||||
end
|
||||
|
||||
# Generate a new remember token and save the record without validations
|
||||
# unless remember_across_browsers is true and the user already has a valid token.
|
||||
def remember_me!(extend_period=false)
|
||||
|
|
|
@ -27,6 +27,10 @@ module Devise
|
|||
module TokenAuthenticatable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def self.required_fields(klass)
|
||||
[:authentication_token]
|
||||
end
|
||||
|
||||
# Generate new authentication token (a.k.a. "single access token").
|
||||
def reset_authentication_token
|
||||
self.authentication_token = self.class.authentication_token
|
||||
|
@ -66,4 +70,4 @@ module Devise
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,10 @@ module Devise
|
|||
# * last_sign_in_ip - Holds the remote ip of the previous sign in
|
||||
#
|
||||
module Trackable
|
||||
def self.required_fields(klass)
|
||||
[:current_sign_in_at, :current_sign_in_ip, :last_sign_in_at, :last_sign_in_ip, :sign_in_count]
|
||||
end
|
||||
|
||||
def update_tracked_fields!(request)
|
||||
old_current, new_current = self.current_sign_in_at, Time.now.utc
|
||||
self.last_sign_in_at = old_current || new_current
|
||||
|
|
|
@ -328,4 +328,21 @@ class ReconfirmableTest < ActiveSupport::TestCase
|
|||
admin = Admin.find_by_unconfirmed_email_with_errors(:email => "new_test@email.com")
|
||||
assert admin.persisted?
|
||||
end
|
||||
|
||||
test 'required_fields should contain the fields that Devise uses' do
|
||||
assert_same_content Devise::Models::Confirmable.required_fields(User), [
|
||||
:confirmation_sent_at,
|
||||
:confirmation_token,
|
||||
:confirmed_at
|
||||
]
|
||||
end
|
||||
|
||||
test 'required_fields should also contain unconfirmable when reconfirmable_email is true' do
|
||||
assert_same_content Devise::Models::Confirmable.required_fields(Admin), [
|
||||
:confirmation_sent_at,
|
||||
:confirmation_token,
|
||||
:confirmed_at,
|
||||
:unconfirmable_email
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
|
|||
user.save!
|
||||
assert_equal email.downcase, user.email
|
||||
end
|
||||
|
||||
|
||||
test 'should remove whitespace from strip whitespace keys when saving' do
|
||||
# strip_whitespace_keys is set to :email by default.
|
||||
email = ' foo@bar.com '
|
||||
|
@ -92,14 +92,14 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
|
|||
:password => 'pass321', :password_confirmation => 'pass321')
|
||||
assert user.reload.valid_password?('pass321')
|
||||
end
|
||||
|
||||
|
||||
test 'should update password with valid current password and :as option' do
|
||||
user = create_user
|
||||
assert user.update_with_password(:current_password => '123456',
|
||||
:password => 'pass321', :password_confirmation => 'pass321', :as => :admin)
|
||||
assert user.reload.valid_password?('pass321')
|
||||
end
|
||||
|
||||
|
||||
test 'should add an error to current password when it is invalid' do
|
||||
user = create_user
|
||||
assert_not user.update_with_password(:current_password => 'other',
|
||||
|
@ -151,7 +151,7 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
|
|||
user.update_without_password(:email => 'new@example.com')
|
||||
assert_equal 'new@example.com', user.email
|
||||
end
|
||||
|
||||
|
||||
test 'should update the user without password with :as option' do
|
||||
user = create_user
|
||||
user.update_without_password(:email => 'new@example.com', :as => :admin)
|
||||
|
@ -170,4 +170,20 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
|
|||
user = User.create(:email => "HEllO@example.com", :password => "123456")
|
||||
assert !user.valid?
|
||||
end
|
||||
end
|
||||
|
||||
test 'required_fiels should be encryptable_password and the email field by default' do
|
||||
assert_same_content Devise::Models::DatabaseAuthenticatable.required_fields(User), [
|
||||
:email,
|
||||
:encrypted_password
|
||||
]
|
||||
end
|
||||
|
||||
test 'required_fields should be encryptable_password and the login when the login is on authentication_keys' do
|
||||
swap Devise, :authentication_keys => [:login] do
|
||||
assert_same_content Devise::Models::DatabaseAuthenticatable.required_fields(User), [
|
||||
:encrypted_password,
|
||||
:login
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -64,4 +64,10 @@ class EncryptableTest < ActiveSupport::TestCase
|
|||
admin.save
|
||||
assert_not admin.valid_password?('123456')
|
||||
end
|
||||
|
||||
test 'required_fields should contain the fields that Devise uses' do
|
||||
assert_same_content Devise::Models::Encryptable.required_fields(User), [
|
||||
:password_salt
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -235,4 +235,12 @@ class LockableTest < ActiveSupport::TestCase
|
|||
assert_nil user.locked_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test 'required_fields should contain the fields that Devise uses' do
|
||||
assert_same_content Devise::Models::Lockable.required_fields(User), [
|
||||
:failed_attempts,
|
||||
:unlock_at,
|
||||
:unlock_token
|
||||
]
|
||||
end
|
||||
end
|
|
@ -195,4 +195,11 @@ class RecoverableTest < ActiveSupport::TestCase
|
|||
assert_equal "has expired, please request a new one", reset_password_user.errors[:reset_password_token].join
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test 'required_fields should contain the fields that Devise uses' do
|
||||
assert_same_content Devise::Models::Recoverable.required_fields(User), [
|
||||
:reset_password_sent_at,
|
||||
:reset_password_token
|
||||
]
|
||||
end
|
||||
end
|
|
@ -54,7 +54,7 @@ class RememberableTest < ActiveSupport::TestCase
|
|||
resource.forget_me!
|
||||
assert resource.remember_created_at.nil?
|
||||
end
|
||||
|
||||
|
||||
test 'forget_me should not try to update resource if it has been destroyed' do
|
||||
resource = create_resource
|
||||
resource.destroy
|
||||
|
@ -165,4 +165,11 @@ class RememberableTest < ActiveSupport::TestCase
|
|||
assert_not_equal old, resource.remember_created_at
|
||||
end
|
||||
end
|
||||
|
||||
test 'should have the required_fiels array' do
|
||||
assert_same_content Devise::Models::Rememberable.required_fields(User), [
|
||||
:remember_created_at,
|
||||
:remember_token
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,4 +46,10 @@ class TokenAuthenticatableTest < ActiveSupport::TestCase
|
|||
user = User.find_for_token_authentication(:auth_token => {'$ne' => user1.authentication_token})
|
||||
assert_nil user
|
||||
end
|
||||
|
||||
test 'required_fields should contain the fields that Devise uses' do
|
||||
assert_same_content Devise::Models::TokenAuthenticatable.required_fields(User), [
|
||||
:authentication_token
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
require 'test_helper'
|
||||
|
||||
class TrackableTest < ActiveSupport::TestCase
|
||||
|
||||
test 'required_fields should contain the fields that Devise uses' do
|
||||
assert_same_content Devise::Models::Trackable.required_fields(User), [
|
||||
:current_sign_in_at,
|
||||
:current_sign_in_ip,
|
||||
:last_sign_in_at,
|
||||
:last_sign_in_ip,
|
||||
:sign_in_count
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -110,4 +110,4 @@ class ValidatableTest < ActiveSupport::TestCase
|
|||
Class.new.send :include, Devise::Models::Validatable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -107,3 +107,54 @@ class ActiveRecordTest < ActiveSupport::TestCase
|
|||
Admin.create!
|
||||
end
|
||||
end
|
||||
|
||||
class CheckFieldsTest < ActiveSupport::TestCase
|
||||
test 'checks if the class respond_to the required fields' do
|
||||
Player = Class.new do
|
||||
extend Devise::Models
|
||||
|
||||
def self.before_validation(instance)
|
||||
end
|
||||
|
||||
devise :database_authenticatable
|
||||
|
||||
attr_accessor :encrypted_password, :email
|
||||
end
|
||||
|
||||
assert_nothing_raised Devise::Models::MissingAttribute do
|
||||
Devise::Models.check_fields!(Player)
|
||||
end
|
||||
end
|
||||
|
||||
test 'raises Devise::Models::MissingAtrribute and shows the missing attribute if the class doesn\'t respond_to one of the attributes' do
|
||||
Clown = Class.new do
|
||||
extend Devise::Models
|
||||
|
||||
def self.before_validation(instance)
|
||||
end
|
||||
|
||||
devise :database_authenticatable
|
||||
|
||||
attr_accessor :encrypted_password
|
||||
end
|
||||
|
||||
assert_raise_with_message Devise::Models::MissingAttribute, "The following attribute(s) is (are) missing on your model: email" do
|
||||
Devise::Models.check_fields!(Clown)
|
||||
end
|
||||
end
|
||||
|
||||
test 'raises Devise::Models::MissingAtrribute with all the missing attributes if there is more than one' do
|
||||
Magician = Class.new do
|
||||
extend Devise::Models
|
||||
|
||||
def self.before_validation(instance)
|
||||
end
|
||||
|
||||
devise :database_authenticatable
|
||||
end
|
||||
|
||||
exception = assert_raise_with_message Devise::Models::MissingAttribute, "The following attribute(s) is (are) missing on your model: encrypted_password, email" do
|
||||
Devise::Models.check_fields!(Magician)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,4 +24,20 @@ class ActiveSupport::TestCase
|
|||
def assert_email_not_sent(&block)
|
||||
assert_no_difference('ActionMailer::Base.deliveries.size') { yield }
|
||||
end
|
||||
|
||||
def assert_same_content(expected, result)
|
||||
assert expected.size == result.size, "the arrays doesn't have the same content"
|
||||
expected.each do |element|
|
||||
result.index(element)
|
||||
assert !element.nil?, "the arrays doesn't have the same content"
|
||||
end
|
||||
end
|
||||
|
||||
def assert_raise_with_message(exception_klass, message)
|
||||
exception = assert_raise exception_klass do
|
||||
yield
|
||||
end
|
||||
|
||||
assert_equal exception.message, message, "The expected message was #{message} but your exception throwed #{exception.message}"
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue