From 5ae575b03b18049c8121d8016a16c0aa43771e51 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 13 Feb 2026 11:05:57 -0300 Subject: [PATCH 1/5] Remove dependency on ORM adapter by implementing our own methods Where things may diverge that we need specific logic between each ORM, we can implement our own finders / query methods, since we have already a base for this with dirty tracking anyway. This eliminates a dependency that's been around since the very beginning of Devise. --- Gemfile.lock | 2 -- .../devise/registrations_controller.rb | 2 +- devise.gemspec | 1 - lib/devise.rb | 1 - lib/devise/models/authenticatable.rb | 4 +-- lib/devise/models/recoverable.rb | 2 +- lib/devise/models/rememberable.rb | 4 +-- lib/devise/orm.rb | 34 +++++++++++++++++++ lib/devise/orm/active_record.rb | 2 -- lib/devise/orm/mongoid.rb | 2 -- lib/devise/token_generator.rb | 2 +- test/integration/registerable_test.rb | 32 ++++++++--------- test/models/rememberable_test.rb | 4 +-- .../users/omniauth_callbacks_controller.rb | 2 +- 14 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bdf09fcccb..a4d960b06c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,7 +13,6 @@ PATH specs: devise (5.0.1) bcrypt (~> 3.0) - orm_adapter (~> 0.1) railties (>= 7.0) responders warden (~> 1.2.3) @@ -187,7 +186,6 @@ GEM rack-openid (~> 1.4) ruby-openid (~> 2.1, >= 2.1.8) version_gem (~> 1.1, >= 1.1.8) - orm_adapter (0.5.0) ostruct (0.6.3) pp (0.6.3) prettyprint diff --git a/app/controllers/devise/registrations_controller.rb b/app/controllers/devise/registrations_controller.rb index 79e2b0e81c..f62180b8e7 100644 --- a/app/controllers/devise/registrations_controller.rb +++ b/app/controllers/devise/registrations_controller.rb @@ -44,7 +44,7 @@ def edit # We need to use a copy of the resource because we don't want to change # the current user in place. def update - self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key) + self.resource = resource_class.devise_find_by_id!(send(:"current_#{resource_name}").to_key) prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email) resource_updated = update_resource(resource, account_update_params) diff --git a/devise.gemspec b/devise.gemspec index 1caa6aeb39..bcdad24ff8 100644 --- a/devise.gemspec +++ b/devise.gemspec @@ -28,7 +28,6 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.7.0' s.add_dependency("warden", "~> 1.2.3") - s.add_dependency("orm_adapter", "~> 0.1") s.add_dependency("bcrypt", "~> 3.0") s.add_dependency("railties", ">= 7.0") s.add_dependency("responders") diff --git a/lib/devise.rb b/lib/devise.rb index 8e0c85e77d..b46342d35b 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -3,7 +3,6 @@ require 'rails' require 'active_support/core_ext/numeric/time' require 'active_support/dependencies' -require 'orm_adapter' require 'set' require 'securerandom' require 'responders' diff --git a/lib/devise/models/authenticatable.rb b/lib/devise/models/authenticatable.rb index df964537ea..2546c6b4f3 100644 --- a/lib/devise/models/authenticatable.rb +++ b/lib/devise/models/authenticatable.rb @@ -227,7 +227,7 @@ def serialize_into_session(record) end def serialize_from_session(key, salt) - record = to_adapter.get(key) + record = devise_find_by_id(key) record if record && record.authenticatable_salt == salt end @@ -265,7 +265,7 @@ def find_for_authentication(tainted_conditions) end def find_first_by_auth_conditions(tainted_conditions, opts = {}) - to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts)) + devise_find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts)) end # Find or initialize a record setting an error if it can't be found. diff --git a/lib/devise/models/recoverable.rb b/lib/devise/models/recoverable.rb index b17c42aae6..85b39e5964 100644 --- a/lib/devise/models/recoverable.rb +++ b/lib/devise/models/recoverable.rb @@ -113,7 +113,7 @@ module ClassMethods # If a user is not found, return nil def with_reset_password_token(token) reset_password_token = Devise.token_generator.digest(self, :reset_password_token, token) - to_adapter.find_first(reset_password_token: reset_password_token) + devise_find_first(reset_password_token: reset_password_token) end # Attempt to find a user by its email. If a record is found, send new diff --git a/lib/devise/models/rememberable.rb b/lib/devise/models/rememberable.rb index a66979ad59..455927ee80 100644 --- a/lib/devise/models/rememberable.rb +++ b/lib/devise/models/rememberable.rb @@ -139,7 +139,7 @@ def serialize_into_cookie(record) def serialize_from_cookie(*args) id, token, generated_at = *args - record = to_adapter.get(id) + record = devise_find_by_id(id) record if record && record.remember_me?(token, generated_at) end @@ -147,7 +147,7 @@ def serialize_from_cookie(*args) def remember_token #:nodoc: loop do token = Devise.friendly_token - break token unless to_adapter.find_first({ remember_token: token }) + break token unless devise_find_first(remember_token: token) end end diff --git a/lib/devise/orm.rb b/lib/devise/orm.rb index 3f3ac86db7..ef3570d125 100644 --- a/lib/devise/orm.rb +++ b/lib/devise/orm.rb @@ -9,8 +9,42 @@ def self.active_record?(model) def self.included(model) if Devise::Orm.active_record?(model) model.include DirtyTrackingActiveRecordMethods + model.extend ActiveRecordQueryMethods else model.include DirtyTrackingMongoidMethods + model.extend MongoidQueryMethods + end + end + + module ActiveRecordQueryMethods + def devise_find_by_id(id) + id = id.first if id.is_a?(Array) + where(id: id).first + end + + def devise_find_by_id!(id) + id = id.first if id.is_a?(Array) + find(id) + end + + def devise_find_first(conditions) + where(conditions).first + end + end + + module MongoidQueryMethods + def devise_find_by_id(id) + id = id.first if id.is_a?(Array) + where(id: id).first + end + + def devise_find_by_id!(id) + id = id.first if id.is_a?(Array) + find(id) + end + + def devise_find_first(conditions) + where(conditions).first end end diff --git a/lib/devise/orm/active_record.rb b/lib/devise/orm/active_record.rb index 0fecf64e3f..419e03b8a9 100644 --- a/lib/devise/orm/active_record.rb +++ b/lib/devise/orm/active_record.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'orm_adapter/adapters/active_record' - ActiveSupport.on_load(:active_record) do extend Devise::Models end diff --git a/lib/devise/orm/mongoid.rb b/lib/devise/orm/mongoid.rb index 034501eff4..e10e13cd63 100644 --- a/lib/devise/orm/mongoid.rb +++ b/lib/devise/orm/mongoid.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true ActiveSupport.on_load(:mongoid) do - require 'orm_adapter/adapters/mongoid' - Mongoid::Document::ClassMethods.send :include, Devise::Models end diff --git a/lib/devise/token_generator.rb b/lib/devise/token_generator.rb index 9eb74a046a..3b918decc3 100644 --- a/lib/devise/token_generator.rb +++ b/lib/devise/token_generator.rb @@ -19,7 +19,7 @@ def generate(klass, column) loop do raw = Devise.friendly_token enc = OpenSSL::HMAC.hexdigest(@digest, key, raw) - break [raw, enc] unless klass.to_adapter.find_first({ column => enc }) + break [raw, enc] unless klass.devise_find_first(column => enc) end end diff --git a/test/integration/registerable_test.rb b/test/integration/registerable_test.rb index 9289ac6af1..875956f797 100644 --- a/test/integration/registerable_test.rb +++ b/test/integration/registerable_test.rb @@ -19,7 +19,7 @@ class RegistrationTest < Devise::IntegrationTest assert warden.authenticated?(:admin) assert_current_url "/admin_area/home" - admin = Admin.to_adapter.find_first(order: [:id, :desc]) + admin = Admin.order(id: :desc).first assert_equal 'new_user@test.com', admin.email end @@ -68,7 +68,7 @@ def user_sign_up assert_not warden.authenticated?(:user) - user = User.to_adapter.find_first(order: [:id, :desc]) + user = User.order(id: :desc).first assert_equal 'new_user@test.com', user.email assert_not user.confirmed? end @@ -110,7 +110,7 @@ def user_sign_up assert_contain "Email is invalid" assert_contain %r{Password confirmation doesn['’]t match Password} assert_contain "2 errors prohibited" - assert_nil User.to_adapter.find_first + assert_nil User.first assert_not warden.authenticated?(:user) end @@ -154,7 +154,7 @@ def user_sign_up assert_current_url '/' assert_contain 'Your account has been updated successfully.' - assert_equal "user.new@example.com", User.to_adapter.find_first.email + assert_equal "user.new@example.com", User.first.email end test 'a signed in user should still be able to use the website after changing their password' do @@ -216,7 +216,7 @@ def user_sign_up assert_contain 'Your account has been updated successfully.' assert warden.authenticated?(:user) - assert_equal "user.new@example.com", User.to_adapter.find_first.email + assert_equal "user.new@example.com", User.first.email end end @@ -232,7 +232,7 @@ def user_sign_up assert_contain 'user@test.com' assert_have_selector 'form input[value="user.new@example.com"]' - assert_equal "user@test.com", User.to_adapter.find_first.email + assert_equal "user@test.com", User.first.email end test 'a signed in user should be able to edit their password' do @@ -247,7 +247,7 @@ def user_sign_up assert_current_url '/' assert_contain 'Your account has been updated successfully.' - assert User.to_adapter.find_first.valid_password?('pass1234') + assert User.first.valid_password?('pass1234') end test 'a signed in user should not be able to edit their password with invalid confirmation' do @@ -260,7 +260,7 @@ def user_sign_up click_button 'Update' assert_contain %r{Password confirmation doesn['’]t match Password} - assert_not User.to_adapter.find_first.valid_password?('pas123') + assert_not User.first.valid_password?('pas123') end test 'a signed in user should see a warning about minimum password length' do @@ -276,7 +276,7 @@ def user_sign_up click_button "Cancel my account" assert_contain "Bye! Your account has been successfully cancelled. We hope to see you again soon." - assert_empty User.to_adapter.find_all + assert_empty User.all end test 'a user should be able to cancel sign up by deleting data in the session' do @@ -303,7 +303,7 @@ def user_sign_up assert_response :success assert_includes response.body, '{"admin":{' - admin = Admin.to_adapter.find_first(order: [:id, :desc]) + admin = Admin.order(id: :desc).first assert_equal 'new_user@test.com', admin.email end @@ -312,7 +312,7 @@ def user_sign_up assert_response :success assert_includes response.body, '{"user":{' - user = User.to_adapter.find_first(order: [:id, :desc]) + user = User.order(id: :desc).first assert_equal 'new_user@test.com', user.email end @@ -340,7 +340,7 @@ def user_sign_up sign_in_as_user delete user_registration_path(format: 'json') assert_response :success - assert_equal 0, User.to_adapter.find_all.size + assert_equal 0, User.all.size end end @@ -355,7 +355,7 @@ class ReconfirmableRegistrationTest < Devise::IntegrationTest assert_current_url '/admin_area/home' assert_contain 'but we need to verify your new email address' - assert_equal 'admin.new@example.com', Admin.to_adapter.find_first.unconfirmed_email + assert_equal 'admin.new@example.com', Admin.first.unconfirmed_email get edit_admin_registration_path assert_contain 'Currently waiting confirmation for: admin.new@example.com' @@ -373,7 +373,7 @@ class ReconfirmableRegistrationTest < Devise::IntegrationTest assert_current_url '/admin_area/home' assert_contain 'Your account has been updated successfully.' - assert Admin.to_adapter.find_first.valid_password?('pas123') + assert Admin.first.valid_password?('pas123') end test 'a signed in admin should not see a reconfirmation message if they did not change their email, despite having an unconfirmed email' do @@ -393,7 +393,7 @@ class ReconfirmableRegistrationTest < Devise::IntegrationTest assert_current_url '/admin_area/home' assert_contain 'Your account has been updated successfully.' - assert_equal "admin.new@example.com", Admin.to_adapter.find_first.unconfirmed_email - assert Admin.to_adapter.find_first.valid_password?('pas123') + assert_equal "admin.new@example.com", Admin.first.unconfirmed_email + assert Admin.first.valid_password?('pas123') end end diff --git a/test/models/rememberable_test.rb b/test/models/rememberable_test.rb index 8b83172120..12610b14fb 100644 --- a/test/models/rememberable_test.rb +++ b/test/models/rememberable_test.rb @@ -21,7 +21,7 @@ def create_resource test 'remember_me should not generate a new token if valid token exists' do user = create_user user.singleton_class.send(:attr_accessor, :remember_token) - User.to_adapter.expects(:find_first).returns(nil) + User.expects(:devise_find_first).returns(nil) user.remember_me! existing_token = user.remember_token @@ -40,7 +40,7 @@ def create_resource test 'can generate remember token' do user = create_user user.singleton_class.send(:attr_accessor, :remember_token) - User.to_adapter.expects(:find_first).returns(nil) + User.expects(:devise_find_first).returns(nil) user.remember_me! assert user.remember_token end diff --git a/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb b/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb index f5327fbd7b..4fdd099e46 100644 --- a/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +++ b/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb @@ -8,7 +8,7 @@ def facebook end def sign_in_facebook - user = User.to_adapter.find_first(email: 'user@test.com') + user = User.devise_find_first(email: 'user@test.com') user.remember_me = true sign_in user render body: "" From d8d0ee68de38d96c38d73dd2ab297f44630ea094 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 13 Feb 2026 11:07:50 -0300 Subject: [PATCH 2/5] Use more idiomatic methods for AR --- lib/devise/orm.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/devise/orm.rb b/lib/devise/orm.rb index ef3570d125..1d0c4fc28d 100644 --- a/lib/devise/orm.rb +++ b/lib/devise/orm.rb @@ -19,7 +19,7 @@ def self.included(model) module ActiveRecordQueryMethods def devise_find_by_id(id) id = id.first if id.is_a?(Array) - where(id: id).first + find_by(id: id) end def devise_find_by_id!(id) @@ -28,7 +28,7 @@ def devise_find_by_id!(id) end def devise_find_first(conditions) - where(conditions).first + find_by(conditions) end end From c975d2c7f552dd83d860540edd96a9fb0ddd5c1b Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 13 Feb 2026 11:10:03 -0300 Subject: [PATCH 3/5] Reorganize ORM module names for consistency / clarity --- lib/devise/orm.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/devise/orm.rb b/lib/devise/orm.rb index 1d0c4fc28d..912fff9ef6 100644 --- a/lib/devise/orm.rb +++ b/lib/devise/orm.rb @@ -8,15 +8,15 @@ def self.active_record?(model) def self.included(model) if Devise::Orm.active_record?(model) - model.include DirtyTrackingActiveRecordMethods - model.extend ActiveRecordQueryMethods + model.include ActiveRecordDirtyTracking + model.extend ActiveRecordFinders else - model.include DirtyTrackingMongoidMethods - model.extend MongoidQueryMethods + model.include MongoidDirtyTracking + model.extend MongoidFinders end end - module ActiveRecordQueryMethods + module ActiveRecordFinders def devise_find_by_id(id) id = id.first if id.is_a?(Array) find_by(id: id) @@ -32,7 +32,7 @@ def devise_find_first(conditions) end end - module MongoidQueryMethods + module MongoidFinders def devise_find_by_id(id) id = id.first if id.is_a?(Array) where(id: id).first @@ -48,7 +48,7 @@ def devise_find_first(conditions) end end - module DirtyTrackingActiveRecordMethods + module ActiveRecordDirtyTracking def devise_email_before_last_save email_before_last_save end @@ -74,7 +74,7 @@ def devise_respond_to_and_will_save_change_to_attribute?(attribute) end end - module DirtyTrackingMongoidMethods + module MongoidDirtyTracking def devise_email_before_last_save respond_to?(:email_previously_was) ? email_previously_was : email_was end From 4a9f04a22d50009cfc4f3ba66fc5bac5a8bde47c Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 13 Feb 2026 11:12:03 -0300 Subject: [PATCH 4/5] Add changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a84d5a2196..d8b9490dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### Future + + * Remove `orm_adapter` dependency. Devise now implements its own ORM-specific finders in `Devise::Orm`. [#5823](https://github.com/heartcombo/devise/pull/5823) + ### Unreleased * enhancements From 3057aa6657d2fda8777171b2120a1c2d36553ba0 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 13 Feb 2026 11:19:22 -0300 Subject: [PATCH 5/5] Simplify finders method naming --- app/controllers/devise/registrations_controller.rb | 2 +- lib/devise/models/authenticatable.rb | 4 ++-- lib/devise/models/recoverable.rb | 2 +- lib/devise/models/rememberable.rb | 4 ++-- lib/devise/orm.rb | 12 ++++++------ lib/devise/token_generator.rb | 2 +- test/models/rememberable_test.rb | 4 ++-- .../users/omniauth_callbacks_controller.rb | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/controllers/devise/registrations_controller.rb b/app/controllers/devise/registrations_controller.rb index f62180b8e7..00fb06749a 100644 --- a/app/controllers/devise/registrations_controller.rb +++ b/app/controllers/devise/registrations_controller.rb @@ -44,7 +44,7 @@ def edit # We need to use a copy of the resource because we don't want to change # the current user in place. def update - self.resource = resource_class.devise_find_by_id!(send(:"current_#{resource_name}").to_key) + self.resource = resource_class.devise_find!(send(:"current_#{resource_name}").to_key) prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email) resource_updated = update_resource(resource, account_update_params) diff --git a/lib/devise/models/authenticatable.rb b/lib/devise/models/authenticatable.rb index 2546c6b4f3..fdeea649c5 100644 --- a/lib/devise/models/authenticatable.rb +++ b/lib/devise/models/authenticatable.rb @@ -227,7 +227,7 @@ def serialize_into_session(record) end def serialize_from_session(key, salt) - record = devise_find_by_id(key) + record = devise_find(key) record if record && record.authenticatable_salt == salt end @@ -265,7 +265,7 @@ def find_for_authentication(tainted_conditions) end def find_first_by_auth_conditions(tainted_conditions, opts = {}) - devise_find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts)) + devise_find_by(devise_parameter_filter.filter(tainted_conditions).merge(opts)) end # Find or initialize a record setting an error if it can't be found. diff --git a/lib/devise/models/recoverable.rb b/lib/devise/models/recoverable.rb index 85b39e5964..c04f1d7e62 100644 --- a/lib/devise/models/recoverable.rb +++ b/lib/devise/models/recoverable.rb @@ -113,7 +113,7 @@ module ClassMethods # If a user is not found, return nil def with_reset_password_token(token) reset_password_token = Devise.token_generator.digest(self, :reset_password_token, token) - devise_find_first(reset_password_token: reset_password_token) + devise_find_by(reset_password_token: reset_password_token) end # Attempt to find a user by its email. If a record is found, send new diff --git a/lib/devise/models/rememberable.rb b/lib/devise/models/rememberable.rb index 455927ee80..e9e3052a81 100644 --- a/lib/devise/models/rememberable.rb +++ b/lib/devise/models/rememberable.rb @@ -139,7 +139,7 @@ def serialize_into_cookie(record) def serialize_from_cookie(*args) id, token, generated_at = *args - record = devise_find_by_id(id) + record = devise_find(id) record if record && record.remember_me?(token, generated_at) end @@ -147,7 +147,7 @@ def serialize_from_cookie(*args) def remember_token #:nodoc: loop do token = Devise.friendly_token - break token unless devise_find_first(remember_token: token) + break token unless devise_find_by(remember_token: token) end end diff --git a/lib/devise/orm.rb b/lib/devise/orm.rb index 912fff9ef6..ca0dd848ee 100644 --- a/lib/devise/orm.rb +++ b/lib/devise/orm.rb @@ -17,33 +17,33 @@ def self.included(model) end module ActiveRecordFinders - def devise_find_by_id(id) + def devise_find(id) id = id.first if id.is_a?(Array) find_by(id: id) end - def devise_find_by_id!(id) + def devise_find!(id) id = id.first if id.is_a?(Array) find(id) end - def devise_find_first(conditions) + def devise_find_by(conditions) find_by(conditions) end end module MongoidFinders - def devise_find_by_id(id) + def devise_find(id) id = id.first if id.is_a?(Array) where(id: id).first end - def devise_find_by_id!(id) + def devise_find!(id) id = id.first if id.is_a?(Array) find(id) end - def devise_find_first(conditions) + def devise_find_by(conditions) where(conditions).first end end diff --git a/lib/devise/token_generator.rb b/lib/devise/token_generator.rb index 3b918decc3..7b573ccfd6 100644 --- a/lib/devise/token_generator.rb +++ b/lib/devise/token_generator.rb @@ -19,7 +19,7 @@ def generate(klass, column) loop do raw = Devise.friendly_token enc = OpenSSL::HMAC.hexdigest(@digest, key, raw) - break [raw, enc] unless klass.devise_find_first(column => enc) + break [raw, enc] unless klass.devise_find_by(column => enc) end end diff --git a/test/models/rememberable_test.rb b/test/models/rememberable_test.rb index 12610b14fb..1e83b50f27 100644 --- a/test/models/rememberable_test.rb +++ b/test/models/rememberable_test.rb @@ -21,7 +21,7 @@ def create_resource test 'remember_me should not generate a new token if valid token exists' do user = create_user user.singleton_class.send(:attr_accessor, :remember_token) - User.expects(:devise_find_first).returns(nil) + User.expects(:devise_find_by).returns(nil) user.remember_me! existing_token = user.remember_token @@ -40,7 +40,7 @@ def create_resource test 'can generate remember token' do user = create_user user.singleton_class.send(:attr_accessor, :remember_token) - User.expects(:devise_find_first).returns(nil) + User.expects(:devise_find_by).returns(nil) user.remember_me! assert user.remember_token end diff --git a/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb b/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb index 4fdd099e46..f39cc59d07 100644 --- a/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +++ b/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb @@ -8,7 +8,7 @@ def facebook end def sign_in_facebook - user = User.devise_find_first(email: 'user@test.com') + user = User.devise_find_by(email: 'user@test.com') user.remember_me = true sign_in user render body: ""