Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

undefined method `will_save_change_to_email?' for User #4542

Closed
MTEKode opened this issue May 18, 2017 · 31 comments · Fixed by #5576
Closed

undefined method `will_save_change_to_email?' for User #4542

MTEKode opened this issue May 18, 2017 · 31 comments · Fixed by #5576
Assignees

Comments

@MTEKode
Copy link

MTEKode commented May 18, 2017

Hi,
I have problems with:

  • Devise 4.3
  • Rails 5.1
  • Mongoid 6.1
NoMethodError (undefined method `will_save_change_to_email?' for #<User:0x007f80b1778ec0>):
  
activesupport (5.1.1) lib/active_support/callbacks.rb:413:in `block in make_lambda'
activesupport (5.1.1) lib/active_support/callbacks.rb:177:in `block (2 levels) in halting_and_conditional'
activesupport (5.1.1) lib/active_support/callbacks.rb:177:in `each'
activesupport (5.1.1) lib/active_support/callbacks.rb:177:in `all?'
activesupport (5.1.1) lib/active_support/callbacks.rb:177:in `block in halting_and_conditional'
activesupport (5.1.1) lib/active_support/callbacks.rb:507:in `block in invoke_before'
activesupport (5.1.1) lib/active_support/callbacks.rb:507:in `each'
activesupport (5.1.1) lib/active_support/callbacks.rb:507:in `invoke_before'
activesupport (5.1.1) lib/active_support/callbacks.rb:130:in `run_callbacks'
mongoid (6.1.0) lib/mongoid/interceptable.rb:138:in `run_callbacks'
activesupport (5.1.1) lib/active_support/callbacks.rb:825:in `_run_validate_callbacks'
activemodel (5.1.1) lib/active_model/validations.rb:405:in `run_validations!'
activemodel (5.1.1) lib/active_model/validations/callbacks.rb:110:in `block in run_validations!'
activesupport (5.1.1) lib/active_support/callbacks.rb:131:in `run_callbacks'
mongoid (6.1.0) lib/mongoid/interceptable.rb:138:in `run_callbacks'
activesupport (5.1.1) lib/active_support/callbacks.rb:825:in `_run_validation_callbacks'
activemodel (5.1.1) lib/active_model/validations/callbacks.rb:110:in `run_validations!'
activemodel (5.1.1) lib/active_model/validations.rb:335:in `valid?'
mongoid (6.1.0) lib/mongoid/validatable.rb:97:in `valid?'
activemodel (5.1.1) lib/active_model/validations.rb:372:in `invalid?'
mongoid (6.1.0) lib/mongoid/persistable/creatable.rb:115:in `prepare_insert'
mongoid (6.1.0) lib/mongoid/persistable/creatable.rb:23:in `insert'
mongoid (6.1.0) lib/mongoid/persistable/savable.rb:23:in `save'
/Volumes/FdData/Users/mToribio/.rvm/gems/ruby-2.4.1/bundler/gems/devise_token_auth-73d82dbd0982/app/controllers/devise_token_auth/registrations_controller.rb:42:in `create'
actionpack (5.1.1) lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
actionpack (5.1.1) lib/abstract_controller/base.rb:186:in `process_action'
actionpack (5.1.1) lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack (5.1.1) lib/abstract_controller/callbacks.rb:20:in `block in process_action'
activesupport (5.1.1) lib/active_support/callbacks.rb:131:in `run_callbacks'
actionpack (5.1.1) lib/abstract_controller/callbacks.rb:19:in `process_action'
actionpack (5.1.1) lib/action_controller/metal/rescue.rb:20:in `process_action'
actionpack (5.1.1) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
activesupport (5.1.1) lib/active_support/notifications.rb:166:in `block in instrument'
activesupport (5.1.1) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
activesupport (5.1.1) lib/active_support/notifications.rb:166:in `instrument'
actionpack (5.1.1) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (5.1.1) lib/action_controller/metal/params_wrapper.rb:252:in `process_action'
actionpack (5.1.1) lib/abstract_controller/base.rb:124:in `process'
actionview (5.1.1) lib/action_view/rendering.rb:30:in `process'
actionpack (5.1.1) lib/action_controller/metal.rb:189:in `dispatch'
actionpack (5.1.1) lib/action_controller/metal.rb:253:in `dispatch'
actionpack (5.1.1) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'
actionpack (5.1.1) lib/action_dispatch/routing/route_set.rb:31:in `serve'
actionpack (5.1.1) lib/action_dispatch/routing/mapper.rb:16:in `block in <class:Constraints>'
actionpack (5.1.1) lib/action_dispatch/routing/mapper.rb:46:in `serve'
actionpack (5.1.1) lib/action_dispatch/journey/router.rb:46:in `block in serve'
actionpack (5.1.1) lib/action_dispatch/journey/router.rb:33:in `each'
actionpack (5.1.1) lib/action_dispatch/journey/router.rb:33:in `serve'
actionpack (5.1.1) lib/action_dispatch/routing/route_set.rb:832:in `call'
warden (1.2.7) lib/warden/manager.rb:36:in `block in call'
warden (1.2.7) lib/warden/manager.rb:35:in `catch'
warden (1.2.7) lib/warden/manager.rb:35:in `call'
rack (2.0.3) lib/rack/etag.rb:25:in `call'
rack (2.0.3) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.3) lib/rack/head.rb:12:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/callbacks.rb:26:in `block in call'
activesupport (5.1.1) lib/active_support/callbacks.rb:97:in `run_callbacks'
actionpack (5.1.1) lib/action_dispatch/middleware/callbacks.rb:24:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/debug_exceptions.rb:59:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.1.1) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.1.1) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.1.1) lib/active_support/tagged_logging.rb:69:in `block in tagged'
activesupport (5.1.1) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.1.1) lib/active_support/tagged_logging.rb:69:in `tagged'
railties (5.1.1) lib/rails/rack/logger.rb:24:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/request_id.rb:25:in `call'
rack (2.0.3) lib/rack/runtime.rb:22:in `call'
activesupport (5.1.1) lib/active_support/cache/strategy/local_cache_middleware.rb:27:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.1.1) lib/action_dispatch/middleware/static.rb:125:in `call'
rack (2.0.3) lib/rack/sendfile.rb:111:in `call'
rack-cors (0.4.1) lib/rack/cors.rb:81:in `call'
railties (5.1.1) lib/rails/engine.rb:522:in `call'
puma (3.8.2) lib/puma/configuration.rb:224:in `call'
puma (3.8.2) lib/puma/server.rb:600:in `handle_request'
puma (3.8.2) lib/puma/server.rb:435:in `process_client'
puma (3.8.2) lib/puma/server.rb:299:in `block in run'
puma (3.8.2) lib/puma/thread_pool.rb:120:in `block in spawn_thread'

In rails 5.0 thats works good.
I have been investigating a little more and I not found where is defined will_save_change_to_email?.
I think thats could be the problem...
For now, I will use Rails 5.0 but I would like use Rails 5.1.

Thanks in advance.

@aalbagarcia
Copy link

aalbagarcia commented May 20, 2017

I have the same problem. I'm using mongoid 6.1 and Active Record. According to the comment in 3e1c9e3 it's not working at the moment. Also related to this issue.

@codewizardry
Copy link

codewizardry commented Jul 9, 2017

I am not sure what exactly transpired to have caused this error. It seems that all of a sudden I'm getting the same Undefined method will_save_change_to_email? for User error.

So, a quick monkey patch, at least for the time being, is to just define will_save_change_to_email? in the model, in my particlar case, the model was User, so I defined it in there as follows:

def will_save_change_to_email?
end

This resolves the problem, at least in my case it did.

The issue here is that method

def will_save_change_to_email?
end

is not even defined in the devise gem.

Although it is referenced in the callback, in the following files:

lib/devise/models/confirmable.rb
lib/devise/models/validatable.rb

and

test/rails_app/lib/shared_admin.rb

it is not defined whatsoever.

Please note that this problem is only raised and replicated when using Mongoid as of today's date, 7/9/2017.

The problem does not exist when using Active Record.

@chrise86
Copy link

I've facing this issue with ActiveRecord.

rails (5.1.2)
devise (4.3.0)

@codewizardry
Copy link

codewizardry commented Jul 25, 2017

Do you have the mongoid gem installed? You probably do, as one could still have both, Active Record and Mongoid in the same app. I don't think the issue is stemming from Active Record, if that was the case, then a significant amount of Rails apps would break, and it would have been resolved a long time ago, not exactly sure what the source of the issue is other than what I described above. At any rate, did you define the method will_save_change_to_email? in your model? For me, all I did is simply define it in my model like this:

def will_save_change_to_email? end

And it worked like a charm.

I'm using:

Rails (5.1.2) Devise (4.3.0) Mongoid (6.2.0) ActiveRecord (5.1.2)

Naturally, when using Mongoid for example, it seems that Devise is calling a "will_save_change_to_email?" method without defining it, it's probably depending on mongoid supplying that method, I think. So, whatever the reason, I simply defined the missing method, and it ended up working great for me.

If you ended up resolving the problem another way, please share your solution with the community on here, perhaps there's a better way than monkey-patching stuff :)

@chrise86
Copy link

chrise86 commented Jul 26, 2017

So it turns out that the model does not have an email attribute, and both email_required? and email_changed? had been defined inside the model to negate the validation in previous versions.

The project is one I've inherited and had the pleasure of taking from Rails 3 all the way up to 5.1, while switching from MySQL to Postgresql. It's been, interesting...

TL;DR:

def will_save_change_to_email?
  false
end

works fine for me!

@tegon
Copy link
Member

tegon commented Dec 21, 2017

Hello @mtoribio, thanks for your report.
Can you provide us a sample application that reproduces the issue in isolation?
That would help us find the issue.

Thank you!

@nao215
Copy link

nao215 commented Dec 25, 2017

I got a same error.

My environment is below.

ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin16]
rails (5.1.4)
devise (4.3.0)

I dont use mongoid, Im using mysql.

And the table we use for devise doesnt have column the name of 'email'.
I think you can reproduce this error by creating app using devise without using email column.

Sorry I dont have time to create public repo, but I wish this will help you.

@tegon
Copy link
Member

tegon commented Jan 8, 2018

@nao215 what devise modules are you using in your model? validatable requires an email attribute and we also call this method on confirmable (but I guess you don't use confirmable since you don't have an email column)

@nao215
Copy link

nao215 commented Jan 9, 2018

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable,
         authentication_keys: [:agency_email]
  config.case_insensitive_keys = [ :agency_email ]
  config.strip_whitespace_keys = [ :agency_email ]
  config.confirmation_keys = [ :agency_email ]
  config.reset_password_keys = [ :agency_email ]

this is our code for devise.
Sorry, I have a lot of client and it was just quick look.

The error happened when we try to up rails version to 5.
I guess
3e1c9e3
This is the reason maybe.

We used validatable from rails 4 and we version uped.
Thanks to Devise.activerecord51?, we can use validatable without 'email' but version up to rails 5, Devise.activerecord51? return true and we cannot use validatable without email column.

Thanks for you reply and sorry for dirty english.
I think the best way is use column which name is "email".

@tegon
Copy link
Member

tegon commented Jan 9, 2018

@nao215 But even when you hit the else clause, the method email_changed? will be called. If you don't have that column I guess this method doesn't exist on your model too, right?

@nao215
Copy link

nao215 commented Jan 12, 2018

@tegon

But even when you hit the else clause, the method email_changed? will be called.

Yes, but the error is undefined method 'will_save_change_to_email?', not undefined method 'email_changed?'.
I cant find definition of will_save_change_to_email but this is looks like auto generated method so I guess if we dont use column name of email, not generated. ※ just I guess

@tegon
Copy link
Member

tegon commented Jan 12, 2018

@nao215 What I meant is that is weird this worked before you update rails, because even though Devise.activerecord51? returns false, the method will_save_change_to_email? won't be called, but email_changed? would. Does your model responds to email_changed??

gigorok added a commit to gigorok/yeti-web that referenced this issue May 10, 2018
@mbell697
Copy link

I've run into this as well.

The problem appears to be that devise only makes a global check for Devise.activerecord51? without consulting which ORM is actually storing the User model. In our case the User model is backed by Mongoid 6.4.1, not ActiveRecord. So devise is trying to call AR methods on a mongoid model which doesn't support them.

@tegon
Copy link
Member

tegon commented Aug 1, 2018

@mbell697 Can you provide us a sample application that reproduces the issue in isolation?
Another option is to use our bug report template.
That would help us find the issue.

@mbell697
Copy link

mbell697 commented Aug 1, 2018

@tegon If you modify your test suite to load both Mongoid and ActiveRecord, but test Mongoid backed device models, you'll likely see a bunch of failures. Can probably just make a quick change here (https://github.com/plataformatec/devise/blob/master/test/rails_app/config/application.rb#L12) to always load active record and run the suite with DEVISE_ORM=mongoid.

@doughsay
Copy link

doughsay commented Oct 3, 2018

I ran into this as well, from a clean new rails application.

STR:

1.) rails new auth_test
2.) add and configure mongoid
3.) add and configure devise
4.) generate new User model using devise generator (generates a mongoid model)
5.) can't sign up (undefined method 'will_save_change_to_email?' for User)

You can fix this problem by not including ActiveRecord: rails new auth_test -O

@tegon
Copy link
Member

tegon commented Dec 27, 2018

Related sample application
#4910 (comment)

@SaimonL
Copy link

SaimonL commented Jan 25, 2019

If you are using Rails 5.x with MongoID and NOT active record then you must comment out "require 'active_storage/engine'" in the application.rb.

Active storage uses Active record.

Example

require_relative 'boot'

require 'rails'
# Pick the frameworks you want:
require 'active_model/railtie'
require 'active_job/railtie'
# require 'active_storage/engine'
require 'action_controller/railtie'
require 'action_mailer/railtie'
require 'action_view/railtie'
require 'action_cable/engine'
require 'sprockets/railtie'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module ReadSharedBooks
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # Don't generate system test files.
    config.generators.system_tests = nil
  end
end

@theshashiverma
Copy link

theshashiverma commented Jan 31, 2019

@mtoribio @aalbagarcia @codewizardry @chrise86 @tegon please have a look on this

#5017

@chrisokamoto
Copy link

chrisokamoto commented Jul 2, 2019

I am still face problem with this.. I am using

  • rails 5.1.7
  • devise 4.6.2
  • activerecord 5.1.7
  • mongoid 6.4.4

I need both mongoid and activerecord on my system.. the User class uses mongoid and has a field called email. Everything was working perfectly on rails 5.0 but since I updated Rails to 5.1.7 I get this message undefined method will_save_change_to_email?. I tried doing what it was suggested here on this thread, creating a method called will_save_change_to_email? on User.rb. but the expected behaviour is still not right. For example, when the user changes his email, Devise is not sending the email for the user to confirm his email again. Instead I just see a notice message saying that my data was updated, which means that I see the registrations.updated message instead of registrations.update_needs_confirmation message.

Any updates on this?

@tegon
Copy link
Member

tegon commented Aug 1, 2019

@chrisokamoto Unfortunately, I haven't been able to dig deep into this yet. I'll try to do it soon.

@chrisokamoto
Copy link

setting this 3 aliases "solved" the problem for me for now:

alias will_save_change_to_email? email_changed?
alias email_in_database email_was
alias email_before_last_save email_before_destroy_was

@rp-pedraza
Copy link

rp-pedraza commented Aug 25, 2019

I define email as a virtual attribute and I found out that I need to call define_attribute_method with it.

Default prefixes and suffixes are defined in ActiveRecord::AttributeMethods::Dirty. ActiveModel::AttributeMethods is also already included by ActiveRecord::AttributeMethods, which is also included by ActiveRecord::Base. Rails version 6.0.0.

Edit: I actually might need to do something more than that because the generic methods declared in ActiveRecord::AttributeMethods::Dirty may not apply to a virtual attribute. I might as well just create custom methods unless I find a generic solution.

@apoorv-1310
Copy link

create a function in model file

def will_save_change_to_email? false end

@vanboom
Copy link

vanboom commented Feb 18, 2021

For info...
I just upgrade an app from Rails 5.2.4.3 (which does not exhibit this issue) to 5.2.4.5 and this error appeared. I am using Mongoid for database and thought I inhibited the ActiveRecord per the Mongoid instructions.

In Rails 5.2.4.3:

defined?(ActiveRecord)
> nil

In Rails 5.2.4.5

defined?(ActiveRecord)
> "constant"

This appears to be causing Devise.activerecord51? to be true and the root cause of the undefined method will_save_change_to_email? error.

From the Mongoid docs...

NOTE

Sometimes Spring tries to load ActiveRecord even when the application contains no ActiveRecord references. If this happens, add an ActiveRecord adapter dependency such as sqlite3 to your Gemfile so that ActiveRecord may be completely loaded or remove Spring from your application.

@vanboom
Copy link

vanboom commented Feb 18, 2021

After removing spring from Gemfile, this issue still occurs:

Under Rails 5.2.4.3, defined?(ActiveRecord) returns nil
Under Rails 5.2.4.5, defined?(ActiveRecord) returns true

UPDATE: DatabaseCleaner gem was including ActiveRecord. Use database_cleaner-mongoid instead.

@SaimonL
Copy link

SaimonL commented May 17, 2021

DEVICE GEM IS WORKING FINE NO ISSUE AT ALL

Here is my following setup

Rails 5.2.6
ruby 2.7.3p183 (2021-04-05 revision 6847ee089d) [x86_64-linux]
mongoid (7.0.13)
devise (4.8.0)
devise-encryptable (0.2.0)

In file "config/initializers/devise.rb"

require 'devise/orm/mongoid'

Problem

Device thinks you are using ActiveRecord even though you have created your rails application with the option --skip-active-record.

Why

You have a gem that is not compatible with mongoid and is using ActiveRecord monkey patching.

How

When device try to detect if you are using ActiveRecord be using the code below

defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new("5.1.x")

it comes up false positive.

Solution

Enter rails console and type in ActiveRecord and you should see error message:

NameError: uninitialized constant ActiveRecord
from (pry):13:in `<main>'

