diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccb0882a4..2bc6a7c843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### Unreleased + +* enhancements + * Compute each strategy's `authenticatable_name` once per strategy class instead of on every request: Warden builds new strategy instances per request, so the per-instance memo never hit. + ### 5.0.4 - 2026-05-08 * security fixes diff --git a/lib/devise/strategies/authenticatable.rb b/lib/devise/strategies/authenticatable.rb index 2af7a741cf..800fae076f 100644 --- a/lib/devise/strategies/authenticatable.rb +++ b/lib/devise/strategies/authenticatable.rb @@ -169,8 +169,14 @@ def parse_authentication_key_values(hash, keys) # Holds the authenticatable name for this class. Devise::Strategies::DatabaseAuthenticatable # becomes simply :database. def authenticatable_name + @authenticatable_name ||= self.class.authenticatable_name + end + + # Computed once per strategy class: Warden builds new strategy + # instances on every request. + def self.authenticatable_name @authenticatable_name ||= - ActiveSupport::Inflector.underscore(self.class.name.split("::").last). + ActiveSupport::Inflector.underscore(name.split("::").last). sub("_authenticatable", "").to_sym end end diff --git a/test/strategies/authenticatable_test.rb b/test/strategies/authenticatable_test.rb new file mode 100644 index 0000000000..34b2c251fb --- /dev/null +++ b/test/strategies/authenticatable_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'devise/strategies/database_authenticatable' +require 'devise/strategies/rememberable' + +class AuthenticatableStrategyTest < ActiveSupport::TestCase + test 'authenticatable_name comes from the strategy class name' do + assert_equal :database, Devise::Strategies::DatabaseAuthenticatable.authenticatable_name + assert_equal :rememberable, Devise::Strategies::Rememberable.authenticatable_name + assert_equal :database, Devise::Strategies::DatabaseAuthenticatable.new({}).send(:authenticatable_name) + end + + test 'authenticatable_name is computed once per strategy class, not per instance' do + Devise::Strategies::DatabaseAuthenticatable.authenticatable_name + ActiveSupport::Inflector.expects(:underscore).never + 2.times { Devise::Strategies::DatabaseAuthenticatable.new({}).send(:authenticatable_name) } + end +end