devise-multi_email 2.0.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ae0a4824abdf90e63b3a5027520c2d4ef6465e31
4
- data.tar.gz: ae37353431e7e16eb565a0ba27c814020f644f3e
2
+ SHA256:
3
+ metadata.gz: 2565382823ed8ab4f837160c54a053df8076a441e2f197802e152932b77634f6
4
+ data.tar.gz: 385ae0a6a352ce1343bea96e30a26fe1d8adf4939615deaf963df87c964b1909
5
5
  SHA512:
6
- metadata.gz: 232a734290ad0eab39000b8ecb76a3c6fc320ece267fdd43a52a1dadf726ee50cd97dae4c712de750a757ca48bbf72e9b7d6fc59b3e138b32414ee6b9d915e06
7
- data.tar.gz: 658d6b2220f1eaf9ca811d4df1b669a938cb7a2911c803abc5546eec9def051756511228eff406b282d7e088084fd8610b7950b88f6d71f2ded2876a187e59f2
6
+ metadata.gz: 5faba7eb1f3f7e04d862954f7cc40d1bf94a08ad7ccbdff2383d6419cb4d5d13f02f3cdf98876275faf3a70be39f6345215440add7cbe888818ead45d05c3855
7
+ data.tar.gz: 73cf0ce8bff419342f769e61f0c3c7455fec01a3733c17afb6337e7e15e798b5de244971e5dd02889ba7c60cbf7c3f53c35e45ca8bca853f0548362353093529
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  /spec/rails_app/log/*
11
11
  /.ruby-version
12
12
  /.idea/
13
+ /gemfiles/*gemfile.lock
data/.travis.yml CHANGED
@@ -1,8 +1,16 @@
1
1
  language: ruby
2
+
2
3
  rvm:
3
- - 2.3.1
4
- - 2.4.1
4
+ - 2.4
5
+ - 2.5
6
+ - 2.6
5
7
  gemfile:
6
- - gemfiles/rails_5_0.gemfile
7
- - gemfiles/rails_4_2.gemfile
8
- before_install: gem install bundler -v 1.10.6
8
+ - gemfiles/rails_5_2.gemfile
9
+ - gemfiles/rails_5_1.gemfile
10
+ - gemfiles/rails_6_0.gemfile
11
+
12
+ jobs:
13
+ exclude:
14
+ - rvm: 2.4
15
+ gemfile: gemfiles/rails_6_0.gemfile
16
+
data/CHANGELOG.md CHANGED
@@ -1,8 +1,20 @@
1
+ ### Unreleased
2
+ * Fix ActiveModel::Errors#keys deprecation in Rails 6.1
3
+
4
+ ### 3.0.0 - 2019-11-06
5
+ * Deprecate the support of Rails 4 (although it might still work)
6
+ * Fix warnings in Rails 6
7
+
8
+ ### 2.0.1 - 2017-05-16
9
+
10
+ * Refactored to simplify some logic and start moving toward mimicking default Devise lifecycle behavior
11
+ * Added `Devise::MultiEmail.only_login_with_primary_email` option to restrict login to only primary emails
12
+ * Added `Devise::MultiEmail.autosave_emails` option to automatically enable `autosave` on "emails" association
13
+
1
14
  ### 2.0.0 - 2017-05-12
2
15
 
3
- * New `Devise::MultiEmail#configure` setup with options for `user` and `emails` associations and `primary_email` method names
16
+ * New `Devise::MultiEmail#configure` setup with options for `user` and `emails` associations and `primary_email_record` method names
4
17
  * Refactor to expose `_multi_email_*` prefixed methods on models
5
- * New `primary_email` method to get primary email record (however, can be configured as `primary_email_record` for backwards-compatibility)
6
18
  * Changed logic when changing an email address to look up existing email record, otherwise creating a new one, then marking it "primary"
7
19
  * Changed logic when changing an email address to mark all others as `primary = false`
8
20
  * Changed logic when changing an email address to `nil` to mark as `primary = false` rather than deleting records
data/README.md CHANGED
@@ -10,7 +10,7 @@ Letting [Devise](https://github.com/plataformatec/devise) support multiple email
10
10
 
11
11
  ## Getting Started
12
12
 
13
- Add this line to your application's `Gemfile`, _devise-multi_email_ has been tested with Devise 4.0 and rails 4.2:
13
+ Add this line to your application's `Gemfile`:
14
14
 
15
15
  ```ruby
16
16
  gem 'devise-multi_email'
@@ -49,6 +49,31 @@ create_table :emails do |t|
49
49
  end
50
50
  ```
51
51
 
52
+ You can choose whether or not users can login with an email address that is not the primary email address.
53
+
54
+ ```ruby
55
+ Devise::MultiEmail.configure do |config|
56
+ # Default is `false`
57
+ config.only_login_with_primary_email = true
58
+ end
59
+ ```
60
+
61
+ The `autosave` is automatically enabled on the `emails` association by default. This is to ensure the `primary`
62
+ flag is persisted for all emails when the primary email is changed. When `autosave` is not enabled on the association,
63
+ only new emails are saved when the parent (e.g. `User`) record is saved. (Updates to already-persisted email records
64
+ are not saved.)
65
+
66
+ If you don't want `autosave` to be enabled automatically, you can disable this feature. What this will do is
67
+ enable alternative behavior, which adds an `after_save` callback to the parent record and calls `email.save` on each email
68
+ record where the `primary` value has changed.
69
+
70
+ ```ruby
71
+ Devise::MultiEmail.configure do |config|
72
+ # Default is `true`
73
+ config.autosave_emails = false
74
+ end
75
+ ```
76
+
52
77
  ### Configure custom association names
53
78
 
54
79
  You may not want to use the association `user.emails` or `email.users`. You can customize the name of the associations used. Add your custom configurations to an initializer file such as `config/initializers/devise-multi_email.rb`.
@@ -61,9 +86,8 @@ Devise::MultiEmail.configure do |config|
61
86
  config.parent_association_name = :team
62
87
  # Default is :emails for parent (e.g. User) model
63
88
  config.emails_association_name = :email_addresses
64
- # For backwards-compatibility, specify :primary_email_record
65
- # Default is :primary_email
66
- config.primary_email_method_name = :primary_email_record
89
+ # Default is :primary_email_record
90
+ config.primary_email_method_name = :primary_email
67
91
  end
68
92
 
69
93
  # Example use of custom association names
@@ -152,17 +176,17 @@ You can do `email.send_confirmation_instructions` for each email individually, b
152
176
 
153
177
  ## Wiki
154
178
 
155
- [Migrating exiting user records](https://github.com/allenwq/devise-multi_email/wiki/Migrating-existing-user-records)
179
+ [Migrating existing user records](https://github.com/allenwq/devise-multi_email/wiki/Migrating-existing-user-records)
156
180
 
157
181
  ## Development
158
182
 
159
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
183
+ After checking out the repo, run `bundle install` to install dependencies.
160
184
 
161
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
185
+ Then, run `bundle exec rake` to run the RSpec test suite.
162
186
 
163
187
  ## Contributing
164
188
 
165
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/devise-multi_email. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
189
+ Bug reports and pull requests are welcome on GitHub at https://github.com/allenwq/devise-multi_email. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
166
190
 
167
191
 
168
192
  ## License
@@ -6,8 +6,8 @@ require 'devise/multi_email/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'devise-multi_email'
8
8
  spec.version = Devise::MultiEmail::VERSION
9
- spec.authors = ['ALLEN WANG QIANG']
10
- spec.email = ['rovingbreeze@gmail.com']
9
+ spec.authors = ['ALLEN WANG QIANG', 'Joel Van Horn']
10
+ spec.email = ['rovingbreeze@gmail.com', 'joel@joelvanhorn.com']
11
11
 
12
12
  spec.summary = %q{Let devise support multiple emails.}
13
13
  spec.description = %q{Devise authenticatable, confirmable and validatable with multiple emails.}
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_runtime_dependency 'devise'
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1.10'
24
+ spec.add_development_dependency 'bundler'
25
25
  spec.add_development_dependency 'rake', '~> 10.0'
26
26
  spec.add_development_dependency 'rspec'
27
27
  spec.add_development_dependency 'sqlite3'
@@ -39,15 +39,16 @@ GEM
39
39
  minitest (~> 5.1)
40
40
  tzinfo (~> 1.1)
41
41
  arel (7.1.4)
42
- bcrypt (3.1.11)
42
+ bcrypt (3.1.13)
43
43
  builder (3.2.3)
44
44
  byebug (9.0.6)
45
- concurrent-ruby (1.0.5)
45
+ concurrent-ruby (1.1.5)
46
+ crass (1.0.5)
46
47
  debug_inspector (0.0.2)
47
- devise (4.2.0)
48
+ devise (4.7.1)
48
49
  bcrypt (~> 3.0)
49
50
  orm_adapter (~> 0.1)
50
- railties (>= 4.1.0, < 5.1)
51
+ railties (>= 4.1.0)
51
52
  responders
52
53
  warden (~> 1.2.3)
53
54
  devise-encryptable (0.2.0)
@@ -56,10 +57,11 @@ GEM
56
57
  devise
57
58
  erubis (2.7.0)
58
59
  execjs (2.7.0)
59
- ffi (1.9.18)
60
+ ffi (1.11.1)
60
61
  globalid (0.3.7)
61
62
  activesupport (>= 4.1.0)
62
- i18n (0.8.1)
63
+ i18n (0.9.5)
64
+ concurrent-ruby (~> 1.0)
63
65
  jbuilder (2.6.3)
64
66
  activesupport (>= 3.0.0, < 5.2)
65
67
  multi_json (~> 1.2)
@@ -70,23 +72,24 @@ GEM
70
72
  listen (3.0.8)
71
73
  rb-fsevent (~> 0.9, >= 0.9.4)
72
74
  rb-inotify (~> 0.9, >= 0.9.7)
73
- loofah (2.0.3)
75
+ loofah (2.3.1)
76
+ crass (~> 1.0.2)
74
77
  nokogiri (>= 1.5.9)
75
78
  mail (2.6.4)
76
79
  mime-types (>= 1.16, < 4)
77
- method_source (0.8.2)
80
+ method_source (0.9.2)
78
81
  mime-types (3.1)
79
82
  mime-types-data (~> 3.2015)
80
83
  mime-types-data (3.2016.0521)
81
- mini_portile2 (2.1.0)
82
- minitest (5.10.1)
84
+ mini_portile2 (2.4.0)
85
+ minitest (5.12.2)
83
86
  multi_json (1.12.1)
84
87
  nio4r (2.0.0)
85
- nokogiri (1.7.0.1)
86
- mini_portile2 (~> 2.1.0)
88
+ nokogiri (1.10.5)
89
+ mini_portile2 (~> 2.4.0)
87
90
  orm_adapter (0.5.0)
88
91
  puma (3.7.1)
89
- rack (2.0.1)
92
+ rack (2.0.7)
90
93
  rack-test (0.6.3)
91
94
  rack (>= 1.0)
92
95
  rails (5.0.2)
@@ -101,23 +104,24 @@ GEM
101
104
  bundler (>= 1.3.0, < 2.0)
102
105
  railties (= 5.0.2)
103
106
  sprockets-rails (>= 2.0.0)
104
- rails-dom-testing (2.0.2)
105
- activesupport (>= 4.2.0, < 6.0)
106
- nokogiri (~> 1.6)
107
- rails-html-sanitizer (1.0.3)
108
- loofah (~> 2.0)
107
+ rails-dom-testing (2.0.3)
108
+ activesupport (>= 4.2.0)
109
+ nokogiri (>= 1.6)
110
+ rails-html-sanitizer (1.3.0)
111
+ loofah (~> 2.3)
109
112
  railties (5.0.2)
110
113
  actionpack (= 5.0.2)
111
114
  activesupport (= 5.0.2)
112
115
  method_source
113
116
  rake (>= 0.8.7)
114
117
  thor (>= 0.18.1, < 2.0)
115
- rake (12.0.0)
118
+ rake (13.0.0)
116
119
  rb-fsevent (0.9.8)
117
120
  rb-inotify (0.9.8)
118
121
  ffi (>= 0.5.0)
119
- responders (2.3.0)
120
- railties (>= 4.2.0, < 5.1)
122
+ responders (3.0.0)
123
+ actionpack (>= 5.0)
124
+ railties (>= 5.0)
121
125
  sass (3.4.23)
122
126
  sass-rails (5.0.6)
123
127
  railties (>= 4.0.0, < 6)
@@ -130,7 +134,7 @@ GEM
130
134
  spring-watcher-listen (2.0.1)
131
135
  listen (>= 2.7, < 4.0)
132
136
  spring (>= 1.2, < 3.0)
133
- sprockets (3.7.1)
137
+ sprockets (3.7.2)
134
138
  concurrent-ruby (~> 1.0)
135
139
  rack (> 1, < 3)
136
140
  sprockets-rails (3.2.0)
@@ -138,18 +142,18 @@ GEM
138
142
  activesupport (>= 4.0)
139
143
  sprockets (>= 3.0.0)
140
144
  sqlite3 (1.3.13)
141
- thor (0.19.4)
145
+ thor (0.20.3)
142
146
  thread_safe (0.3.6)
143
147
  tilt (2.0.6)
144
148
  turbolinks (5.0.1)
145
149
  turbolinks-source (~> 5)
146
150
  turbolinks-source (5.0.0)
147
- tzinfo (1.2.2)
151
+ tzinfo (1.2.5)
148
152
  thread_safe (~> 0.1)
149
153
  uglifier (3.1.4)
150
154
  execjs (>= 0.3.0, < 3)
151
- warden (1.2.7)
152
- rack (>= 1.0)
155
+ warden (1.2.8)
156
+ rack (>= 2.0.6)
153
157
  web-console (3.4.0)
154
158
  actionview (>= 5.0)
155
159
  activemodel (>= 5.0)
@@ -1,3 +1,5 @@
1
1
  class Email < ApplicationRecord
2
2
  belongs_to :user
3
+
4
+ table_name 'user_emails'
3
5
  end
@@ -30,7 +30,7 @@ class DeviseCreateUsers < ActiveRecord::Migration[5.0]
30
30
  add_index :users, :reset_password_token, unique: true
31
31
  # add_index :users, :unlock_token, unique: true
32
32
 
33
- create_table :emails do |t|
33
+ create_table :user_emails do |t|
34
34
  t.integer :user_id, null: false
35
35
  t.string :email, null: false
36
36
  t.string :unconfirmed_email
@@ -44,6 +44,6 @@ class DeviseCreateUsers < ActiveRecord::Migration[5.0]
44
44
  t.timestamps null: false
45
45
  end
46
46
 
47
- add_index :emails, :confirmation_token, unique: true
47
+ add_index :user_emails, :confirmation_token, unique: true
48
48
  end
49
49
  end
@@ -12,7 +12,7 @@
12
12
 
13
13
  ActiveRecord::Schema.define(version: 20170307145547) do
14
14
 
15
- create_table "emails", force: :cascade do |t|
15
+ create_table "user_emails", force: :cascade do |t|
16
16
  t.integer "user_id", null: false
17
17
  t.string "email", null: false
18
18
  t.string "unconfirmed_email"
@@ -1,5 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "rails", "~> 4.2.8"
3
+ gem "rails", "~> 5.2.0"
4
4
 
5
5
  gemspec path: "../"
@@ -1,5 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "rails", "~> 5.0.3"
3
+ gem "rails", ">= 6.0.2.1", "< 6.1"
4
4
 
5
5
  gemspec path: "../"
@@ -14,16 +14,29 @@ module Devise
14
14
  model_class.__send__ :include, mod
15
15
  end
16
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`."
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
20
26
  end
27
+ end
21
28
 
29
+ def autosave_enabled?
30
+ reflection.options[:autosave] == true
31
+ end
32
+
33
+ def model_class
22
34
  @model_class ||= reflection.class_name.constantize
23
35
  end
24
36
 
25
37
  def reflection
26
- @reflection ||= @klass.reflect_on_association(name)
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`.")
27
40
  end
28
41
  end
29
42
  end
@@ -4,14 +4,12 @@ module Devise
4
4
  module MultiEmail
5
5
  class EmailModelManager
6
6
 
7
- attr_reader :record
8
-
9
- def initialize(record)
10
- @record = record
7
+ def initialize(email_record)
8
+ @email_record = email_record
11
9
  end
12
10
 
13
11
  def parent
14
- record.__send__(record.class.multi_email_association.name)
12
+ @email_record.__send__(@email_record.class.multi_email_association.name)
15
13
  end
16
14
  end
17
15
  end
@@ -29,6 +29,7 @@ module Devise
29
29
  extend ActiveSupport::Concern
30
30
 
31
31
  included do
32
+ multi_email_association.configure_autosave!{ include AuthenticatableAutosaveExtensions }
32
33
  multi_email_association.include_module(EmailAuthenticatable)
33
34
  end
34
35
 
@@ -36,26 +37,40 @@ module Devise
36
37
 
37
38
  # Gets the primary email address of the user.
38
39
  def email
39
- multi_email.primary_email.try(:email)
40
+ multi_email.primary_email_record.try(:email)
40
41
  end
41
42
 
42
43
  # Sets the default email address of the user.
43
44
  def email=(new_email)
44
- multi_email.change_primary_email_to(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
45
60
  end
46
61
  end
47
62
 
48
63
  module ClassMethods
49
64
  def find_first_by_auth_conditions(tainted_conditions, opts = {})
50
65
  filtered_conditions = devise_parameter_filter.filter(tainted_conditions.dup)
51
- email = filtered_conditions.delete(:email)
66
+ criteria = filtered_conditions.extract!(:email, :unconfirmed_email)
52
67
 
53
- if email && email.is_a?(String)
68
+ if criteria.keys.any?
54
69
  conditions = filtered_conditions.to_h.merge(opts).
55
- reverse_merge(multi_email_association.reflection.table_name => { email: email })
70
+ reverse_merge(build_conditions(criteria))
56
71
 
57
72
  resource = joins(multi_email_association.name).find_by(conditions)
58
- resource.current_login_email = email if resource.respond_to?(:current_login_email=)
73
+ resource.current_login_email = criteria.values.first if resource
59
74
  resource
60
75
  else
61
76
  super(tainted_conditions, opts)
@@ -63,7 +78,17 @@ module Devise
63
78
  end
64
79
 
65
80
  def find_by_email(email)
66
- joins(multi_email_association.name).where(multi_email_association.reflection.table_name => { email: email.downcase }).first
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 }
67
92
  end
68
93
  end
69
94
  end
@@ -8,12 +8,12 @@ module Devise
8
8
  included do
9
9
  devise :confirmable
10
10
 
11
- extend ClassReplacementMethods
11
+ include ConfirmableExtensions
12
12
  end
13
13
 
14
- module ClassReplacementMethods
15
- def allow_unconfirmed_access_for
16
- 0.days
14
+ module ConfirmableExtensions
15
+ def confirmation_period_valid?
16
+ primary? ? super : false
17
17
  end
18
18
  end
19
19
  end
@@ -42,12 +42,18 @@ module Devise
42
42
 
43
43
  # delegate before creating overriding methods
44
44
  delegate :skip_confirmation!, :skip_confirmation_notification!, :skip_reconfirmation!, :confirmation_required?,
45
- :confirmation_token, :confirmed_at, :confirmation_sent_at, :confirm, :confirmed?, :unconfirmed_email,
46
- :reconfirmation_required?, :pending_reconfirmation?, to: :primary_email_record, allow_nil: true
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
47
53
 
48
54
  # This need to be forwarded to the email that the user logged in with
49
55
  def active_for_authentication?
50
- login_email = current_login_email_record
56
+ login_email = multi_email.login_email_record
51
57
 
52
58
  if login_email && !login_email.primary?
53
59
  super && login_email.active_for_authentication?
@@ -58,7 +64,7 @@ module Devise
58
64
 
59
65
  # Shows email not confirmed instead of account inactive when the email that user used to login is not confirmed
60
66
  def inactive_message
61
- login_email = current_login_email_record
67
+ login_email = multi_email.login_email_record
62
68
 
63
69
  if login_email && !login_email.primary? && !login_email.confirmed?
64
70
  :unconfirmed
@@ -88,10 +94,9 @@ module Devise
88
94
 
89
95
  private
90
96
 
91
- def current_login_email_record
92
- if respond_to?(:current_login_email) && current_login_email
93
- multi_email.emails.find_by(email: current_login_email)
94
- end
97
+ def unconfirmed_access_possible?
98
+ Devise.allow_unconfirmed_access_for.nil? || \
99
+ Devise.allow_unconfirmed_access_for > 0.days
95
100
  end
96
101
 
97
102
  module ClassMethods
@@ -7,8 +7,13 @@ module Devise
7
7
 
8
8
  included do
9
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?
10
+ if Devise.activerecord51?
11
+ validates_uniqueness_of :email, allow_blank: true, case_sensitive: true, if: :will_save_change_to_email?
12
+ validates_format_of :email, with: email_regexp, allow_blank: true, if: :will_save_change_to_email?
13
+ else
14
+ validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
15
+ validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
16
+ end
12
17
  end
13
18
 
14
19
  def email_required?
@@ -60,19 +65,31 @@ module Devise
60
65
  private
61
66
 
62
67
  def propagate_email_errors
63
- email_error_key = self.class.multi_email_association.name
64
-
65
- if respond_to?("#{email_error_key}_attributes=")
66
- email_error_key = "#{email_error_key}.email".to_sym
68
+ association_name = self.class.multi_email_association.name
69
+ email_error_key = errors_attribute_names.detect do |key|
70
+ [association_name.to_s, "#{association_name}.email"].include?(key.to_s)
67
71
  end
72
+ return unless email_error_key.present?
73
+
74
+ email_errors =
75
+ if errors.respond_to?(:details)
76
+ errors
77
+ .details[email_error_key]
78
+ .map { |e| e[:error] }
79
+ .zip(errors.delete(email_error_key) || [])
80
+ else
81
+ errors.delete(email_error_key)
82
+ end
68
83
 
69
- email_errors = errors.delete(email_error_key) || []
70
-
71
- email_errors.each do |error|
72
- errors.add(:email, error)
84
+ email_errors.each do |type, message|
85
+ errors.add(:email, type, message: message)
73
86
  end
74
87
  end
75
88
 
89
+ def errors_attribute_names
90
+ errors.respond_to?(:attribute_names) ? errors.attribute_names : errors.keys
91
+ end
92
+
76
93
  module ClassMethods
77
94
 
78
95
  # All validations used by this module.
@@ -80,7 +97,7 @@ module Devise
80
97
  :validates_confirmation_of, :validates_length_of].freeze
81
98
 
82
99
  def assert_validations_api!(base) #:nodoc:
83
- unavailable_validations = VALIDATIONS.select{ |v| !base.respond_to?(v) }
100
+ unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
84
101
 
85
102
  unless unavailable_validations.empty?
86
103
  raise "Could not use :validatable module since #{base} does not respond " <<
@@ -8,9 +8,10 @@ module Devise
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  included do
11
+ multi_email_association.configure_autosave!
11
12
  multi_email_association.include_module(EmailModelExtensions)
12
13
  end
13
-
14
+
14
15
  delegate Devise::MultiEmail.primary_email_method_name, to: :multi_email, allow_nil: false
15
16
 
16
17
  def multi_email
@@ -4,51 +4,86 @@ module Devise
4
4
  module MultiEmail
5
5
  class ParentModelManager
6
6
 
7
- attr_reader :record
7
+ def initialize(parent_record)
8
+ @parent_record = parent_record
9
+ end
8
10
 
9
- def initialize(record)
10
- @record = record
11
+ def current_email_record
12
+ login_email_record || primary_email_record
11
13
  end
12
14
 
13
- # Gets the email records that have not been deleted
14
- def filtered_emails
15
- emails.reject(&:destroyed?).reject(&:marked_for_destruction?)
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
16
20
  end
17
21
 
18
22
  # Gets the primary email record.
19
- def primary_email
23
+ def primary_email_record
20
24
  filtered_emails.find(&:primary?)
21
25
  end
22
- alias_method Devise::MultiEmail.primary_email_method_name, :primary_email
23
-
24
- def change_primary_email_to(new_email)
25
- # Use Devise formatting settings for emails
26
- formatted_email = record.class.send(:devise_parameter_filter).filter(email: new_email)[:email]
27
-
28
- valid_emails = filtered_emails
26
+ alias_method Devise::MultiEmail.primary_email_method_name, :primary_email_record
29
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 = {})
30
32
  # mark none as primary when set to nil
31
33
  if new_email.nil?
32
- valid_emails.each{ |record| record.primary = false }
34
+ filtered_emails.each { |item| item.primary = false }
33
35
 
34
36
  # select or build an email record
35
37
  else
36
- record = valid_emails.find{ |record| record.email == formatted_email }
38
+ record = find_or_build_for_email(new_email)
37
39
 
38
- unless record
39
- record = emails.build(email: formatted_email)
40
- valid_emails << record
40
+ if record.try(:confirmed?) || primary_email_record.nil? || options[:allow_unconfirmed]
41
+ set_primary_record_to(record, options)
41
42
  end
42
-
43
- # toggle the selected record as primary and others as not
44
- valid_emails.each{ |other| other.primary = (other == record) }
45
43
  end
46
44
 
47
45
  record
48
46
  end
49
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
+
50
59
  def emails
51
- record.__send__(record.class.multi_email_association.name)
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
52
87
  end
53
88
  end
54
89
  end
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module MultiEmail
3
- VERSION = "2.0.0"
3
+ VERSION = "3.0.1"
4
4
  end
5
5
  end
@@ -3,32 +3,54 @@ require 'devise'
3
3
 
4
4
  module Devise
5
5
  module MultiEmail
6
- def self.configure(&block)
7
- yield self
8
- end
6
+ class << self
7
+ def configure(&block)
8
+ yield self
9
+ end
9
10
 
10
- def self.parent_association_name
11
- @parent_association_name ||= :user
12
- end
11
+ @autosave_emails = false
13
12
 
14
- def self.parent_association_name=(name)
15
- @parent_association_name = name.try(:to_sym)
16
- end
13
+ def autosave_emails?
14
+ @autosave_emails == true
15
+ end
17
16
 
18
- def self.emails_association_name
19
- @emails_association_name ||= :emails
20
- end
17
+ def autosave_emails=(value)
18
+ @autosave_emails = (value == true)
19
+ end
21
20
 
22
- def self.emails_association_name=(name)
23
- @emails_association_name = name.try(:to_sym)
24
- end
21
+ @only_login_with_primary_email = false
25
22
 
26
- def self.primary_email_method_name
27
- @primary_email_method_name ||= :primary_email
28
- end
23
+ def only_login_with_primary_email?
24
+ @only_login_with_primary_email == true
25
+ end
26
+
27
+ def only_login_with_primary_email=(value)
28
+ @only_login_with_primary_email = (value == true)
29
+ end
30
+
31
+ def parent_association_name
32
+ @parent_association_name ||= :user
33
+ end
34
+
35
+ def parent_association_name=(name)
36
+ @parent_association_name = name.try(:to_sym)
37
+ end
38
+
39
+ def emails_association_name
40
+ @emails_association_name ||= :emails
41
+ end
42
+
43
+ def emails_association_name=(name)
44
+ @emails_association_name = name.try(:to_sym)
45
+ end
46
+
47
+ def primary_email_method_name
48
+ @primary_email_method_name ||= :primary_email_record
49
+ end
29
50
 
30
- def self.primary_email_method_name=(name)
31
- @primary_email_method_name = name.try(:to_sym)
51
+ def primary_email_method_name=(name)
52
+ @primary_email_method_name = name.try(:to_sym)
53
+ end
32
54
  end
33
55
  end
34
56
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-multi_email
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ALLEN WANG QIANG
8
+ - Joel Van Horn
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2017-05-16 00:00:00.000000000 Z
12
+ date: 2022-04-27 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: devise
@@ -28,16 +29,16 @@ dependencies:
28
29
  name: bundler
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
- - - "~>"
32
+ - - ">="
32
33
  - !ruby/object:Gem::Version
33
- version: '1.10'
34
+ version: '0'
34
35
  type: :development
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
- - - "~>"
39
+ - - ">="
39
40
  - !ruby/object:Gem::Version
40
- version: '1.10'
41
+ version: '0'
41
42
  - !ruby/object:Gem::Dependency
42
43
  name: rake
43
44
  requirement: !ruby/object:Gem::Requirement
@@ -111,13 +112,13 @@ dependencies:
111
112
  description: Devise authenticatable, confirmable and validatable with multiple emails.
112
113
  email:
113
114
  - rovingbreeze@gmail.com
115
+ - joel@joelvanhorn.com
114
116
  executables: []
115
117
  extensions: []
116
118
  extra_rdoc_files: []
117
119
  files:
118
120
  - ".gitignore"
119
121
  - ".rspec"
120
- - ".simplecov"
121
122
  - ".travis.yml"
122
123
  - CHANGELOG.md
123
124
  - CODE_OF_CONDUCT.md
@@ -209,9 +210,9 @@ files:
209
210
  - examples/rails5_app/tmp/.keep
210
211
  - examples/rails5_app/vendor/assets/javascripts/.keep
211
212
  - examples/rails5_app/vendor/assets/stylesheets/.keep
212
- - gemfiles/rails_4_2.gemfile
213
- - gemfiles/rails_5_0.gemfile
214
213
  - gemfiles/rails_5_1.gemfile
214
+ - gemfiles/rails_5_2.gemfile
215
+ - gemfiles/rails_6_0.gemfile
215
216
  - lib/devise/multi_email.rb
216
217
  - lib/devise/multi_email/association_manager.rb
217
218
  - lib/devise/multi_email/email_model_extensions.rb
@@ -241,8 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
241
242
  - !ruby/object:Gem::Version
242
243
  version: '0'
243
244
  requirements: []
244
- rubyforge_project:
245
- rubygems_version: 2.6.8
245
+ rubygems_version: 3.0.3.1
246
246
  signing_key:
247
247
  specification_version: 4
248
248
  summary: Let devise support multiple emails.
data/.simplecov DELETED
@@ -1,6 +0,0 @@
1
- require 'simplecov'
2
- require 'coveralls'
3
-
4
- SimpleCov.start do
5
- add_filter 'spec/'
6
- end