If you don't then start removing gems until you see that error message.

For me it was the following gems that gave me grief:

  • crono
  • delayed_job_web
  • database_cleaner

I replaced "cromo" with "whenever", "database_cleaner" with "database_cleaner-mongoid"

@carlosantoniodasilva
Copy link
Member

@SaimonL what you explained here makes sense to me.

Is anyone still having this kind of issue? Please let me know so I can try to take another look, otherwise I'm gonna close it in a week or two.

@carlosantoniodasilva carlosantoniodasilva self-assigned this Mar 7, 2023
@mbell697
Copy link

mbell697 commented Mar 7, 2023

Yes - the behavior is still the same.

What @SaimonL described is a possible trigger, but in our case it's much simpler - our application uses both Mongoid and ActiveRecord.

At the base level, the problem is that Devise currently checks for the presence of the ActiveRecord gem to determine how to behave:

defined?(ActiveRecord) && ActiveRecord.gem_version >= Gem::Version.new("5.1.x")

This drives a lot of choices down stream, e.g.

if Devise.activerecord51?

The problem is that the presence of the ActiverRecord gem doesn't mean that any given user model is an ActiveRecord model. The better approach would be to check each user model to determine if it's an AR model or a Mongoid model and react accordingly.

To clarify - Mongoid implements the AR <5.1 API in this case, so the AR5.1 check is acting as both "Is this AR or Mongoid?" and "what version of AR is this?" at the same time which is a little confusing.

