Rails5.1.3 + Devise4.3.0でのエラーについて(will_save_change_to_email?)


先日開発中のアプリケーションをRails5.1.3にアップデートしたところ、Deviseの挙動が少しおかしくなったので、それを解消するまでの話です。

## Undefined method `will_save_change_to_email?`
とりあえずアップデートをかけてテストしてみたら以下のようなエラー。

```
undefined method `will_save_change_to_email?' for #<User:0x007fedc1671310>
Did you mean?  will_save_change_to_name?
               will_save_change_to_id?
               will_save_change_to_uid?
               will_save_change_to_token?
               will_save_change_to_attribute?
```


とりあえず[Githubの中身](https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb)を確認

``` devise/lib/devise/models/validatable.rb
module Devise
  module Models
    # Validatable creates all needed validations for a user email and password.
    # It's optional, given you may want to create the validations by yourself.
    # Automatically validate if the email is present, unique and its format is
    # valid. Also tests presence of password, confirmation and length.
    #
    # == Options
    #
    # Validatable adds the following options to devise_for:
    #
    #   * +email_regexp+: the regular expression used to validate e-mails;
    #   * +password_length+: a range expressing password length. Defaults to 8..72.
    #
    module Validatable
      # All validations used by this module.
      VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
                     :validates_confirmation_of, :validates_length_of].freeze

      def self.required_fields(klass)
        []
      end

      def self.included(base)
        base.extend ClassMethods
        assert_validations_api!(base)
        base.class_eval do
          validates_presence_of   :email, if: :email_required?
          if Devise.activerecord51?
            validates_uniqueness_of :email, allow_blank: true, if: :will_save_change_to_email?
            validates_format_of     :email, with: email_regexp, allow_blank: true, if: :will_save_change_to_email?
          else
            validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
            validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?
          end

          validates_presence_of     :password, if: :password_required?
          validates_confirmation_of :password, if: :password_required?
          validates_length_of       :password, within: password_length, allow_blank: true
        end
      end

      def self.assert_validations_api!(base) #:nodoc:
        unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }

        unless unavailable_validations.empty?
          raise "Could not use :validatable module since #{base} does not respond " <<
                "to the following methods: #{unavailable_validations.to_sentence}."
        end
      end

    protected

      # Checks whether a password is needed or not. For validations only.
      # Passwords are always required if it's a new record, or if the password
      # or confirmation are being set somewhere.
      def password_required?
        !persisted? || !password.nil? || !password_confirmation.nil?
      end

      def email_required?
        true
      end

      module ClassMethods
        Devise::Models.config(self, :email_regexp, :password_length)
      end
    end
  end
end
```


``` 抜粋
if Devise.activerecord51?
            validates_uniqueness_of :email, allow_blank: true, if: :will_save_change_to_email?
            validates_format_of     :email, with: email_regexp, allow_blank: true, if: :will_save_change_to_email?
```

ここでActiveRecord5.1だった時の処理が追加されていますね。
ここの条件として利用されているのがwill_save_change_to_email?という関数になります。ですが、どうやらdeviseのレポジトリの中のどこにもその関数が定義されていないようです。

### バグっぽい？(2017/08/08時点)
[Issues](https://github.com/plataformatec/devise/issues/4542)でも指摘されているように、どうやらバグっぽいのですね。自分でこのdeviseを適用しているモデルにこの関数を定義してあげる必要があります。

``` app/models/user.rb
def will_save_change_to_email?
  false
end
```

今回私のアプリケーションではEmailを使っていなかったので、falseに設定しています。Emailを使っているならtrueにするといいでしょう。