devise-multi_email_revived 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/CHANGELOG.md +38 -0
  6. data/CODE_OF_CONDUCT.md +13 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +199 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +7 -0
  13. data/devise-multi-email.code-workspace +8 -0
  14. data/devise-multi_email.gemspec +31 -0
  15. data/examples/rails5_app/.gitignore +21 -0
  16. data/examples/rails5_app/Gemfile +55 -0
  17. data/examples/rails5_app/Gemfile.lock +189 -0
  18. data/examples/rails5_app/README.md +24 -0
  19. data/examples/rails5_app/Rakefile +6 -0
  20. data/examples/rails5_app/app/assets/config/manifest.js +3 -0
  21. data/examples/rails5_app/app/assets/images/.keep +0 -0
  22. data/examples/rails5_app/app/assets/javascripts/application.js +16 -0
  23. data/examples/rails5_app/app/assets/javascripts/cable.js +13 -0
  24. data/examples/rails5_app/app/assets/javascripts/channels/.keep +0 -0
  25. data/examples/rails5_app/app/assets/stylesheets/application.css +15 -0
  26. data/examples/rails5_app/app/channels/application_cable/channel.rb +4 -0
  27. data/examples/rails5_app/app/channels/application_cable/connection.rb +4 -0
  28. data/examples/rails5_app/app/controllers/application_controller.rb +3 -0
  29. data/examples/rails5_app/app/controllers/concerns/.keep +0 -0
  30. data/examples/rails5_app/app/helpers/application_helper.rb +2 -0
  31. data/examples/rails5_app/app/jobs/application_job.rb +2 -0
  32. data/examples/rails5_app/app/mailers/application_mailer.rb +4 -0
  33. data/examples/rails5_app/app/models/application_record.rb +3 -0
  34. data/examples/rails5_app/app/models/concerns/.keep +0 -0
  35. data/examples/rails5_app/app/models/email.rb +5 -0
  36. data/examples/rails5_app/app/models/user.rb +9 -0
  37. data/examples/rails5_app/app/views/layouts/application.html.erb +14 -0
  38. data/examples/rails5_app/app/views/layouts/mailer.html.erb +13 -0
  39. data/examples/rails5_app/app/views/layouts/mailer.text.erb +1 -0
  40. data/examples/rails5_app/bin/bundle +3 -0
  41. data/examples/rails5_app/bin/rails +4 -0
  42. data/examples/rails5_app/bin/rake +4 -0
  43. data/examples/rails5_app/bin/setup +34 -0
  44. data/examples/rails5_app/bin/update +29 -0
  45. data/examples/rails5_app/config/application.rb +15 -0
  46. data/examples/rails5_app/config/boot.rb +3 -0
  47. data/examples/rails5_app/config/cable.yml +9 -0
  48. data/examples/rails5_app/config/database.yml +25 -0
  49. data/examples/rails5_app/config/environment.rb +5 -0
  50. data/examples/rails5_app/config/environments/development.rb +56 -0
  51. data/examples/rails5_app/config/environments/production.rb +86 -0
  52. data/examples/rails5_app/config/environments/test.rb +42 -0
  53. data/examples/rails5_app/config/initializers/application_controller_renderer.rb +6 -0
  54. data/examples/rails5_app/config/initializers/assets.rb +11 -0
  55. data/examples/rails5_app/config/initializers/backtrace_silencers.rb +7 -0
  56. data/examples/rails5_app/config/initializers/cookies_serializer.rb +5 -0
  57. data/examples/rails5_app/config/initializers/devise.rb +274 -0
  58. data/examples/rails5_app/config/initializers/filter_parameter_logging.rb +4 -0
  59. data/examples/rails5_app/config/initializers/inflections.rb +16 -0
  60. data/examples/rails5_app/config/initializers/mime_types.rb +4 -0
  61. data/examples/rails5_app/config/initializers/new_framework_defaults.rb +24 -0
  62. data/examples/rails5_app/config/initializers/session_store.rb +3 -0
  63. data/examples/rails5_app/config/initializers/wrap_parameters.rb +14 -0
  64. data/examples/rails5_app/config/locales/devise.en.yml +62 -0
  65. data/examples/rails5_app/config/locales/en.yml +23 -0
  66. data/examples/rails5_app/config/puma.rb +47 -0
  67. data/examples/rails5_app/config/routes.rb +4 -0
  68. data/examples/rails5_app/config/secrets.yml +22 -0
  69. data/examples/rails5_app/config/spring.rb +6 -0
  70. data/examples/rails5_app/config.ru +5 -0
  71. data/examples/rails5_app/db/migrate/20170307140813_devise_create_users.rb +49 -0
  72. data/examples/rails5_app/db/migrate/20170307145547_add_password_salt_to_users.rb +5 -0
  73. data/examples/rails5_app/db/schema.rb +44 -0
  74. data/examples/rails5_app/db/seeds.rb +7 -0
  75. data/examples/rails5_app/lib/assets/.keep +0 -0
  76. data/examples/rails5_app/lib/tasks/.keep +0 -0
  77. data/examples/rails5_app/log/.keep +0 -0
  78. data/examples/rails5_app/public/404.html +67 -0
  79. data/examples/rails5_app/public/422.html +67 -0
  80. data/examples/rails5_app/public/500.html +66 -0
  81. data/examples/rails5_app/public/apple-touch-icon-precomposed.png +0 -0
  82. data/examples/rails5_app/public/apple-touch-icon.png +0 -0
  83. data/examples/rails5_app/public/favicon.ico +0 -0
  84. data/examples/rails5_app/public/robots.txt +5 -0
  85. data/examples/rails5_app/test/controllers/.keep +0 -0
  86. data/examples/rails5_app/test/fixtures/.keep +0 -0
  87. data/examples/rails5_app/test/fixtures/files/.keep +0 -0
  88. data/examples/rails5_app/test/helpers/.keep +0 -0
  89. data/examples/rails5_app/test/integration/.keep +0 -0
  90. data/examples/rails5_app/test/mailers/.keep +0 -0
  91. data/examples/rails5_app/test/models/.keep +0 -0
  92. data/examples/rails5_app/test/test_helper.rb +10 -0
  93. data/examples/rails5_app/tmp/.keep +0 -0
  94. data/examples/rails5_app/vendor/assets/javascripts/.keep +0 -0
  95. data/examples/rails5_app/vendor/assets/stylesheets/.keep +0 -0
  96. data/gemfiles/rails_5_1.gemfile +5 -0
  97. data/gemfiles/rails_5_2.gemfile +5 -0
  98. data/gemfiles/rails_6_0.gemfile +5 -0
  99. data/lib/devise/multi-email/association_manager.rb +43 -0
  100. data/lib/devise/multi-email/email_model_extensions.rb +20 -0
  101. data/lib/devise/multi-email/email_model_manager.rb +16 -0
  102. data/lib/devise/multi-email/models/authenticatable.rb +96 -0
  103. data/lib/devise/multi-email/models/confirmable.rb +108 -0
  104. data/lib/devise/multi-email/models/validatable.rb +107 -0
  105. data/lib/devise/multi-email/parent_model_extensions.rb +28 -0
  106. data/lib/devise/multi-email/parent_model_manager.rb +90 -0
  107. data/lib/devise/multi-email/version.rb +5 -0
  108. data/lib/devise/multi_email/association_manager.rb +43 -0
  109. data/lib/devise/multi_email/email_model_extensions.rb +20 -0
  110. data/lib/devise/multi_email/email_model_manager.rb +16 -0
  111. data/lib/devise/multi_email/models/authenticatable.rb +96 -0
  112. data/lib/devise/multi_email/models/confirmable.rb +108 -0
  113. data/lib/devise/multi_email/models/validatable.rb +107 -0
  114. data/lib/devise/multi_email/parent_model_extensions.rb +28 -0
  115. data/lib/devise/multi_email/parent_model_manager.rb +90 -0
  116. data/lib/devise/multi_email/version.rb +5 -0
  117. data/lib/devise/multi_email.rb +60 -0
  118. data/mise.toml +2 -0
  119. metadata +262 -0
