has_emails 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.
- data/CHANGELOG +23 -0
- data/MIT-LICENSE +20 -0
- data/README +130 -0
- data/Rakefile +81 -0
- data/app/mailers/application_mailer.rb +85 -0
- data/app/models/email.rb +84 -0
- data/app/models/email_address.rb +123 -0
- data/app/models/email_recipient.rb +78 -0
- data/app/models/email_recipient_build_extension.rb +7 -0
- data/db/bootstrap/events.yml +4 -0
- data/db/bootstrap/states.yml +9 -0
- data/db/migrate/001_create_email_addresses.rb +23 -0
- data/db/migrate/002_add_email_specs.rb +18 -0
- data/db/migrate/003_add_email_recipient_specs.rb +18 -0
- data/init.rb +1 -0
- data/lib/has_emails.rb +92 -0
- data/test/app_root/app/models/department.rb +2 -0
- data/test/app_root/app/models/user.rb +3 -0
- data/test/app_root/config/environment.rb +33 -0
- data/test/app_root/db/migrate/001_create_users.rb +11 -0
- data/test/app_root/db/migrate/002_create_departments.rb +12 -0
- data/test/fixtures/departments.yml +9 -0
- data/test/fixtures/email_addresses.yml +32 -0
- data/test/fixtures/message_recipients.yml +85 -0
- data/test/fixtures/messages.yml +52 -0
- data/test/fixtures/state_changes.yml +128 -0
- data/test/fixtures/users.yml +11 -0
- data/test/test_helper.rb +76 -0
- data/test/unit/application_mailer_test.rb +69 -0
- data/test/unit/email_address_test.rb +200 -0
- data/test/unit/email_recipient_test.rb +79 -0
- data/test/unit/email_test.rb +89 -0
- data/test/unit/has_emails_test.rb +36 -0
- data/test/unit/recipient_extension_test.rb +99 -0
- metadata +113 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*SVN*
|
2
|
+
|
3
|
+
*0.0.1* (September 26th, 2007)
|
4
|
+
|
5
|
+
* Refactor has_email_address/has_email_addresses so that new associations for unsent/sent emails isn't created for has_email_address
|
6
|
+
|
7
|
+
* Add state changes fixtures for tests
|
8
|
+
|
9
|
+
* Add tests for ApplicationMailer
|
10
|
+
|
11
|
+
* Add support for tracking the original email address for senders/recipients even if the email address changes on the associated model (i.e. EmailAddress/User/etc.)
|
12
|
+
|
13
|
+
* Support converting models with an email_address/email_addresses association
|
14
|
+
|
15
|
+
* Allow the sender of emails to be an arbitrary string email address
|
16
|
+
|
17
|
+
* Add documentation
|
18
|
+
|
19
|
+
* Move test fixtures out of the test application root directory
|
20
|
+
|
21
|
+
* Convert dos newlines to unix newlines
|
22
|
+
|
23
|
+
* Update against latest changes to has_messages
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006-2007 Aaron Pfeifer & Neil Abraham
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
= has_emails
|
2
|
+
|
3
|
+
+has_emails+ adds support for emailing capabilities between ActiveRecord models.
|
4
|
+
|
5
|
+
== Resources
|
6
|
+
|
7
|
+
API
|
8
|
+
|
9
|
+
* http://api.pluginaweek.org/has_emails
|
10
|
+
|
11
|
+
Wiki
|
12
|
+
|
13
|
+
* http://wiki.pluginaweek.org/Has_emails
|
14
|
+
|
15
|
+
Announcement
|
16
|
+
|
17
|
+
* http://www.pluginaweek.org
|
18
|
+
|
19
|
+
Source
|
20
|
+
|
21
|
+
* http://svn.pluginaweek.org/trunk/plugins/active_record/has/has_emails
|
22
|
+
|
23
|
+
Development
|
24
|
+
|
25
|
+
* http://dev.pluginaweek.org/browser/trunk/plugins/active_record/has/has_emails
|
26
|
+
|
27
|
+
== Description
|
28
|
+
|
29
|
+
Emailing between users and other parts of a system is a fairly common feature in
|
30
|
+
web applications, especially for those that support social networking. Emailing
|
31
|
+
doesn't necessarily need to be between users, but can also act as a way for the
|
32
|
+
web application to send notices and other notifications to users.
|
33
|
+
|
34
|
+
Rails already provides ActionMailer as a way of sending emails. However, the
|
35
|
+
framework does not provide an easy way to persist emails, track their status, and
|
36
|
+
process them asynchronously. Designing and building this type of framework can
|
37
|
+
become complex and cumbersome and takes away from the focus of the web application.
|
38
|
+
This plugin helps ease that process by providing a complete implementation for
|
39
|
+
sending and receiving emails to and between models in your application.
|
40
|
+
|
41
|
+
== Usage
|
42
|
+
|
43
|
+
=== Adding emailing support
|
44
|
+
|
45
|
+
If you want to use the built-in support for email addresses (using the EmailAddress
|
46
|
+
model), you can add emailing support for users like so:
|
47
|
+
|
48
|
+
class User < ActiveRecord::Base
|
49
|
+
has_email_address
|
50
|
+
# has_email_addresses if you want to support multiple addresses
|
51
|
+
end
|
52
|
+
|
53
|
+
On the other hand, if you already have the email address for users stored as a
|
54
|
+
column in your users table, you can add emailing support like so:
|
55
|
+
|
56
|
+
class User < ActiveRecord::Base
|
57
|
+
has_messages :emails,
|
58
|
+
:message_class => 'Email'
|
59
|
+
end
|
60
|
+
|
61
|
+
=== Creating new emails
|
62
|
+
|
63
|
+
email = user.emails.build
|
64
|
+
email.to << [user1, email_address1, 'someone@somewhere.com']
|
65
|
+
email.subject = 'Hey!'
|
66
|
+
email.body = 'Does anyone want to go out tonight?'
|
67
|
+
email.deliver!
|
68
|
+
|
69
|
+
As can be seen in the above example, you can use Users, EmailAddresses, or even
|
70
|
+
Strings as recipients in emails. Each will be automatically converted to the
|
71
|
+
EmailRecipient class so that it can be stored in the database.
|
72
|
+
|
73
|
+
=== Replying to emails
|
74
|
+
|
75
|
+
reply = email.reply_to_all
|
76
|
+
reply.body = "I'd love to go out!"
|
77
|
+
reply.deliver!
|
78
|
+
|
79
|
+
=== Forwarding emails
|
80
|
+
|
81
|
+
forward = email.forward
|
82
|
+
forward.body = 'Interested?'
|
83
|
+
forward.deliver!
|
84
|
+
|
85
|
+
=== External process messaging
|
86
|
+
|
87
|
+
In addition to delivering emails immediately, you can also *queue* emails so
|
88
|
+
that an external application processes and delivers them. This is especially
|
89
|
+
useful when you want to asynchronously send e-mails so that it doesn't block the
|
90
|
+
user interface on your web application.
|
91
|
+
|
92
|
+
To queue emails for external processing, you can simply use the <tt>queue!</tt>
|
93
|
+
event, rather than <tt>deliver!</tt>. This will indicate to any external processes
|
94
|
+
that the email is ready to be sent. The external process can then invoke <tt>deliver!</tt>
|
95
|
+
whenever it is ready to send the queued email.
|
96
|
+
|
97
|
+
=== Running migrations
|
98
|
+
|
99
|
+
To migrate the tables required for this plugin, you can either run the
|
100
|
+
migration from the command line like so:
|
101
|
+
|
102
|
+
rake db:migrate:plugins PLUGIN=has_emails
|
103
|
+
|
104
|
+
or (more ideally) generate a migration file that will integrate into your main
|
105
|
+
application's migration path:
|
106
|
+
|
107
|
+
ruby script/generate plugin_migration has_emails
|
108
|
+
|
109
|
+
== Testing
|
110
|
+
|
111
|
+
Before you can run any tests, the following gems must be installed:
|
112
|
+
* plugin_test_helper[http://wiki.pluginaweek.org/Plugin_test_helper]
|
113
|
+
* dry_validity_assertions[http://wiki.pluginaweek.org/Dry_validity_assertions]
|
114
|
+
|
115
|
+
== Dependencies
|
116
|
+
|
117
|
+
This plugin depends on the presence of the following plugins:
|
118
|
+
* has_messages[http://wiki.pluginaweek.org/Has_messages]
|
119
|
+
|
120
|
+
This plugin is also a plugin+. That means that it contains a slice of an
|
121
|
+
application, such as models and migrations. To test or use a plugin+, you
|
122
|
+
must have the following plugins/gems installed:
|
123
|
+
* plugin_dependencies[http://wiki.pluginaweek.org/Plugin_dependencies]
|
124
|
+
* loaded_plugins[http://wiki.pluginaweek.org/Loaded_plugins]
|
125
|
+
* appable_plugins[http://wiki.pluginaweek.org/Appable_plugins]
|
126
|
+
* plugin_migrations[http://wiki.pluginaweek.org/Plugin_migrations]
|
127
|
+
|
128
|
+
Instead of installing each individual plugin+ feature, you can install them all
|
129
|
+
at once using the plugins+[http://wiki.pluginaweek.org/Plugins_plus] meta package,
|
130
|
+
which contains all additional features.
|
data/Rakefile
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/sshpublisher'
|
5
|
+
|
6
|
+
PKG_NAME = 'has_emails'
|
7
|
+
PKG_VERSION = '0.0.1'
|
8
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
9
|
+
RUBY_FORGE_PROJECT = 'pluginaweek'
|
10
|
+
|
11
|
+
desc 'Default: run unit tests.'
|
12
|
+
task :default => :test
|
13
|
+
|
14
|
+
desc 'Test the has_emails plugin.'
|
15
|
+
Rake::TestTask.new(:test) do |t|
|
16
|
+
t.libs << 'lib'
|
17
|
+
t.pattern = 'test/**/*_test.rb'
|
18
|
+
t.verbose = true
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Generate documentation for the has_emails plugin.'
|
22
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
23
|
+
rdoc.rdoc_dir = 'rdoc'
|
24
|
+
rdoc.title = 'HasEmails'
|
25
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
26
|
+
rdoc.rdoc_files.include('README')
|
27
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
28
|
+
end
|
29
|
+
|
30
|
+
spec = Gem::Specification.new do |s|
|
31
|
+
s.name = PKG_NAME
|
32
|
+
s.version = PKG_VERSION
|
33
|
+
s.platform = Gem::Platform::RUBY
|
34
|
+
s.summary = 'Adds support for emailing capabilities between ActiveRecord models.'
|
35
|
+
|
36
|
+
s.files = FileList['{app,db,lib,test}/**/*'].to_a + %w(CHANGELOG init.rb MIT-LICENSE Rakefile README)
|
37
|
+
s.require_path = 'lib'
|
38
|
+
s.autorequire = 'has_emails'
|
39
|
+
s.has_rdoc = true
|
40
|
+
s.test_files = Dir['test/**/*_test.rb']
|
41
|
+
s.add_dependency 'has_messages', '>= 0.0.1'
|
42
|
+
s.add_dependency 'validates_as_email_address', '>= 0.0.1'
|
43
|
+
|
44
|
+
s.author = 'Aaron Pfeifer, Neil Abraham'
|
45
|
+
s.email = 'info@pluginaweek.org'
|
46
|
+
s.homepage = 'http://www.pluginaweek.org'
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::GemPackageTask.new(spec) do |p|
|
50
|
+
p.gem_spec = spec
|
51
|
+
p.need_tar = true
|
52
|
+
p.need_zip = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'Publish the beta gem'
|
56
|
+
task :pgem => [:package] do
|
57
|
+
Rake::SshFilePublisher.new('pluginaweek@pluginaweek.org', '/home/pluginaweek/gems.pluginaweek.org/gems', 'pkg', "#{PKG_FILE_NAME}.gem").upload
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'Publish the API documentation'
|
61
|
+
task :pdoc => [:rdoc] do
|
62
|
+
Rake::SshDirPublisher.new('pluginaweek@pluginaweek.org', "/home/pluginaweek/api.pluginaweek.org/#{PKG_NAME}", 'rdoc').upload
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Publish the API docs and gem'
|
66
|
+
task :publish => [:pdoc, :release]
|
67
|
+
|
68
|
+
desc 'Publish the release files to RubyForge.'
|
69
|
+
task :release => [:gem, :package] do
|
70
|
+
require 'rubyforge'
|
71
|
+
|
72
|
+
ruby_forge = RubyForge.new
|
73
|
+
ruby_forge.login
|
74
|
+
|
75
|
+
%w( gem tgz zip ).each do |ext|
|
76
|
+
file = "pkg/#{PKG_FILE_NAME}.#{ext}"
|
77
|
+
puts "Releasing #{File.basename(file)}..."
|
78
|
+
|
79
|
+
ruby_forge.add_release(RUBY_FORGE_PROJECT, PKG_NAME, PKG_VERSION, file)
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Provides base operations for emailing
|
2
|
+
class ApplicationMailer < ActionMailer::Base
|
3
|
+
@@default_subject_prefix = "[#{File.basename(File.expand_path(RAILS_ROOT)).camelize}] "
|
4
|
+
cattr_accessor :default_subject_prefix
|
5
|
+
|
6
|
+
# Specify the prefix to use for the subject. This defaults to the
|
7
|
+
# +default_subject_prefix+ specified for ApplicationMailer.
|
8
|
+
adv_attr_accessor :subject_prefix
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def method_missing(method_symbol, *parameters) #:nodoc:
|
12
|
+
case method_symbol.id2name
|
13
|
+
when /^queue_([_a-z]\w*)/ then new($1, *parameters).queue
|
14
|
+
else super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :to, :recipients
|
20
|
+
|
21
|
+
# Sets or gets the subject of the email. All subjects are prefixed with a
|
22
|
+
# value indicating the application it is coming from.
|
23
|
+
def subject(*parameters)
|
24
|
+
if parameters.empty?
|
25
|
+
super
|
26
|
+
else
|
27
|
+
super(subject_prefix + super)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :subject=, :subject
|
31
|
+
|
32
|
+
# Delivers an email based on the content in the specified email
|
33
|
+
def email(email)
|
34
|
+
@from = email.sender
|
35
|
+
@recipients = email.to
|
36
|
+
@cc = email.cc
|
37
|
+
@bcc = email.bcc
|
38
|
+
@subject = email.subject
|
39
|
+
@body = email.body
|
40
|
+
@sent_on = email.sent_at || Time.now
|
41
|
+
end
|
42
|
+
|
43
|
+
# Queues the current e-mail that has been constructed
|
44
|
+
def queue
|
45
|
+
Email.transaction do
|
46
|
+
# Create the main email
|
47
|
+
email = Email.create!(
|
48
|
+
:sender => from,
|
49
|
+
:subject => subject,
|
50
|
+
:body => body
|
51
|
+
)
|
52
|
+
|
53
|
+
# Add recipients
|
54
|
+
email.to = [to].flatten
|
55
|
+
email.cc = [cc].flatten
|
56
|
+
email.bcc = [bcc].flatten
|
57
|
+
email.queue!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def initialize_defaults(method_name) #:nodoc
|
63
|
+
@sent_on ||= Time.now
|
64
|
+
@subject_prefix ||= @@default_subject_prefix.dup
|
65
|
+
@recipients ||= []
|
66
|
+
@cc ||= []
|
67
|
+
@bcc ||= []
|
68
|
+
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
def quote_address_if_necessary_with_conversion(address, charset) #:nodoc
|
73
|
+
# Uses is_a? instead of === because of AssociationProxy
|
74
|
+
if !address.is_a?(Array)
|
75
|
+
if EmailAddress === address || EmailRecipient === address
|
76
|
+
address = address.with_name
|
77
|
+
elsif !(String === address)
|
78
|
+
address = EmailAddress.convert_from(address).to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
quote_address_if_necessary_without_conversion(address, charset)
|
83
|
+
end
|
84
|
+
alias_method_chain :quote_address_if_necessary, :conversion
|
85
|
+
end
|
data/app/models/email.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Represents an email which has been sent to one or more recipients. This is
|
2
|
+
# essentially the same as the Message class, but overrides the +to+, +cc+, and +bcc+
|
3
|
+
# associations to that proper instances of the MessageRecipient class are created.
|
4
|
+
class Email < Message
|
5
|
+
validates_presence_of :sender_spec
|
6
|
+
validates_as_email_address :sender_spec,
|
7
|
+
:allow_nil => true
|
8
|
+
|
9
|
+
with_options(
|
10
|
+
:class_name => 'EmailRecipient',
|
11
|
+
:foreign_key => 'message_id',
|
12
|
+
:order => 'position ASC',
|
13
|
+
:dependent => true
|
14
|
+
) do |e|
|
15
|
+
e.has_many :to,
|
16
|
+
:conditions => ['kind = ?', 'to'],
|
17
|
+
:extend => [MessageRecipientToBuildExtension, EmailRecipientBuildExtension]
|
18
|
+
e.has_many :cc,
|
19
|
+
:conditions => ['kind = ?', 'cc'],
|
20
|
+
:extend => [MessageRecipientCcBuildExtension, EmailRecipientBuildExtension]
|
21
|
+
e.has_many :bcc,
|
22
|
+
:conditions => ['kind = ?', 'bcc'],
|
23
|
+
:extend => [MessageRecipientBccBuildExtension, EmailRecipientBuildExtension]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the sender of the message. This can be a string if being sent
|
27
|
+
# from an arbitrary e-mail address.
|
28
|
+
def sender_with_spec
|
29
|
+
sender_without_spec || sender_spec
|
30
|
+
end
|
31
|
+
alias_method_chain :sender, :spec
|
32
|
+
|
33
|
+
# If sender is a string, then sets the spec, otherwise uses the original
|
34
|
+
# sender setter
|
35
|
+
def sender_with_spec=(value)
|
36
|
+
self.sender_spec = EmailAddress.convert_from(value).spec
|
37
|
+
self.sender_without_spec = value if !value.is_a?(String)
|
38
|
+
end
|
39
|
+
alias_method_chain :sender=, :spec
|
40
|
+
|
41
|
+
# Converts the sender into an Email Address, whether it be a string,
|
42
|
+
# EmailAddress, or other model type
|
43
|
+
def sender_email_address
|
44
|
+
EmailAddress.convert_from(sender_spec)
|
45
|
+
end
|
46
|
+
|
47
|
+
# The name of the person whose sending the email
|
48
|
+
def sender_name
|
49
|
+
sender_without_spec ? EmailAddress.convert_from(sender_without_spec).name : sender_email_address.name
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a string version of the email address plus any name like
|
53
|
+
# "John Doe <john.doe@gmail.com>"..
|
54
|
+
def sender_with_name
|
55
|
+
address = self.email_address
|
56
|
+
address.name = self.name
|
57
|
+
address.with_name
|
58
|
+
end
|
59
|
+
|
60
|
+
# Actually delivers the email to the recipients
|
61
|
+
def deliver
|
62
|
+
ApplicationMailer.deliver_email(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Saves the +sender_spec+ in the forwarded message
|
66
|
+
def forward
|
67
|
+
message = super
|
68
|
+
message.sender_spec = sender_spec
|
69
|
+
message
|
70
|
+
end
|
71
|
+
|
72
|
+
# Saves the +sender_spec+ in the replied message
|
73
|
+
def reply
|
74
|
+
message = super
|
75
|
+
message.sender_spec = sender_spec
|
76
|
+
message
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
# Strings are allowed to participate in messaging
|
81
|
+
def model_participant?
|
82
|
+
sender_id && sender_type || sender_spec.nil?
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Represents a valid RFC822 email address
|
2
|
+
class EmailAddress < ActiveRecord::Base
|
3
|
+
class << self
|
4
|
+
# Converts the specified record to an EmailAddress. It will convert the
|
5
|
+
# following types:
|
6
|
+
# 1. String
|
7
|
+
# 2. A record with an +email_address+ String attribute
|
8
|
+
# 3. A record with an +email_address+ association
|
9
|
+
# 4. A record with an +email_addresses+ association (first record will be chosen)
|
10
|
+
#
|
11
|
+
# If an EmailAddress is specified, the same model will be returned. An
|
12
|
+
# ArgumentError is raised if it doesn't match any of the above criteria.
|
13
|
+
def convert_from(record)
|
14
|
+
if record
|
15
|
+
if EmailAddress === record
|
16
|
+
record
|
17
|
+
elsif String === record
|
18
|
+
EmailAddress.new(:spec => record)
|
19
|
+
elsif record.respond_to?(:email_address)
|
20
|
+
if record.email_address.is_a?(String)
|
21
|
+
address = EmailAddress.new(:spec => record.email_address)
|
22
|
+
address.name = record.name if record.respond_to?(:name)
|
23
|
+
address
|
24
|
+
else
|
25
|
+
record.email_address
|
26
|
+
end
|
27
|
+
elsif record.respond_to?(:email_addresses)
|
28
|
+
record.email_addresses.first
|
29
|
+
else
|
30
|
+
raise ArgumentError, "Cannot convert #{record.class} to an EmailAddress"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determines if the given spec is a valid address using the RFC822 spec
|
36
|
+
def valid?(spec)
|
37
|
+
!RFC822::EmailAddress.match(spec).nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
acts_as_tokenized :token_field => 'verification_code', :token_length => 32
|
42
|
+
|
43
|
+
# Support e-mail address verification
|
44
|
+
has_states :initial => :unverified
|
45
|
+
|
46
|
+
# Add messaging capabilities. This will give us an email_box.
|
47
|
+
has_messages :emails,
|
48
|
+
:message_class => 'Email'
|
49
|
+
belongs_to :emailable,
|
50
|
+
:polymorphic => true
|
51
|
+
|
52
|
+
validates_presence_of :spec
|
53
|
+
|
54
|
+
with_options(:allow_nil => true) do |klass|
|
55
|
+
klass.validates_uniqueness_of :spec
|
56
|
+
klass.validates_as_email_address :spec
|
57
|
+
end
|
58
|
+
|
59
|
+
# The name of the person who owns this email address
|
60
|
+
attr_accessor :name
|
61
|
+
|
62
|
+
# Ensure that the e-mail address has a verification code that can be sent
|
63
|
+
# to the user
|
64
|
+
before_create :set_code_expiry
|
65
|
+
|
66
|
+
state :unverified, :verified
|
67
|
+
|
68
|
+
# Verifies that the email address is valid
|
69
|
+
event :verify do
|
70
|
+
transition_to :verified, :from => :unverified
|
71
|
+
end
|
72
|
+
|
73
|
+
# Sets the full address
|
74
|
+
def spec=(new_spec)
|
75
|
+
@local_name = @domain = nil
|
76
|
+
write_attribute(:spec, new_spec)
|
77
|
+
end
|
78
|
+
|
79
|
+
# The part of the e-mail address before the @
|
80
|
+
def local_name
|
81
|
+
parse_spec if !@local_name
|
82
|
+
@local_name
|
83
|
+
end
|
84
|
+
|
85
|
+
# The part of the e-mail address after the @
|
86
|
+
def domain
|
87
|
+
parse_spec if !@domain
|
88
|
+
@domain
|
89
|
+
end
|
90
|
+
|
91
|
+
# Gets the name of the person whose email address this is. Default is an
|
92
|
+
# empty string. Override this if you want it to appear in with_name
|
93
|
+
def name
|
94
|
+
@name || ''
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns a string version of the email address plus any name like
|
98
|
+
# "John Doe <john.doe@gmail.com>". In order to have a valid name within the
|
99
|
+
# string, you must override +name+.
|
100
|
+
def with_name
|
101
|
+
name.blank? ? to_s : "#{name} <#{to_s}>"
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns the full email address (without the name)
|
105
|
+
def to_s #:nodoc
|
106
|
+
spec
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
# Sets the time at which the verification code will expire
|
111
|
+
def set_code_expiry
|
112
|
+
self.code_expiry = 48.hour.from_now
|
113
|
+
end
|
114
|
+
|
115
|
+
# Parses the current spec and sets +@local_name+ and +@domain+ based on the
|
116
|
+
# matching groups within the regular expression
|
117
|
+
def parse_spec
|
118
|
+
if !@local_name && !@domain && match = RFC822::EmailAddress.match(spec)
|
119
|
+
@local_name = match.captures[0]
|
120
|
+
@domain = match.captures[1]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Represents a recipient on an email
|
2
|
+
class EmailRecipient < MessageRecipient
|
3
|
+
validates_presence_of :receiver_spec
|
4
|
+
validates_as_email_address :receiver_spec,
|
5
|
+
:allow_nil => true
|
6
|
+
|
7
|
+
before_save :ensure_exclusive_references
|
8
|
+
|
9
|
+
# Alias for domain-specific language
|
10
|
+
alias_method :email, :message
|
11
|
+
alias_method :email=, :message=
|
12
|
+
alias_attribute :email_id, :message_id
|
13
|
+
|
14
|
+
delegate :to_s,
|
15
|
+
:to => :email_address
|
16
|
+
|
17
|
+
# Returns the receiver of the message. This can be a string if being sent
|
18
|
+
# to an arbitrary e-mail address.
|
19
|
+
def receiver_with_spec
|
20
|
+
receiver_without_spec || receiver_spec
|
21
|
+
end
|
22
|
+
alias_method_chain :receiver, :spec
|
23
|
+
|
24
|
+
# If receiver is a string, then sets the spec, otherwise uses the original
|
25
|
+
# receiver setter
|
26
|
+
def receiver_with_spec=(value)
|
27
|
+
self.receiver_spec = EmailAddress.convert_from(value).spec
|
28
|
+
self.receiver_without_spec = value if !value.is_a?(String)
|
29
|
+
end
|
30
|
+
alias_method_chain :receiver=, :spec
|
31
|
+
|
32
|
+
# Converts the receiver into an Email Address, whether it be a string,
|
33
|
+
# EmailAddress, or other model type
|
34
|
+
def email_address
|
35
|
+
EmailAddress.convert_from(receiver_spec)
|
36
|
+
end
|
37
|
+
|
38
|
+
# The name of the person whose receiving the email
|
39
|
+
def name
|
40
|
+
receiver_without_spec ? EmailAddress.convert_from(receiver_without_spec).name : email_address.name
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns a string version of the email address plus any name like
|
44
|
+
# "John Doe <john.doe@gmail.com>"..
|
45
|
+
def with_name
|
46
|
+
address = self.email_address
|
47
|
+
address.name = self.name
|
48
|
+
address.with_name
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def validate_on_create #:nodoc:
|
53
|
+
begin
|
54
|
+
email_address if receiver
|
55
|
+
true
|
56
|
+
rescue ArgumentError
|
57
|
+
errors.add 'receiver_id', 'must be a string, have a email_address attribute, or be a class that has_email_addresses'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Strings are allowed to participate in messaging
|
62
|
+
def model_participant?
|
63
|
+
receiver_id && receiver_type || receiver_spec.nil?
|
64
|
+
end
|
65
|
+
|
66
|
+
# Ensures that the country id/user region combo is not set at the same time as
|
67
|
+
# the region id
|
68
|
+
def ensure_exclusive_references
|
69
|
+
if model_participant?
|
70
|
+
self.receiver_spec = nil
|
71
|
+
else
|
72
|
+
self.receiver_id = nil
|
73
|
+
self.receiver_type = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
true
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module EmailRecipientBuildExtension #:nodoc:
|
2
|
+
# Checks if the recipient and record are equal, using the recipient's
|
3
|
+
# email_address
|
4
|
+
def is_recipient_equal?(recipient, record) #:nodoc:
|
5
|
+
recipient.email_address.to_s == EmailAddress.convert_from(record).to_s
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CreateEmailAddresses < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :email_addresses do |t|
|
4
|
+
t.column :emailable_id, :integer, :null => false, :references => nil
|
5
|
+
t.column :emailable_type, :string, :null => false
|
6
|
+
t.column :spec, :string, :null => false, :limit => 382
|
7
|
+
t.column :verification_code, :string, :limit => 40
|
8
|
+
t.column :code_expiry, :datetime
|
9
|
+
t.column :created_at, :timestamp, :null => false
|
10
|
+
t.column :updated_at, :datetime, :null => false
|
11
|
+
end
|
12
|
+
add_index :email_addresses, :spec, :unique => true
|
13
|
+
add_index :email_addresses, :verification_code, :unique => true
|
14
|
+
|
15
|
+
PluginAWeek::Has::States.migrate_up(:email_addresses)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
PluginAWeek::Has::States.migrate_down(:email_addresses)
|
20
|
+
|
21
|
+
drop_table :email_addresses
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class AddEmailSpecs < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
# Workaround change_column not allowing change to :null => true
|
4
|
+
remove_column :messages, :sender_id
|
5
|
+
remove_column :messages, :sender_type
|
6
|
+
|
7
|
+
add_column :messages, :sender_id, :integer, :null => true, :default => nil, :references => nil
|
8
|
+
add_column :messages, :sender_type, :string, :null => true, :default => nil
|
9
|
+
add_column :messages, :sender_spec, :string, :limit => 320
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
remove_column :messages, :sender_spec
|
14
|
+
|
15
|
+
change_column :messages, :sender_id, :integer, :null => false, :references => nil
|
16
|
+
change_column :messages, :sender_type, :string, :null => false
|
17
|
+
end
|
18
|
+
end
|