@carlosantoniodasilva
Copy link
Member

@mbell697 thanks for the information, I think that makes sense. I believe Devise never considered being used in an app that has both ORMs loaded -- even though I think it'd be possible to even use Devise in both at the same time by requiring both ORMs at the initializer.

The better approach would be to check each user model to determine if it's an AR model or a Mongoid model and react accordingly.

Sounds like this could work yeah, I will see if I can look into some changes there. I want to get Devise running tests with latest versions of Mongo as well at some point.

carlosantoniodasilva added a commit that referenced this issue Mar 23, 2023
Devise is able to work with a specific ORM, either Active Record or
Mongoid, but nothing stops apps from using multiple ORMs within the same
application -- they just need to pick one to use with Devise. That's
generally determined by the require that is added to the Devise
initializer, that will load up either ORM's extensions so you can call
things like `devise` on your model to set it up.

However, some conditional logic in Devise, more specifically around
dirty tracking, was only considering having Active Record loaded up
after a certain version, to determine which methods to call in parts of
the implementation. In a previous change we refactored all that dirty
tracking code into this `OrmDirtyTracking` module to make it easier to
view all the methods that were being conditionally called, and now we're
repurposing this into a more generic `Orm` module (that's nodoc'ed by
default) so that upon including it, we can conditionally include the
proper dirty tracking extensions but also check whether the including
model is really Active Record or not, so we can trigger the correct
dirty tracking behavior for Mongoid as well if both are loaded on the
same app, whereas previously the Mongoid behavior would always use the
new Active Record behavior, but support may differ.

While we are also working to ensure the latest versions of Mongoid are
fully running with Devise, this should improve the situation by giving
apps with multiple ORMs loaded a chance to rely on some of these Devise
bits of functionality better now that weren't working properly before
without some monkey-patching on their end.

Closes #5539
Closes #4542
@carlosantoniodasilva
Copy link
Member

Okay, I have a possible fix for this scenario with multiple ORMs loaded in #5576, and I'm also working to reenable Mongoid tests in #5568 (it's all green up to Mongoid 7.x, but Mongoid 8 will need some additional work as it apparently introduced changes to dirty tracking as well)

That should be enough to fix these issues I hope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.