devise-multi_email 1.0.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -1
  3. data/CHANGELOG.md +15 -2
  4. data/Gemfile +2 -9
  5. data/README.md +69 -18
  6. data/devise-multi_email.gemspec +4 -0
  7. data/examples/rails5_app/.gitignore +21 -0
  8. data/examples/rails5_app/Gemfile +55 -0
  9. data/examples/rails5_app/Gemfile.lock +185 -0
  10. data/examples/rails5_app/README.md +24 -0
  11. data/examples/rails5_app/Rakefile +6 -0
  12. data/examples/rails5_app/app/assets/config/manifest.js +3 -0
  13. data/examples/rails5_app/app/assets/images/.keep +0 -0
  14. data/examples/rails5_app/app/assets/javascripts/application.js +16 -0
  15. data/examples/rails5_app/app/assets/javascripts/cable.js +13 -0
  16. data/examples/rails5_app/app/assets/javascripts/channels/.keep +0 -0
  17. data/examples/rails5_app/app/assets/stylesheets/application.css +15 -0
  18. data/examples/rails5_app/app/channels/application_cable/channel.rb +4 -0
  19. data/examples/rails5_app/app/channels/application_cable/connection.rb +4 -0
  20. data/examples/rails5_app/app/controllers/application_controller.rb +3 -0
  21. data/examples/rails5_app/app/controllers/concerns/.keep +0 -0
  22. data/examples/rails5_app/app/helpers/application_helper.rb +2 -0
  23. data/examples/rails5_app/app/jobs/application_job.rb +2 -0
  24. data/examples/rails5_app/app/mailers/application_mailer.rb +4 -0
  25. data/examples/rails5_app/app/models/application_record.rb +3 -0
  26. data/examples/rails5_app/app/models/concerns/.keep +0 -0
  27. data/examples/rails5_app/app/models/email.rb +3 -0
  28. data/examples/rails5_app/app/models/user.rb +9 -0
  29. data/examples/rails5_app/app/views/layouts/application.html.erb +14 -0
  30. data/examples/rails5_app/app/views/layouts/mailer.html.erb +13 -0
  31. data/examples/rails5_app/app/views/layouts/mailer.text.erb +1 -0
  32. data/examples/rails5_app/bin/bundle +3 -0
  33. data/examples/rails5_app/bin/rails +4 -0
  34. data/examples/rails5_app/bin/rake +4 -0
  35. data/examples/rails5_app/bin/setup +34 -0
  36. data/examples/rails5_app/bin/update +29 -0
  37. data/examples/rails5_app/config/application.rb +15 -0
  38. data/examples/rails5_app/config/boot.rb +3 -0
  39. data/examples/rails5_app/config/cable.yml +9 -0
  40. data/examples/rails5_app/config/database.yml +25 -0
  41. data/examples/rails5_app/config/environment.rb +5 -0
  42. data/examples/rails5_app/config/environments/development.rb +56 -0
  43. data/examples/rails5_app/config/environments/production.rb +86 -0
  44. data/examples/rails5_app/config/environments/test.rb +42 -0
  45. data/examples/rails5_app/config/initializers/application_controller_renderer.rb +6 -0
  46. data/examples/rails5_app/config/initializers/assets.rb +11 -0
  47. data/examples/rails5_app/config/initializers/backtrace_silencers.rb +7 -0
  48. data/examples/rails5_app/config/initializers/cookies_serializer.rb +5 -0
  49. data/examples/rails5_app/config/initializers/devise.rb +274 -0
  50. data/examples/rails5_app/config/initializers/filter_parameter_logging.rb +4 -0
  51. data/examples/rails5_app/config/initializers/inflections.rb +16 -0
  52. data/examples/rails5_app/config/initializers/mime_types.rb +4 -0
  53. data/examples/rails5_app/config/initializers/new_framework_defaults.rb +24 -0
  54. data/examples/rails5_app/config/initializers/session_store.rb +3 -0
  55. data/examples/rails5_app/config/initializers/wrap_parameters.rb +14 -0
  56. data/examples/rails5_app/config/locales/devise.en.yml +62 -0
  57. data/examples/rails5_app/config/locales/en.yml +23 -0
  58. data/examples/rails5_app/config/puma.rb +47 -0
  59. data/examples/rails5_app/config/routes.rb +4 -0
  60. data/examples/rails5_app/config/secrets.yml +22 -0
  61. data/examples/rails5_app/config/spring.rb +6 -0
  62. data/examples/rails5_app/config.ru +5 -0
  63. data/examples/rails5_app/db/migrate/20170307140813_devise_create_users.rb +49 -0
  64. data/examples/rails5_app/db/migrate/20170307145547_add_password_salt_to_users.rb +5 -0
  65. data/examples/rails5_app/db/schema.rb +44 -0
  66. data/examples/rails5_app/db/seeds.rb +7 -0
  67. data/examples/rails5_app/lib/assets/.keep +0 -0
  68. data/examples/rails5_app/lib/tasks/.keep +0 -0
  69. data/examples/rails5_app/log/.keep +0 -0
  70. data/examples/rails5_app/public/404.html +67 -0
  71. data/examples/rails5_app/public/422.html +67 -0
  72. data/examples/rails5_app/public/500.html +66 -0
  73. data/examples/rails5_app/public/apple-touch-icon-precomposed.png +0 -0
  74. data/examples/rails5_app/public/apple-touch-icon.png +0 -0
  75. data/examples/rails5_app/public/favicon.ico +0 -0
  76. data/examples/rails5_app/public/robots.txt +5 -0
  77. data/examples/rails5_app/test/controllers/.keep +0 -0
  78. data/examples/rails5_app/test/fixtures/.keep +0 -0
  79. data/examples/rails5_app/test/fixtures/files/.keep +0 -0
  80. data/examples/rails5_app/test/helpers/.keep +0 -0
  81. data/examples/rails5_app/test/integration/.keep +0 -0
  82. data/examples/rails5_app/test/mailers/.keep +0 -0
  83. data/examples/rails5_app/test/models/.keep +0 -0
  84. data/examples/rails5_app/test/test_helper.rb +10 -0
  85. data/examples/rails5_app/tmp/.keep +0 -0
  86. data/examples/rails5_app/vendor/assets/javascripts/.keep +0 -0
  87. data/examples/rails5_app/vendor/assets/stylesheets/.keep +0 -0
  88. data/gemfiles/rails_4_2.gemfile +5 -0
  89. data/gemfiles/rails_5_0.gemfile +5 -0
  90. data/gemfiles/rails_5_1.gemfile +5 -0
  91. data/lib/devise/multi_email/association_manager.rb +30 -0
  92. data/lib/devise/multi_email/email_model_extensions.rb +20 -0
  93. data/lib/devise/multi_email/email_model_manager.rb +18 -0
  94. data/lib/devise/multi_email/models/authenticatable.rb +23 -44
  95. data/lib/devise/multi_email/models/confirmable.rb +20 -21
  96. data/lib/devise/multi_email/models/validatable.rb +42 -38
  97. data/lib/devise/multi_email/parent_model_extensions.rb +27 -0
  98. data/lib/devise/multi_email/parent_model_manager.rb +55 -0
  99. data/lib/devise/multi_email/version.rb +1 -1
  100. data/lib/devise/multi_email.rb +32 -0
  101. metadata +147 -2
