On Github paneq / devise-coupling
robert @pankowecki
@arkency
flexible authentication solution for Rails based on Warden
composed of 10 modules
class User
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:token_authenticatable, :lockable, :timeoutable, :omniauthable,
:remember_for => 6.months
end
☹
User class has a tendency for becoming God-object in monolithic rails apps
wc -l app/models/user.rb 348 app/models/user.rb 528 app/models/user.rb 508 app/models/user.rb 762 app/models/user.rb
Adding devise does not help in counteracting Fact #1
Devise::Models.constants.sort.each {|c| Devise::Models.const_get(c).instance_methods.each{|m| puts "#{c}##{m}" } }; "
Authenticatable#valid_for_authentication?
Authenticatable#unauthenticated_message
Authenticatable#active_for_authentication?
Authenticatable#inactive_message
Authenticatable#authenticatable_salt
Authenticatable#serializable_hash
Authenticatable#devise_mailer
Authenticatable#send_devise_notification
Authenticatable#downcase_keys
Authenticatable#strip_whitespace
Authenticatable#apply_to_attribute_or_variable
Confirmable#confirm!
Confirmable#confirmed?
Confirmable#pending_reconfirmation?
Confirmable#send_confirmation_instructions
Confirmable#resend_confirmation_token
Confirmable#active_for_authentication?
Confirmable#inactive_message
Confirmable#skip_confirmation!
Confirmable#skip_confirmation_notification!
Confirmable#skip_reconfirmation!
Confirmable#send_on_create_confirmation_instructions
Confirmable#confirmation_required?
Confirmable#confirmation_period_valid?
Confirmable#confirmation_period_expired?
Confirmable#pending_any_confirmation
Confirmable#generate_confirmation_token
Confirmable#generate_confirmation_token!
Confirmable#after_password_reset
Confirmable#postpone_email_change_until_confirmation
Confirmable#postpone_email_change?
Confirmable#reconfirmation_required?
Confirmable#send_confirmation_notification?
Confirmable#distance_of_time_in_words
Confirmable#time_ago_in_words
Confirmable#distance_of_time_in_words_to_now
Confirmable#date_select
Confirmable#time_select
Confirmable#datetime_select
Confirmable#select_datetime
Confirmable#select_date
Confirmable#select_time
Confirmable#select_second
Confirmable#select_minute
Confirmable#select_hour
Confirmable#select_day
Confirmable#select_month
Confirmable#select_year
Confirmable#time_tag
DatabaseAuthenticatable#password=
DatabaseAuthenticatable#valid_password?
DatabaseAuthenticatable#clean_up_passwords
DatabaseAuthenticatable#update_with_password
DatabaseAuthenticatable#update_without_password
DatabaseAuthenticatable#destroy_with_password
DatabaseAuthenticatable#after_database_authentication
DatabaseAuthenticatable#authenticatable_salt
DatabaseAuthenticatable#password_digest
Lockable#lock_strategy_enabled?
Lockable#unlock_strategy_enabled?
Lockable#lock_access!
Lockable#unlock_access!
Lockable#access_locked?
Lockable#send_unlock_instructions
Lockable#resend_unlock_token
Lockable#active_for_authentication?
Lockable#inactive_message
Lockable#valid_for_authentication?
Lockable#unauthenticated_message
Lockable#attempts_exceeded?
Lockable#generate_unlock_token
Lockable#generate_unlock_token!
Lockable#lock_expired?
Lockable#if_access_locked
Recoverable#reset_password!
Recoverable#send_reset_password_instructions
Recoverable#reset_password_period_valid?
Recoverable#should_generate_reset_token?
Recoverable#generate_reset_password_token
Recoverable#generate_reset_password_token!
Recoverable#clear_reset_password_token
Recoverable#after_password_reset
Rememberable#remember_me
Rememberable#remember_me=
Rememberable#extend_remember_period
Rememberable#extend_remember_period=
Rememberable#remember_me!
Rememberable#forget_me!
Rememberable#remember_expired?
Rememberable#remember_expires_at
Rememberable#rememberable_value
Rememberable#rememberable_options
Rememberable#generate_remember_token?
Rememberable#generate_remember_timestamp?
Timeoutable#timedout?
Timeoutable#timeout_in
TokenAuthenticatable#reset_authentication_token
TokenAuthenticatable#reset_authentication_token!
TokenAuthenticatable#ensure_authentication_token
TokenAuthenticatable#ensure_authentication_token!
TokenAuthenticatable#after_token_authentication
TokenAuthenticatable#expire_auth_token_on_timeout
Trackable#update_tracked_fields!
Validatable#password_required?
Validatable#email_required?"
Biggest rails apps problem: lack of modularity
class User # wannabe module A if anyone cares has_many :alphas has_many :betas has_many :gammas has_many :deltas # wannabe module B if we only had time to refactor someday has_many :sigmas has_many :epsilons has_many :lambdas # maybe on hackathon day in our company has_many :has_many # nope... # too many exception end
# ==> Configuration for :database_authenticatable # For bcrypt, this is the cost for hashing the password and defaults to 10. If # using other encryptors, it sets how many times you want the password re-encrypted. # # Limiting the stretches to just one in testing will increase the performance of # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use # a value less than 10 in other environments. config.stretches = Rails.env.test? ? 1 : 10
will increase the performance of your test suite dramatically !!!111oneoneone
will increase the performance of your test suite dramatically !!!
FactoryGirl.define do
factory :user do
email { generate(:email) }
password { generate(:password) }
end
end
who coupled authentication concerns (like having password) to all our test suits that require User object (in domain sense)
or encrypted password
maybe just in Invoicing, Inventory or Digital Product moduls tests?
or two...
If you want users to update all information except the password itself, you can use update_without_password provided by Devise and then proceed to implement the views.
class RegistrationsController < Devise::RegistrationsController
def update
@user = User.find(current_user.id)
successfully_updated = if needs_password?(@user, params)
@user.update_with_password(devise_parameter_sanitizer.sanitize(:account_update))
else
# remove the virtual current_password attribute
# update_without_password doesn't know how to ignore it
params[:user].delete(:current_password)
@user.update_without_password(devise_parameter_sanitizer.sanitize(:account_update))
end
if successfully_updated
set_flash_message :notice, :updated
# Sign in the user bypassing validation in case their password changed
sign_in @user, :bypass => true
redirect_to after_update_path_for(@user)
else
render "edit"
end
end
private
# check if we need password to update user data
# ie if password or email was changed
# extend this as needed
def needs_password?(user, params)
user.email != params[:user][:email] ||
params[:user][:password].present?
end
end
def update_with_password(params, *options)
current_password = params.delete(:current_password)
if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end
result = if valid_password?(current_password)
update_attributes(params, *options)
else
self.assign_attributes(params, *options)
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end
clean_up_passwords
result
end
def update_without_password(params, *options)
params.delete(:password)
params.delete(:password_confirmation)
result = update_attributes(params, *options)
clean_up_passwords
result
end
module Auth
class UserCredentials
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:token_authenticatable, :lockable, :timeoutable, :omniauthable,
:remember_for => 6.months
end
end
Authentication module does not sound so bad.
User knows nothing about possible authentication methods such as password for WEB, token per mobile device etc.
Maybe logins, passwords, tokens, emails (duplicated) belong to Auth module and the rest of your system couldn't care less about them?
Think about it next time you add devise to your User module, mkay? 😉
• 3 ways to do eager loading (preloading) in Rails 3 & 4