@@ -0,0 +1,28 @@
1
+ require 'devise/multi_email/email_model_extensions'
2
+ require 'devise/multi_email/association_manager'
3
+ require 'devise/multi_email/parent_model_manager'
4
+
5
+ module Devise
6
+ module MultiEmail
7
+ module ParentModelExtensions
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ multi_email_association.configure_autosave!
12
+ multi_email_association.include_module(EmailModelExtensions)
13
+ end
14
+
15
+ delegate Devise::MultiEmail.primary_email_method_name, to: :multi_email, allow_nil: false
16
+
17
+ def multi_email
18
+ @multi_email ||= ParentModelManager.new(self)
19
+ end
20
+
21
+ module ClassMethods
22
+ def multi_email_association
23
+ @multi_email ||= AssociationManager.new(self, Devise::MultiEmail.emails_association_name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,90 @@
1
+ require 'devise/multi_email/email_model_extensions'
2
+
3
+ module Devise
4
+ module MultiEmail
5
+ class ParentModelManager
6
+
7
+ def initialize(parent_record)
8
+ @parent_record = parent_record
9
+ end
10
+
11
+ def current_email_record
12
+ login_email_record || primary_email_record
13
+ end
14
+
15
+ def login_email_record
16
+ if @parent_record.current_login_email.present?
17
+ formatted_email = format_email(@parent_record.current_login_email)
18
+ filtered_emails.find { |item| item.email == formatted_email }
19
+ end
20
+ end
21
+
22
+ # Gets the primary email record.
23
+ def primary_email_record
24
+ filtered_emails.find(&:primary?)
25
+ end
26
+ alias_method Devise::MultiEmail.primary_email_method_name, :primary_email_record
27
+
28
+ # :allow_unconfirmed option sets this email record to primary
29
+ # :skip_confirmations option confirms this email record (without saving)
30
+ # @see `set_primary_record_to`
31
+ def change_primary_email_to(new_email, options = {})
32
+ # mark none as primary when set to nil
33
+ if new_email.nil?
34
+ filtered_emails.each { |item| item.primary = false }
35
+
36
+ # select or build an email record
37
+ else
38
+ record = find_or_build_for_email(new_email)
39
+
40
+ if record.try(:confirmed?) || primary_email_record.nil? || options[:allow_unconfirmed]
41
+ set_primary_record_to(record, options)
42
+ end
43
+ end
44
+
45
+ record
46
+ end
47
+
48
+ # Use Devise formatting settings for emails
49
+ def format_email(email)
50
+ @parent_record.class.__send__(:devise_parameter_filter).filter(email: email)[:email]
51
+ end
52
+
53
+ def find_or_build_for_email(email)
54
+ formatted_email = format_email(email)
55
+ record = filtered_emails.find { |item| item.email == formatted_email }
56
+ record || emails.build(email: formatted_email)
57
+ end
58
+
59
+ def emails
60
+ @parent_record.__send__(@parent_record.class.multi_email_association.name)
61
+ end
62
+
63
+ # Gets the email records that have not been deleted
64
+ def filtered_emails(options = {})
65
+ emails.to_a.reject(&:destroyed?).reject(&:marked_for_destruction?)
66
+ end
67
+
68
+ def confirmed_emails
69
+ filtered_emails.select { |record| record.try(:confirmed?) }
70
+ end
71
+
72
+ def unconfirmed_emails
73
+ filtered_emails.reject { |record| record.try(:confirmed?) }
74
+ end
75
+
76
+ protected
77
+
78
+ # :skip_confirmations option confirms this email record (without saving)
79
+ def set_primary_record_to(record, options = {})
80
+ # Toggle primary flag for all emails
81
+ filtered_emails.each { |other| other.primary = (other.email == record.email) }
82
+
83
+ if options[:skip_confirmations]
84
+ record.try(:skip_confirmation!)
85
+ record.try(:skip_reconfirmation!)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,5 @@
1
+ module Devise
2
+ module MultiEmail
3
+ VERSION = "3.0.1"
4
+ end
5
+ end
@@ -0,0 +1,43 @@
1
+
2
+ module Devise
3
+ module MultiEmail
4
+ class AssociationManager
5
+
6
+ attr_reader :name
7
+
8
+ def initialize(klass, association_name)
9
+ @klass = klass
10
+ @name = association_name
11
+ end
12
+
13
+ def include_module(mod)
14
+ model_class.__send__ :include, mod
15
+ end
16
+
17
+ # Specify a block with alternative behavior which should be
18
+ # run when `autosave` is not enabled.
19
+ def configure_autosave!(&block)
20
+ unless autosave_enabled?
21
+ if Devise::MultiEmail.autosave_emails?
22
+ reflection.autosave = true
23
+ else
24
+ yield if block_given?
25
+ end
26
+ end
27
+ end
28
+
29
+ def autosave_enabled?
30
+ reflection.options[:autosave] == true
31
+ end
32
+
33
+ def model_class
34
+ @model_class ||= reflection.class_name.constantize
35
+ end
36
+
37
+ def reflection
38
+ @reflection ||= @klass.reflect_on_association(name) ||
39
+ raise("#{@klass}##{name} association not found: It might be because your declaration is after `devise :multi_email_confirmable`.")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ require 'devise/multi_email/association_manager'
2
+ require 'devise/multi_email/email_model_manager'
3
+
4
+ module Devise
5
+ module MultiEmail
6
+ module EmailModelExtensions
7
+ extend ActiveSupport::Concern
8
+
9
+ def multi_email
10
+ @multi_email ||= EmailModelManager.new(self)
11
+ end
12
+
13
+ module ClassMethods
14
+ def multi_email_association
15
+ @multi_email ||= AssociationManager.new(self, Devise::MultiEmail.parent_association_name)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ require 'devise/multi_email/email_model_extensions'
2
+
3
+ module Devise
4
+ module MultiEmail
5
+ class EmailModelManager
6
+
7
+ def initialize(email_record)
8
+ @email_record = email_record
9
+ end
10
+
11
+ def parent
12
+ @email_record.__send__(@email_record.class.multi_email_association.name)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,96 @@
1
+ require 'devise/multi_email/parent_model_extensions'
2
+
3
+ module Devise
4
+ module Models
5
+ module EmailAuthenticatable
6
+ def devise_scope
7
+ self.class.multi_email_association.model_class
8
+ end
9
+ end
10
+
11
+ module MultiEmailAuthenticatable
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ include Devise::MultiEmail::ParentModelExtensions
16
+
17
+ attr_accessor :current_login_email
18
+
19
+ devise :database_authenticatable
20
+
21
+ include AuthenticatableExtensions
22
+ end
23
+
24
+ def self.required_fields(klass)
25
+ []
26
+ end
27
+
28
+ module AuthenticatableExtensions
29
+ extend ActiveSupport::Concern
30
+
31
+ included do
32
+ multi_email_association.configure_autosave!{ include AuthenticatableAutosaveExtensions }
33
+ multi_email_association.include_module(EmailAuthenticatable)
34
+ end
35
+
36
+ delegate :skip_confirmation!, to: Devise::MultiEmail.primary_email_method_name, allow_nil: false
37
+
38
+ # Gets the primary email address of the user.
39
+ def email
40
+ multi_email.primary_email_record.try(:email)
41
+ end
42
+
43
+ # Sets the default email address of the user.
44
+ def email=(new_email)
45
+ multi_email.change_primary_email_to(new_email, allow_unconfirmed: true)
46
+ end
47
+ end
48
+
49
+ module AuthenticatableAutosaveExtensions
50
+ extend ActiveSupport::Concern
51
+
52
+ included do
53
+ # Toggle `primary` value for all emails if `autosave` is not on
54
+ after_save do
55
+ multi_email.filtered_emails.each do |email|
56
+ # update value in database without persisting any other changes
57
+ email.save if email.changes.key?(:primary)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ module ClassMethods
64
+ def find_first_by_auth_conditions(tainted_conditions, opts = {})
65
+ filtered_conditions = devise_parameter_filter.filter(tainted_conditions.dup)
66
+ criteria = filtered_conditions.extract!(:email, :unconfirmed_email)
67
+
68
+ if criteria.keys.any?
69
+ conditions = filtered_conditions.to_h.merge(opts).
70
+ reverse_merge(build_conditions(criteria))
71
+
72
+ resource = joins(multi_email_association.name).find_by(conditions)
73
+ resource.current_login_email = criteria.values.first if resource
74
+ resource
75
+ else
76
+ super(tainted_conditions, opts)
77
+ end
78
+ end
79
+
80
+ def find_by_email(email)
81
+ joins(multi_email_association.name).where(build_conditions email: email).first
82
+ end
83
+
84
+ def build_conditions(criteria)
85
+ criteria = devise_parameter_filter.filter(criteria)
86
+ # match the primary email record if the `unconfirmed_email` column is specified
87
+ if Devise::MultiEmail.only_login_with_primary_email? || criteria[:unconfirmed_email]
88
+ criteria.merge!(primary: true)
89
+ end
90
+
91
+ { multi_email_association.reflection.table_name.to_sym => criteria }
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,108 @@
1
+ require 'devise/multi_email/parent_model_extensions'
2
+
3
+ module Devise
4
+ module Models
5
+ module EmailConfirmable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ devise :confirmable
10
+
11
+ include ConfirmableExtensions
12
+ end
13
+
14
+ module ConfirmableExtensions
15
+ def confirmation_period_valid?
16
+ primary? ? super : false
17
+ end
18
+ end
19
+ end
20
+
21
+ module MultiEmailConfirmable
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ include Devise::MultiEmail::ParentModelExtensions
26
+
27
+ devise :confirmable
28
+
29
+ include ConfirmableExtensions
30
+ end
31
+
32
+ def self.required_fields(klass)
33
+ []
34
+ end
35
+
36
+ module ConfirmableExtensions
37
+ extend ActiveSupport::Concern
38
+
39
+ included do
40
+ multi_email_association.include_module(EmailConfirmable)
41
+ end
42
+
43
+ # delegate before creating overriding methods
44
+ delegate :skip_confirmation!, :skip_confirmation_notification!, :skip_reconfirmation!, :confirmation_required?,
45
+ :confirmation_token, :confirmed_at, :confirmed_at=, :confirmation_sent_at, :confirm, :confirmed?, :unconfirmed_email,
46
+ :reconfirmation_required?, :pending_reconfirmation?, to: Devise::MultiEmail.primary_email_method_name, allow_nil: true
47
+
48
+ # In case email updates are being postponed, don't change anything
49
+ # when the postpone feature tries to switch things back
50
+ def email=(new_email)
51
+ multi_email.change_primary_email_to(new_email, allow_unconfirmed: unconfirmed_access_possible?)
52
+ end
53
+
54
+ # This need to be forwarded to the email that the user logged in with
55
+ def active_for_authentication?
56
+ login_email = multi_email.login_email_record
57
+
58
+ if login_email && !login_email.primary?
59
+ super && login_email.active_for_authentication?
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ # Shows email not confirmed instead of account inactive when the email that user used to login is not confirmed
66
+ def inactive_message
67
+ login_email = multi_email.login_email_record
68
+
69
+ if login_email && !login_email.primary? && !login_email.confirmed?
70
+ :unconfirmed
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ protected
77
+
78
+ # Overrides Devise::Models::Confirmable#postpone_email_change?
79
+ def postpone_email_change?
80
+ false
81
+ end
82
+
83
+ # Email should handle the confirmation token.
84
+ def generate_confirmation_token
85
+ end
86
+
87
+ # Email will send reconfirmation instructions.
88
+ def send_reconfirmation_instructions
89
+ end
90
+
91
+ # Email will send confirmation instructions.
92
+ def send_on_create_confirmation_instructions
93
+ end
94
+
95
+ private
96
+
97
+ def unconfirmed_access_possible?
98
+ Devise.allow_unconfirmed_access_for.nil? || \
99
+ Devise.allow_unconfirmed_access_for > 0.days
100
+ end
101
+
102
+ module ClassMethods
103
+ delegate :confirm_by_token, :send_confirmation_instructions, to: 'multi_email_association.model_class', allow_nil: false
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,107 @@
1
+ require 'devise/multi_email/parent_model_extensions'
2
+
3
+ module Devise
4
+ module Models
5
+ module EmailValidatable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ validates_presence_of :email, if: :email_required?
10
+ validates_uniqueness_of :email, allow_blank: true, case_sensitive: true, if: :will_save_change_to_email?
11
+ validates_format_of :email, with: email_regexp, allow_blank: true, if: :will_save_change_to_email?
12
+ end
13
+
14
+ def email_required?
15
+ true
16
+ end
17
+
18
+ module ClassMethods
19
+ Devise::Models.config(self, :email_regexp)
20
+ end
21
+ end
22
+
23
+ module MultiEmailValidatable
24
+ extend ActiveSupport::Concern
25
+
26
+ included do
27
+ include Devise::MultiEmail::ParentModelExtensions
28
+
29
+ assert_validations_api!(self)
30
+
31
+ validates_presence_of :email, if: :email_required?
32
+
33
+ validates_presence_of :password, if: :password_required?
34
+ validates_confirmation_of :password, if: :password_required?
35
+ validates_length_of :password, within: password_length, allow_blank: true
36
+
37
+ after_validation :propagate_email_errors
38
+
39
+ multi_email_association.include_module(EmailValidatable)
40
+
41
+ devise_modules << :validatable
42
+ end
43
+
44
+ def self.required_fields(klass)
45
+ []
46
+ end
47
+
48
+ protected
49
+
50
+ # Same as Devise::Models::Validatable#password_required?
51
+ def password_required?
52
+ !persisted? || !password.nil? || !password_confirmation.nil?
53
+ end
54
+
55
+ # Same as Devise::Models::Validatable#email_required?
56
+ def email_required?
57
+ true
58
+ end
59
+
60
+ private
61
+
62
+ def propagate_email_errors
63
+ association_name = self.class.multi_email_association.name
64
+ email_error_key = errors_attribute_names.detect do |key|
65
+ [association_name.to_s, "#{association_name}.email"].include?(key.to_s)
66
+ end
67
+ return unless email_error_key.present?
68
+
69
+ email_errors =
70
+ if errors.respond_to?(:details)
71
+ errors
72
+ .details[email_error_key]
73
+ .map { |e| e[:error] }
74
+ .zip(errors.delete(email_error_key) || [])
75
+ else
76
+ errors.delete(email_error_key)
77
+ end
78
+
79
+ email_errors.each do |type, message|
80
+ errors.add(:email, type, message: message)
81
+ end
82
+ end
83
+
84
+ def errors_attribute_names
85
+ errors.respond_to?(:attribute_names) ? errors.attribute_names : errors.keys
86
+ end
87
+
88
+ module ClassMethods
89
+
90
+ # All validations used by this module.
91
+ VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
92
+ :validates_confirmation_of, :validates_length_of].freeze
93
+
94
+ def assert_validations_api!(base) #:nodoc:
95
+ unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
96
+
97
+ unless unavailable_validations.empty?
98
+ raise "Could not use :validatable module since #{base} does not respond " <<
99
+ "to the following methods: #{unavailable_validations.to_sentence}."
100
+ end
101
+ end
102
+
103
+ Devise::Models.config(self, :password_length)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ require 'devise/multi_email/email_model_extensions'
2
+ require 'devise/multi_email/association_manager'
3
+ require 'devise/multi_email/parent_model_manager'
4
+
5
+ module Devise
6
+ module MultiEmail
7
+ module ParentModelExtensions
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ multi_email_association.configure_autosave!
12
+ multi_email_association.include_module(EmailModelExtensions)
13
+ end
14
+
15
+ delegate Devise::MultiEmail.primary_email_method_name, to: :multi_email, allow_nil: false
16
+
17
+ def multi_email
18
+ @multi_email ||= ParentModelManager.new(self)
19
+ end
20
+
21
+ module ClassMethods
22
+ def multi_email_association
23
+ @multi_email ||= AssociationManager.new(self, Devise::MultiEmail.emails_association_name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,90 @@
1
+ require 'devise/multi_email/email_model_extensions'
2
+
3
+ module Devise
4
+ module MultiEmail
5
+ class ParentModelManager
6
+
7
+ def initialize(parent_record)
8
+ @parent_record = parent_record
9
+ end
10
+
11
+ def current_email_record
12
+ login_email_record || primary_email_record
13
+ end
14
+
15
+ def login_email_record
16
+ if @parent_record.current_login_email.present?
17
+ formatted_email = format_email(@parent_record.current_login_email)
18
+ filtered_emails.find { |item| item.email == formatted_email }
19
+ end
20
+ end
21
+
22
+ # Gets the primary email record.
23
+ def primary_email_record
24
+ filtered_emails.find(&:primary?)
25
+ end
26
+ alias_method Devise::MultiEmail.primary_email_method_name, :primary_email_record
27
+
28
+ # :allow_unconfirmed option sets this email record to primary
29
+ # :skip_confirmations option confirms this email record (without saving)
30
+ # @see `set_primary_record_to`
31
+ def change_primary_email_to(new_email, options = {})
32
+ # mark none as primary when set to nil
33
+ if new_email.nil?
34
+ filtered_emails.each { |item| item.primary = false }
35
+
36
+ # select or build an email record
37
+ else
38
+ record = find_or_build_for_email(new_email)
39
+
40
+ if record.try(:confirmed?) || primary_email_record.nil? || options[:allow_unconfirmed]
41
+ set_primary_record_to(record, options)
42
+ end
43
+ end
44
+
45
+ record
46
+ end
47
+
48
+ # Use Devise formatting settings for emails
49
+ def format_email(email)
50
+ @parent_record.class.__send__(:devise_parameter_filter).filter(email: email)[:email]
51
+ end
52
+
53
+ def find_or_build_for_email(email)
54
+ formatted_email = format_email(email)
55
+ record = filtered_emails.find { |item| item.email == formatted_email }
56
+ record || emails.build(email: formatted_email)
57
+ end
58
+
59
+ def emails
60
+ @parent_record.__send__(@parent_record.class.multi_email_association.name)
61
+ end
62
+
63
+ # Gets the email records that have not been deleted
64
+ def filtered_emails(options = {})
65
+ emails.to_a.reject(&:destroyed?).reject(&:marked_for_destruction?)
66
+ end
67
+
68
+ def confirmed_emails
69
+ filtered_emails.select { |record| record.try(:confirmed?) }
70
+ end
71
+
72
+ def unconfirmed_emails
73
+ filtered_emails.reject { |record| record.try(:confirmed?) }
74
+ end
75
+
76
+ protected
77
+
78
+ # :skip_confirmations option confirms this email record (without saving)
79
+ def set_primary_record_to(record, options = {})
80
+ # Toggle primary flag for all emails
81
+ filtered_emails.each { |other| other.primary = (other.email == record.email) }
82
+
83
+ if options[:skip_confirmations]
84
+ record.try(:skip_confirmation!)
85
+ record.try(:skip_reconfirmation!)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,5 @@
1
+ module Devise
2
+ module MultiEmail
3
+ VERSION = '3.1.0'
4
+ end
5
+ end