@@ -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", "~> 4.2.8"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.0.3"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", "~> 5.1.1"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,30 @@
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
+ def model_class
18
+ unless reflection
19
+ raise "#{@klass}##{name} association not found: It might be because your declaration is after `devise :multi_email_confirmable`."
20
+ end
21
+
22
+ @model_class ||= reflection.class_name.constantize
23
+ end
24
+
25
+ def reflection
26
+ @reflection ||= @klass.reflect_on_association(name)
27
+ end
28
+ end
29
+ end
30
+ 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,18 @@
1
+ require 'devise/multi_email/email_model_extensions'
2
+
3
+ module Devise
4
+ module MultiEmail
5
+ class EmailModelManager
6
+
7
+ attr_reader :record
8
+
9
+ def initialize(record)
10
+ @record = record
11
+ end
12
+
13
+ def parent
14
+ record.__send__(record.class.multi_email_association.name)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,59 +1,47 @@
1
+ require 'devise/multi_email/parent_model_extensions'
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module EmailAuthenticatable
4
- USER_ASSOCIATION = :user
5
-
6
6
  def devise_scope
7
- user_association = self.class.reflect_on_association(USER_ASSOCIATION)
8
- if user_association
9
- user_association.class_name.constantize
10
- else
11
- raise "#{self.class.name}: Association :#{USER_ASSOCIATION} not found: Have you declared that ?"
12
- end
7
+ self.class.multi_email_association.model_class
13
8
  end
14
9
  end
15
10
 
16
11
  module MultiEmailAuthenticatable
17
12
  extend ActiveSupport::Concern
18
- EMAILS_ASSOCIATION = :emails
19
13
 
20
14
  included do
21
- devise :database_authenticatable
15
+ include Devise::MultiEmail::ParentModelExtensions
22
16
 
23
17
  attr_accessor :current_login_email
24
18
 
25
- email_class.send :include, EmailAuthenticatable
19
+ devise :database_authenticatable
20
+
21
+ include AuthenticatableExtensions
26
22
  end
27
23
 
28
24
  def self.required_fields(klass)
29
25
  []
30
26
  end
31
27
 
