Merge pull request #1673 from plataformatec/check_attributes_rebased

Check attributes on models
This commit is contained in:
José Valim 2012-02-22 08:07:16 -08:00
commit c5cb60a752
20 changed files with 218 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -110,4 +110,4 @@ class ValidatableTest < ActiveSupport::TestCase
Class.new.send :include, Devise::Models::Validatable
end
end
end
end

View File

@ -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

View File

@ -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