revise 0.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.
@@ -0,0 +1,48 @@
1
+ module Revise
2
+ module Helpers
3
+ module Core
4
+ def revise_for(*resources)
5
+ Revise.app = self
6
+
7
+ options = resources.extract_options!
8
+
9
+ resources.each do |resource|
10
+ begin
11
+ if Revise::MODULES.has_key?(resource)
12
+ models = Revise::MODULES[resource]
13
+ models.each do |m|
14
+ model = Revise::Models.const_get(m)
15
+ add_helpers(model)
16
+ add_controllers(model)
17
+ add_mailers(model)
18
+ end
19
+ else
20
+ Padrino.logger.error "Hey man #{resource} doesn't exist"
21
+ end
22
+ rescue Exception => e
23
+ Padrino.logger.error "Failed to load: #{resource} Because #{e.message()}"
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+ def add_helpers(model)
30
+ model::HELPERS.each do |helper|
31
+ self.helpers(Revise::Helpers.const_get(helper))
32
+ end
33
+ end
34
+
35
+ def add_controllers(model)
36
+ model::CONTROLLERS.each do |controller|
37
+ self.send(:extend, Revise::Controllers.const_get(controller))
38
+ end
39
+ end
40
+
41
+ def add_mailers(model)
42
+ model::MAILERS.each do |mailer|
43
+ self.send(:extend, Revise::Mailers.const_get(mailer))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ module Revise
2
+ module Inviter
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ extend ClassMethods
7
+ attr_writer :invitation_limit unless respond_to? :invitation_limit
8
+ end
9
+
10
+ def invitation_limit
11
+ self[:invitation_limit] || self.class.invitation_limit
12
+ end
13
+
14
+ # Return true if this user has invitations left to send
15
+ def has_invitations_left?
16
+ if self.class.invitation_limit.present? || self.respond_to?(:invitation_limit)
17
+ if invitation_limit
18
+ return invitation_limit > 0
19
+ else
20
+ return self.class.invitation_limit > 0 if self.class.invitation_limit.present?
21
+ return false
22
+ end
23
+ else
24
+ return true
25
+ end
26
+ end
27
+
28
+ protected
29
+ def decrement_invitation_limit!
30
+ if self.class.invitation_limit.present?
31
+ self.invitation_limit ||= self.class.invitation_limit
32
+ self.update_attribute(:invitation_limit, invitation_limit - 1)
33
+ end
34
+ end
35
+
36
+ module ClassMethods
37
+ Revise::Models.config(self, :invitation_limit)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ en:
2
+ revise:
3
+ recoverable:
4
+ reset_password_instructions:
5
+ subject: "Reset Password Instructions For %{domain}"
6
+ confirmable:
7
+ confirmation_instructions:
8
+ subject: "Confirm Your Email At %{domain}"
9
+ invitable:
10
+ invation_instructions:
11
+ subject: "You've Been Invited To %{domain}"
@@ -0,0 +1,18 @@
1
+ module Revise
2
+ module Mailers
3
+ module Confirmable
4
+ def self.extended(klass)
5
+ klass.mailer :confirmable do
6
+ email :confirmation_instructions do |name, email, confirmation_token|
7
+ from Revise.mailer_from
8
+ to email
9
+ subject t('revise.confirmable.confirmation_instructions.subject', :domain => ENV['DOMAIN'])
10
+ locals :name => name, :email => email, :confirmation_token => confirmation_token
11
+ render 'revise/confirmation_instructions'
12
+ content_type :html
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Revise
2
+ module Mailers
3
+ module Invitable
4
+ def self.extended(klass)
5
+ klass.mailer :invitable do
6
+ email :invitation_instructions do |name, email, invitation_token|
7
+ from Revise.mailer_from
8
+ to email
9
+ subject t('revise.invitable.invitation_instructions.subject', :domain => ENV['DOMAIN'])
10
+ locals :name => name, :email => email, :invitation_token => invitation_token
11
+ render 'revise/invitation_instructions'
12
+ content_type :html
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Revise
2
+ module Mailers
3
+ module Recoverable
4
+ def self.extended(klass)
5
+ klass.mailer :recoverable do
6
+ email :reset_password_instructions do |name, email, reset_password_token|
7
+ from Revise.mailer_from
8
+ to email
9
+ subject t('revise.recoverable.reset_password_instructions.subject', :domain => ENV['DOMAIN'])
10
+ locals :name => name, :email => email, :reset_password_token => reset_password_token
11
+ render 'revise/reset_password_instructions'
12
+ content_type :html
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ module Revise
2
+ module Models
3
+ class MissingAttribute < StandardError
4
+ def initialize(attributes)
5
+ @attributes = attributes
6
+ end
7
+
8
+ def message
9
+ "The following attribute(s) is (are) missing on your model: #{@attributes.join(", ")}"
10
+ end
11
+ end
12
+
13
+ def self.config(mod, *accessors)
14
+ class << mod; attr_accessor :available_configs; end
15
+ mod.available_configs = accessors
16
+
17
+ accessors.each do |accessor|
18
+ mod.class_eval <<-METHOD, __FILE__, __LINE__ + 1
19
+ def #{accessor}
20
+ if defined?(@#{accessor})
21
+ @#{accessor}
22
+ elsif superclass.respond_to?(:#{accessor})
23
+ superclass.#{accessor}
24
+ else
25
+ Revise.#{accessor}
26
+ end
27
+ end
28
+
29
+ def #{accessor}=(value)
30
+ @#{accessor} = value
31
+ end
32
+ METHOD
33
+ end
34
+ end
35
+
36
+ def self.check_fields!(klass)
37
+ failed_attributes = []
38
+ instance = klass.new
39
+
40
+ klass.revise_modules.each do |mod|
41
+ constant = const_get(mod.to_s.classify)
42
+
43
+ if constant.respond_to?(:required_fields)
44
+ constant.required_fields(klass).each do |field|
45
+ failed_attributes << field unless instance.respond_to?(field)
46
+ end
47
+ else
48
+ ActiveSupport::Deprecation.warn "The module #{mod} doesn't implement self.required_fields(klass). " \
49
+ "Devise uses required_fields to warn developers of any missing fields in their models. " \
50
+ "Please implement #{mod}.required_fields(klass) that returns an array of symbols with the required fields."
51
+ end
52
+ end
53
+
54
+ if failed_attributes.any?
55
+ fail Revise::Models::MissingAttribute.new(failed_attributes)
56
+ end
57
+ end
58
+
59
+ def revise(*modules)
60
+ options = modules.extract_options!.dup
61
+
62
+ plural_name = self.model_name.plural.to_sym
63
+ Revise::MODULES[plural_name] = []
64
+
65
+ revise_modules_hook! do
66
+ include Revise::Models::Authenticatable
67
+
68
+ modules.each do |m|
69
+ mod = Revise::Models.const_get(m.to_s.classify)
70
+
71
+ if mod.const_defined?("ClassMethods")
72
+ class_mod = mod.const_get("ClassMethods")
73
+ extend class_mod
74
+
75
+ if class_mod.respond_to?(:available_configs)
76
+ available_configs = class_mod.available_configs
77
+ available_configs.each do |config|
78
+ next unless options.key?(config)
79
+ send(:"#{config}=", options.delete(config))
80
+ end
81
+ end
82
+ end
83
+
84
+ include mod
85
+
86
+ Revise::MODULES[plural_name] << m.to_s.classify
87
+ end
88
+
89
+ options.each { |key, value| send(:"#{key}=", value) }
90
+ end
91
+ end
92
+
93
+ def revise_modules_hook!
94
+ yield
95
+ end
96
+ end
97
+ end
98
+
99
+ require 'revise/models/authenticatable'
@@ -0,0 +1,137 @@
1
+ module Revise
2
+ module Models
3
+ module Authenticatable
4
+ extend ActiveSupport::Concern
5
+
6
+ MAILERS = []
7
+ HELPERS = ['Authentication']
8
+ CONTROLLERS = ['Main', 'Sessions', 'Accounts']
9
+
10
+ BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at, :role,
11
+ :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
12
+ :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
13
+ :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :authentication_token, :role,
14
+ :roles]
15
+
16
+ included do
17
+ before_validation :downcase_keys
18
+ before_validation :strip_whitespace
19
+ attr_accessor :skip_email
20
+ end
21
+
22
+ def self.required_fields(klass)
23
+ [:role]
24
+ end
25
+
26
+ def valid_for_authentication?
27
+ block_given? ? yield : true
28
+ end
29
+
30
+ def unauthenticated_message
31
+ :invalid
32
+ end
33
+
34
+ def active_for_authentication?
35
+ true
36
+ end
37
+
38
+ def inactive_message
39
+ :inactive
40
+ end
41
+
42
+ def password_required?
43
+ encrypted_password.blank? || password.present?
44
+ end
45
+
46
+ def role?(role)
47
+ return false unless self.respond_to?(:role)
48
+ return self.role.to_sym == role.to_sym
49
+ end
50
+
51
+ array = %w(serializable_hash)
52
+ array << "to_xml"
53
+
54
+ array.each do |method|
55
+ class_eval <<-RUBY, __FILE__, __LINE__
56
+ def #{method}(options=nil)
57
+ options ||= {}
58
+ options[:except] = Array(options[:except])
59
+
60
+ if options[:force_except]
61
+ options[:except].concat Array(options[:force_except])
62
+ else
63
+ options[:except].concat BLACKLIST_FOR_SERIALIZATION
64
+ end
65
+ super(options)
66
+ end
67
+ RUBY
68
+ end
69
+
70
+ protected
71
+
72
+ def send_revise_notification(resource, notification, *attributes)
73
+ return false if self.email.blank?
74
+ Revise.app.deliver(resource, notification, *attributes) unless @skip_email
75
+ end
76
+
77
+ def downcase_keys
78
+ self.class.case_insensitive_keys.each { |k| self[k].try(:downcase!) }
79
+ end
80
+
81
+ def strip_whitespace
82
+ self.class.strip_whitespace_keys.each { |k| self[k].try(:strip!) }
83
+ end
84
+
85
+ module ClassMethods
86
+ Revise::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys,
87
+ :case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage)
88
+
89
+ def find_for_authentication(conditions)
90
+ find_first_by_auth_conditions(conditions)
91
+ end
92
+
93
+ def find_first_by_auth_conditions(conditions)
94
+ to_adapter.find_first revise_param_filter.filter(conditions)
95
+ end
96
+
97
+ def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
98
+ find_or_initialize_with_errors([attribute], { attribute => value }, error)
99
+ end
100
+
101
+ # Find an initialize a group of attributes based on a list of required attributes.
102
+ def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
103
+ attributes = attributes.slice(*required_attributes)
104
+ attributes.delete_if { |key, value| value.blank? }
105
+
106
+ if attributes.size == required_attributes.size
107
+ record = find_first_by_auth_conditions(attributes)
108
+ end
109
+
110
+ unless record
111
+ record = new
112
+
113
+ required_attributes.each do |key|
114
+ value = attributes[key]
115
+ record.send("#{key}=", value)
116
+ record.errors.add(key, value.present? ? error : :blank)
117
+ end
118
+ end
119
+
120
+ record
121
+ end
122
+
123
+ protected
124
+ def revise_param_filter
125
+ @revise_param_filter ||= Revise::ParamFilter.new(case_insensitive_keys, strip_whitespace_keys)
126
+ end
127
+
128
+ def generate_token(column)
129
+ loop do
130
+ token = String.friendly_token
131
+ break token unless to_adapter.find_first({ column => token })
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,236 @@
1
+ module Revise
2
+ module Models
3
+ module Confirmable
4
+ extend ActiveSupport::Concern
5
+
6
+ MAILERS = ['Confirmable']
7
+ HELPERS = []
8
+ CONTROLLERS = ['Confirmations']
9
+
10
+ included do
11
+ before_create :generate_confirmation_token, :if => :confirmation_required?
12
+ after_create :send_on_create_confirmation_instructions, :if => :confirmation_required?
13
+ before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
14
+ after_update :send_confirmation_instructions, :if => :reconfirmation_required?
15
+ end
16
+
17
+ def self.required_fields(klass)
18
+ required_methods = [:confirmation_token, :confirmed_at, :confirmation_sent_at]
19
+ required_methods << :unconfirmed_email if klass.reconfirmable
20
+ required_methods
21
+ end
22
+
23
+ # Confirm a user by setting it's confirmed_at to actual time. If the user
24
+ # is already confirmed, add an error to email field. If the user is invalid
25
+ # add errors
26
+ def confirm!
27
+ pending_any_confirmation do
28
+ if confirmation_period_expired?
29
+ self.errors.add(:confirmation_period_expired, 'Token expired')
30
+ return false
31
+ end
32
+
33
+ self.confirmation_token = nil
34
+ self.confirmed_at = Time.now.utc
35
+
36
+ if self.class.reconfirmable && unconfirmed_email.present?
37
+ skip_reconfirmation!
38
+ self.email = unconfirmed_email
39
+ self.unconfirmed_email = nil
40
+
41
+ # We need to validate in such cases to enforce e-mail uniqueness
42
+ save(:validate => true)
43
+ else
44
+ save(:validate => false)
45
+ end
46
+ end
47
+ end
48
+
49
+ # Verifies whether a user is confirmed or not
50
+ def confirmed?
51
+ !!confirmed_at
52
+ end
53
+
54
+ def pending_reconfirmation?
55
+ self.class.reconfirmable && unconfirmed_email.present?
56
+ end
57
+
58
+ # Send confirmation instructions by email
59
+ def send_confirmation_instructions
60
+ self.confirmation_token = nil if reconfirmation_required?
61
+ @reconfirmation_required = false
62
+
63
+ generate_confirmation_token! if self.confirmation_token.blank?
64
+ send_revise_notification(:confirmable, :confirmation_instructions, self.name, self.email, self.confirmation_token)
65
+ end
66
+
67
+ # Resend confirmation token. This method does not need to generate a new token.
68
+ def resend_confirmation_token
69
+ pending_any_confirmation do
70
+ self.confirmation_token = nil if confirmation_period_expired?
71
+ send_confirmation_instructions
72
+ end
73
+ end
74
+
75
+ # Overwrites active_for_authentication? for confirmation
76
+ # by verifying whether a user is active to sign in or not. If the user
77
+ # is already confirmed, it should never be blocked. Otherwise we need to
78
+ # calculate if the confirm time has not expired for this user.
79
+ def active_for_authentication?
80
+ super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
81
+ end
82
+
83
+ # The message to be shown if the account is inactive.
84
+ def inactive_message
85
+ !confirmed? ? :unconfirmed : super
86
+ end
87
+
88
+ # If you don't want confirmation to be sent on create, neither a code
89
+ # to be generated, call skip_confirmation!
90
+ def skip_confirmation!
91
+ self.confirmed_at = Time.now.utc
92
+ end
93
+
94
+ # If you don't want reconfirmation to be sent, neither a code
95
+ # to be generated, call skip_reconfirmation!
96
+ def skip_reconfirmation!
97
+ @bypass_postpone = true
98
+ end
99
+
100
+ protected
101
+
102
+ # A callback method used to deliver confirmation
103
+ # instructions on creation. This can be overriden
104
+ # in models to map to a nice sign up e-mail.
105
+ def send_on_create_confirmation_instructions
106
+ send_revise_notification(:confirmable, :confirmation_instructions, self.name, self.email, self.confirmation_token)
107
+ end
108
+
109
+ # Callback to overwrite if confirmation is required or not.
110
+ def confirmation_required?
111
+ !confirmed?
112
+ end
113
+
114
+ # Checks if the confirmation for the user is within the limit time.
115
+ # We do this by calculating if the difference between today and the
116
+ # confirmation sent date does not exceed the confirm in time configured.
117
+ # Confirm_within is a model configuration, must always be an integer value.
118
+ #
119
+ # Example:
120
+ #
121
+ # # allow_unconfirmed_access_for = 1.day and confirmation_sent_at = today
122
+ # confirmation_period_valid? # returns true
123
+ #
124
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 4.days.ago
125
+ # confirmation_period_valid? # returns true
126
+ #
127
+ # # allow_unconfirmed_access_for = 5.days and confirmation_sent_at = 5.days.ago
128
+ # confirmation_period_valid? # returns false
129
+ #
130
+ # # allow_unconfirmed_access_for = 0.days
131
+ # confirmation_period_valid? # will always return false
132
+ #
133
+ def confirmation_period_valid?
134
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
135
+ end
136
+
137
+ # Checks if the user confirmation happens before the token becomes invalid
138
+ # Examples:
139
+ #
140
+ # # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
141
+ # confirmation_period_expired? # returns false
142
+ #
143
+ # # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
144
+ # confirmation_period_expired? # returns true
145
+ #
146
+ # # confirm_within = nil
147
+ # confirmation_period_expired? # will always return false
148
+ #
149
+ def confirmation_period_expired?
150
+ self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
151
+ end
152
+
153
+ # Checks whether the record requires any confirmation.
154
+ def pending_any_confirmation
155
+ if (!confirmed? || pending_reconfirmation?)
156
+ yield
157
+ else
158
+ self.errors.add(:email, :already_confirmed)
159
+ false
160
+ end
161
+ end
162
+
163
+ # Generates a new random token for confirmation, and stores the time
164
+ # this token is being generated
165
+ def generate_confirmation_token
166
+ self.confirmation_token = self.class.confirmation_token
167
+ self.confirmation_sent_at = Time.now.utc
168
+ end
169
+
170
+ def generate_confirmation_token!
171
+ generate_confirmation_token && save(:validate => false)
172
+ end
173
+
174
+ def after_password_reset
175
+ super
176
+ confirm! unless confirmed?
177
+ end
178
+
179
+ def postpone_email_change_until_confirmation
180
+ @reconfirmation_required = true
181
+ self.unconfirmed_email = self.email
182
+ self.email = self.email_was
183
+ end
184
+
185
+ def postpone_email_change?
186
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
187
+ @bypass_postpone = nil
188
+ postpone
189
+ end
190
+
191
+ def reconfirmation_required?
192
+ self.class.reconfirmable && @reconfirmation_required
193
+ end
194
+
195
+ module ClassMethods
196
+ # Attempt to find a user by its email. If a record is found, send new
197
+ # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
198
+ # field. If no user is found, returns a new user with an email not found error.
199
+ # Options must contain the user email
200
+ def send_confirmation_instructions(attributes={})
201
+ confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
202
+ unless confirmable.try(:persisted?)
203
+ confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
204
+ end
205
+ confirmable.resend_confirmation_token if confirmable.persisted?
206
+ confirmable
207
+ end
208
+
209
+ # Find a user by its confirmation token and try to confirm it.
210
+ # If no user is found, returns a new user with an error.
211
+ # If the user is already confirmed, create an error for the user
212
+ # Options must have the confirmation_token
213
+ def confirm_by_token(confirmation_token)
214
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
215
+ confirmable.confirm! if confirmable.persisted?
216
+ confirmable
217
+ end
218
+
219
+ # Generate a token checking if one does not already exist in the database.
220
+ def confirmation_token
221
+ generate_token(:confirmation_token)
222
+ end
223
+
224
+ # Find a record for confirmation by unconfirmed email field
225
+ def find_by_unconfirmed_email_with_errors(attributes = {})
226
+ unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
227
+ unconfirmed_attributes = attributes.symbolize_keys
228
+ unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
229
+ find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
230
+ end
231
+
232
+ Revise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
233
+ end
234
+ end
235
+ end
236
+ end