32
- # Gets the primary email record.
33
- def primary_email_record
34
- valid_emails = emails.each.select do |email_record|
35
- !email_record.destroyed? && !email_record.marked_for_destruction?
28
+ module AuthenticatableExtensions
29
+ extend ActiveSupport::Concern
30
+
31
+ included do
32
+ multi_email_association.include_module(EmailAuthenticatable)
36
33
  end
37
34
 
38
- result = valid_emails.find(&:primary?)
39
- result ||= valid_emails.first
40
- result
41
- end
35
+ delegate :skip_confirmation!, to: Devise::MultiEmail.primary_email_method_name, allow_nil: false
42
36
 
43
- # Gets the primary email address of the user.
44
- def email
45
- primary_email_record.try(:email)
46
- end
37
+ # Gets the primary email address of the user.
38
+ def email
39
+ multi_email.primary_email.try(:email)
40
+ end
47
41
 
48
- # Sets the default email address of the user.
49
- def email=(email)
50
- record = primary_email_record
51
- if email
52
- record ||= emails.build
53
- record.email = email
54
- record.primary = true
55
- elsif email.nil? && record
56
- record.mark_for_destruction
42
+ # Sets the default email address of the user.
43
+ def email=(new_email)
44
+ multi_email.change_primary_email_to(new_email)
57
45
  end
58
46
  end
59
47
 
@@ -64,9 +52,9 @@ module Devise
64
52
 
65
53
  if email && email.is_a?(String)
66
54
  conditions = filtered_conditions.to_h.merge(opts).
67
- reverse_merge(emails: { email: email })
55
+ reverse_merge(multi_email_association.reflection.table_name => { email: email })
68
56
 
69
- resource = joins(:emails).find_by(conditions)
57
+ resource = joins(multi_email_association.name).find_by(conditions)
70
58
  resource.current_login_email = email if resource.respond_to?(:current_login_email=)
71
59
  resource
72
60
  else
@@ -74,17 +62,8 @@ module Devise
74
62
  end
75
63
  end
76
64
 
77
- def email_class
78
- email_association = reflect_on_association(EMAILS_ASSOCIATION)
79
- if email_association
80
- email_association.class_name.constantize
81
- else
82
- raise "#{self.class.name}: Association :#{EMAILS_ASSOCIATION} not found: It might because your declaration is after `devise :multi_email_confirmable`."
83
- end
84
- end
85
-
86
65
  def find_by_email(email)
87
- joins(:emails).where(emails: {email: email.downcase}).first
66
+ joins(multi_email_association.name).where(multi_email_association.reflection.table_name => { email: email.downcase }).first
88
67
  end
89
68
  end
90
69
  end
@@ -1,3 +1,5 @@
1
+ require 'devise/multi_email/parent_model_extensions'
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module EmailConfirmable
@@ -11,7 +13,7 @@ module Devise
11
13
 
12
14
  module ClassReplacementMethods
13
15
  def allow_unconfirmed_access_for
14
- 0.day
16
+ 0.days
15
17
  end
16
18
  end
17
19
  end
@@ -20,18 +22,25 @@ module Devise
20
22
  extend ActiveSupport::Concern
21
23
 
22
24
  included do
25
+ include Devise::MultiEmail::ParentModelExtensions
26
+
23
27
  devise :confirmable
24
- include InstanceReplacementMethods
25
- extend ClassReplacementMethods
26
28
 
27
- email_class.send :include, EmailConfirmable
29
+ include ConfirmableExtensions
28
30
  end
29
31
 
30
32
  def self.required_fields(klass)
31
33
  []
32
34
  end
33
35
 
34
- module InstanceReplacementMethods
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
35
44
  delegate :skip_confirmation!, :skip_confirmation_notification!, :skip_reconfirmation!, :confirmation_required?,
36
45
  :confirmation_token, :confirmed_at, :confirmation_sent_at, :confirm, :confirmed?, :unconfirmed_email,
37
46
  :reconfirmation_required?, :pending_reconfirmation?, to: :primary_email_record, allow_nil: true
@@ -58,7 +67,7 @@ module Devise
58
67
  end
59
68
  end
60
69
 
61
- protected
70
+ protected
62
71
 
63
72
  # Overrides Devise::Models::Confirmable#postpone_email_change?
64
73
  def postpone_email_change?
@@ -77,28 +86,18 @@ module Devise
77
86
  def send_on_create_confirmation_instructions
78
87
  end
79
88
 
80
- private
89
+ private
81
90
 
82
91
  def current_login_email_record
83
92
  if respond_to?(:current_login_email) && current_login_email
84
- send(self.class::EMAILS_ASSOCIATION).find_by(email: current_login_email)
93
+ multi_email.emails.find_by(email: current_login_email)
85
94
  end
86
95
  end
