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,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/404.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The page you were looking for doesn't exist.</h1>
62
+ <p>You may have mistyped the address or the page may have moved.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
File without changes
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-agent: *
5
+ # Disallow: /
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
7
+ fixtures :all
8
+
9
+ # Add more helper methods to be used by all tests here...
10
+ end
File without changes
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.1.1"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.2.0"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", ">= 6.0.2.1", "< 6.1"
4
+
5
+ gemspec path: "../"
@@ -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