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