87
- end
88
-
89
- module ClassReplacementMethods
90
- # Overrides Devise::Models::Confirmable.confirm_by_token
91
- # Forward the logic to email.
92
- def confirm_by_token(token)
93
- email_class.confirm_by_token(token)
94
- end
95
96
 
96
- # Overrides Devise::Models::Confirmable.send_confirmation_instructions
97
- # Forward the logic to email.
98
- def send_confirmation_instructions(params)
99
- email_class.send_confirmation_instructions(params)
97
+ module ClassMethods
98
+ delegate :confirm_by_token, :send_confirmation_instructions, to: 'multi_email_association.model_class', allow_nil: false
100
99
  end
101
100
  end
102
101
  end
103
102
  end
104
- end
103
+ end
@@ -1,14 +1,14 @@
1
+ require 'devise/multi_email/parent_model_extensions'
2
+
1
3
  module Devise
2
4
  module Models
3
5
  module EmailValidatable
4
- def self.included(base)
5
- base.extend ClassMethods
6
+ extend ActiveSupport::Concern
6
7
 
7
- base.class_eval do
8
- validates_presence_of :email, if: :email_required?
9
- validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
10
- validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
11
- end
8
+ included do
9
+ validates_presence_of :email, if: :email_required?
10
+ validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
11
+ validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
12
12
  end
13
13
 
14
14
  def email_required?
@@ -21,42 +21,31 @@ module Devise
21
21
  end
22
22
 
23
23
  module MultiEmailValidatable
24
- def self.required_fields(klass)
25
- []
26
- end
24
+ extend ActiveSupport::Concern
27
25
 
28
- # All validations used by this module.
29
- VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
30
- :validates_confirmation_of, :validates_length_of].freeze
26
+ included do
27
+ include Devise::MultiEmail::ParentModelExtensions
31
28
 
32
- def self.included(base)
33
- base.extend ClassMethods
34
- assert_validations_api!(base)
29
+ assert_validations_api!(self)
35
30
 
36
- base.class_eval do
37
- validates_presence_of :email, if: :email_required?
31
+ validates_presence_of :email, if: :email_required?
38
32
 
39
- validates_presence_of :password, if: :password_required?
40
- validates_confirmation_of :password, if: :password_required?
41
- validates_length_of :password, within: password_length, allow_blank: true
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
42
36
 
43
- after_validation :propagate_email_errors
44
- email_class.send :include, EmailValidatable
45
- end
37
+ after_validation :propagate_email_errors
46
38
 
47
- base.devise_modules << :validatable
48
- end
39
+ multi_email_association.include_module(EmailValidatable)
49
40
 
50
- def self.assert_validations_api!(base) #:nodoc:
51
- unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
41
+ devise_modules << :validatable
42
+ end
52
43
 
53
- unless unavailable_validations.empty?
54
- raise "Could not use :validatable module since #{base} does not respond " <<
55
- "to the following methods: #{unavailable_validations.to_sentence}."
56
- end
44
+ def self.required_fields(klass)
45
+ []
57
46
  end
58
47
 
59
- protected
48
+ protected
60
49
 
61
50
  # Same as Devise::Models::Validatable#password_required?
62
51
  def password_required?
@@ -68,15 +57,16 @@ module Devise
68
57
  true
69
58
  end
70
59
 
71
- private
60
+ private
72
61
 
73
62
  def propagate_email_errors
74
- email_error_key = self.class::EMAILS_ASSOCIATION
75
- if respond_to?("#{self.class::EMAILS_ASSOCIATION}_attributes=")
63
+ email_error_key = self.class.multi_email_association.name
64
+
65
+ if respond_to?("#{email_error_key}_attributes=")
76
66
  email_error_key = "#{email_error_key}.email".to_sym
77
67
  end
78
68
 
79
- return if (email_errors = errors.delete(email_error_key)).nil?
69
+ email_errors = errors.delete(email_error_key) || []
80
70
 
81
71
  email_errors.each do |error|
82
72
  errors.add(:email, error)
@@ -84,8 +74,22 @@ module Devise
84
74
  end
85
75
 
86
76
  module ClassMethods
77
+
78
+ # All validations used by this module.
79
+ VALIDATIONS = [:validates_presence_of, :validates_uniqueness_of, :validates_format_of,
80
+ :validates_confirmation_of, :validates_length_of].freeze
81
+
82
+ def assert_validations_api!(base) #:nodoc:
83
+ unavailable_validations = VALIDATIONS.select{ |v| !base.respond_to?(v) }
84
+
85
+ unless unavailable_validations.empty?
86
+ raise "Could not use :validatable module since #{base} does not respond " <<
87
+ "to the following methods: #{unavailable_validations.to_sentence}."
88
+ end
89
+ end
90
+
87
91
  Devise::Models.config(self, :password_length)
88
92
  end
89
93
  end
90
94
  end
91
- end
95
+ end