devise-multi_email 2.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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