devise-multi_email 2.0.0 → 2.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
2
  SHA1:
3
- metadata.gz: ae0a4824abdf90e63b3a5027520c2d4ef6465e31
4
- data.tar.gz: ae37353431e7e16eb565a0ba27c814020f644f3e
3
+ metadata.gz: 9ba4375a3b35995bdfa11564cc3f2a4e879d9731
4
+ data.tar.gz: 32c1c262c80fbfa87ef8819e40e0821ecc8ce822
5
5
  SHA512:
6
- metadata.gz: 232a734290ad0eab39000b8ecb76a3c6fc320ece267fdd43a52a1dadf726ee50cd97dae4c712de750a757ca48bbf72e9b7d6fc59b3e138b32414ee6b9d915e06
7
- data.tar.gz: 658d6b2220f1eaf9ca811d4df1b669a938cb7a2911c803abc5546eec9def051756511228eff406b282d7e088084fd8610b7950b88f6d71f2ded2876a187e59f2
6
+ metadata.gz: 9926f6af62664391396203089a60f9a0876d8b3548898ee5148626e963d4c5bd818ff8db4d20c934cf3d7672f3886274fe68e75465a08cf1fa560b84060cdd61
7
+ data.tar.gz: 711a2674cee47fb189a46220adf4d32b334609f74ad490016bc4cd87d387ac9c836b5e1eae2165d5478d747e6b0c58971b75c54bad3f522487ca47de349ac063
@@ -1,8 +1,13 @@
1
+ ### 2.0.1 - 2017-05-16
2
+
3
+ * Refactored to simplify some logic and start moving toward mimicking default Devise lifecycle behavior
4
+ * Added `Devise::MultiEmail.only_login_with_primary_email` option to restrict login to only primary emails
5
+ * Added `Devise::MultiEmail.autosave_emails` option to automatically enable `autosave` on "emails" association
6
+
1
7
  ### 2.0.0 - 2017-05-12
2
8
 
3
- * New `Devise::MultiEmail#configure` setup with options for `user` and `emails` associations and `primary_email` method names
9
+ * New `Devise::MultiEmail#configure` setup with options for `user` and `emails` associations and `primary_email_record` method names
4
10
  * 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
11
  * Changed logic when changing an email address to look up existing email record, otherwise creating a new one, then marking it "primary"
7
12
  * Changed logic when changing an email address to mark all others as `primary = false`
8
13
  * Changed logic when changing an email address to `nil` to mark as `primary = false` rather than deleting records
data/README.md CHANGED
@@ -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
@@ -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.}
@@ -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
@@ -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
@@ -45,9 +45,15 @@ module Devise
45
45
  :confirmation_token, :confirmed_at, :confirmation_sent_at, :confirm, :confirmed?, :unconfirmed_email,
46
46
  :reconfirmation_required?, :pending_reconfirmation?, to: :primary_email_record, allow_nil: true
47
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: false)
52
+ end
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,12 +94,6 @@ 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
95
- end
96
-
97
97
  module ClassMethods
98
98
  delegate :confirm_by_token, :send_confirmation_instructions, to: 'multi_email_association.model_class', allow_nil: false
99
99
  end
@@ -61,12 +61,8 @@ module Devise
61
61
 
62
62
  def propagate_email_errors
63
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
67
- end
68
-
69
- email_errors = errors.delete(email_error_key) || []
64
+ email_errors = errors.delete(email_error_key) ||
65
+ errors.delete("#{email_error_key}.email".to_sym) || []
70
66
 
71
67
  email_errors.each do |error|
72
68
  errors.add(:email, error)
@@ -80,7 +76,7 @@ module Devise
80
76
  :validates_confirmation_of, :validates_length_of].freeze
81
77
 
82
78
  def assert_validations_api!(base) #:nodoc:
83
- unavailable_validations = VALIDATIONS.select{ |v| !base.respond_to?(v) }
79
+ unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
84
80
 
85
81
  unless unavailable_validations.empty?
86
82
  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.lazy.reject(&:destroyed?).reject(&:marked_for_destruction?).to_a
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 = "2.0.1"
4
4
  end
5
5
  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: 2.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: 2017-06-27 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: devise
@@ -111,6 +112,7 @@ 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: []