pluginaweek-has_emails 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,49 @@
1
+ == master
2
+
3
+ == 0.3.0 / 2009-04-19
4
+
5
+ * Add compatibility with has_messages 0.4.0 / Rails 2.3
6
+
7
+ == 0.2.1 / 2009-01-11
8
+
9
+ * Add compatibility with has_messages 0.3.1 / state_machine 0.5.0
10
+
11
+ == 0.2.0 / 2008-12-14
12
+
13
+ * Remove the PluginAWeek namespace
14
+
15
+ == 0.1.4 / 2008-10-26
16
+
17
+ * Add compatibility with has_messages 0.2.0
18
+ * Change how the base module is included to prevent namespacing conflicts
19
+
20
+ == 0.1.3 / 2008-09-07
21
+
22
+ * Add compatibility with state_machine 0.3.0
23
+
24
+ == 0.1.2 / 2008-06-29
25
+
26
+ * Add compatibility with has_messages 0.1.2
27
+
28
+ == 0.1.1 / 2008-06-22
29
+
30
+ * Remove log files from gems
31
+
32
+ == 0.1.0 / 2008-05-05
33
+
34
+ * Update to latest has_messages api
35
+ * Simplify by removing support for models other than EmailAddresses in Emails
36
+ * Updated documentation
37
+
38
+ == 0.0.1 / 2007-09-26
39
+
40
+ * Refactor has_email_address/has_email_addresses so that new associations for unsent/sent emails isn't created for has_email_address
41
+ * Add state changes fixtures for tests
42
+ * Add tests for ApplicationMailer
43
+ * 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.)
44
+ * Support converting models with an email_address/email_addresses association
45
+ * Allow the sender of emails to be an arbitrary string email address
46
+ * Add documentation
47
+ * Move test fixtures out of the test application root directory
48
+ * Convert dos newlines to unix newlines
49
+ * Update against latest changes to has_messages
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2009 Aaron Pfeifer
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.rdoc ADDED
@@ -0,0 +1,96 @@
1
+ = has_emails
2
+
3
+ +has_emails+ demonstrates a reference implementation for sending emails with
4
+ logging and asynchronous support.
5
+
6
+ == Resources
7
+
8
+ API
9
+
10
+ * http://api.pluginaweek.org/has_emails
11
+
12
+ Bugs
13
+
14
+ * http://pluginaweek.lighthouseapp.com/projects/13272-has_emails
15
+
16
+ Development
17
+
18
+ * http://github.com/pluginaweek/has_emails
19
+
20
+ Source
21
+
22
+ * git://github.com/pluginaweek/has_emails.git
23
+
24
+ == Description
25
+
26
+ Emailing between users and other parts of a system is a fairly common feature
27
+ in web applications, especially for those that support social networking.
28
+ Emailing doesn't necessarily need to be between users, but can also act as a
29
+ way for the web application to send notices and other notifications to users.
30
+
31
+ Rails already provides ActionMailer as a way of sending emails. However, the
32
+ framework does not provide an easy way to persist emails, track their status,
33
+ and process them asynchronously. Designing and building a framework that
34
+ supports this can be complex and takes away from the business focus. This
35
+ plugin can help ease that process by demonstrating a reference implementation
36
+ of these features.
37
+
38
+ == Usage
39
+
40
+ === Creating new emails
41
+
42
+ Emails should usually still be created using ActionMailer. However, instead of
43
+ delivering the emails, you can queue the emails like so:
44
+
45
+ Notifier.deliver_signup_notification(david) # sends the email now
46
+ Notifier.queue_signup_notification(david) # sends the email later (has_emails kicks in)
47
+
48
+ In addition to queueing emails, you can build them directly like so:
49
+
50
+ email_address = EmailAddress.find(123)
51
+ email = email_address.emails.build
52
+ email.to EmailAddress.find(456)
53
+ email.subject = 'Hey!'
54
+ email.body = 'Does anyone want to go out tonight?'
55
+ email.deliver
56
+
57
+ === Replying to emails
58
+
59
+ reply = email.reply_to_all
60
+ reply.body = "I'd love to go out!"
61
+ reply.deliver
62
+
63
+ === Forwarding emails
64
+
65
+ forward = email.forward
66
+ forward.body = 'Interested?'
67
+ forward.deliver
68
+
69
+ === Processing email asynchronously
70
+
71
+ In addition to delivering emails immediately, you can also *queue* emails so
72
+ that an external application processes and delivers them (as mentioned above).
73
+ This is especially useful when you want to asynchronously send e-mails so that
74
+ it doesn't block the user interface on your web application.
75
+
76
+ To process queued emails, you need an external cron job that checks and sends
77
+ them like so:
78
+
79
+ Email.with_state('queued').each do |email|
80
+ email.deliver
81
+ end
82
+
83
+ == Testing
84
+
85
+ Before you can run any tests, the following gem must be installed:
86
+ * plugin_test_helper[http://github.com/pluginaweek/plugin_test_helper]
87
+
88
+ To run against a specific version of Rails:
89
+
90
+ rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
91
+
92
+ == Dependencies
93
+
94
+ * Rails 2.3 or later
95
+ * has_messages[http://github.com/pluginaweek/has_messages]
96
+ * state_machine[http://github.com/pluginaweek/state_machine]
data/Rakefile ADDED
@@ -0,0 +1,98 @@
1
+ require 'rake/testtask'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/sshpublisher'
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = 'has_emails'
8
+ s.version = '0.3.0'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Demonstrates a reference implementation for sending emails with logging and asynchronous support in ActiveRecord'
11
+ s.description = s.summary
12
+
13
+ s.files = FileList['{app,db,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
14
+ s.require_path = 'lib'
15
+ s.has_rdoc = true
16
+ s.test_files = Dir['test/**/*_test.rb']
17
+ s.add_dependency 'has_messages', '>= 0.4.0'
18
+ s.add_dependency 'validates_as_email_address', '>= 0.0.2'
19
+
20
+ s.author = 'Aaron Pfeifer'
21
+ s.email = 'aaron@pluginaweek.org'
22
+ s.homepage = 'http://www.pluginaweek.org'
23
+ s.rubyforge_project = 'pluginaweek'
24
+ end
25
+
26
+ desc 'Default: run all tests.'
27
+ task :default => :test
28
+
29
+ desc "Test the #{spec.name} plugin."
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.test_files = spec.test_files
33
+ t.verbose = true
34
+ end
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ namespace :test do
39
+ desc "Test the #{spec.name} plugin with Rcov."
40
+ Rcov::RcovTask.new(:rcov) do |t|
41
+ t.libs << 'lib'
42
+ t.test_files = spec.test_files
43
+ t.rcov_opts << '--exclude="^(?!lib/|app/)"'
44
+ t.verbose = true
45
+ end
46
+ end
47
+ rescue LoadError
48
+ end
49
+
50
+ desc "Generate documentation for the #{spec.name} plugin."
51
+ Rake::RDocTask.new(:rdoc) do |rdoc|
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = spec.name
54
+ rdoc.template = '../rdoc_template.rb'
55
+ rdoc.options << '--line-numbers' << '--inline-source'
56
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb', 'app/**/*.rb')
57
+ end
58
+
59
+ desc 'Generate a gemspec file.'
60
+ task :gemspec do
61
+ File.open("#{spec.name}.gemspec", 'w') do |f|
62
+ f.write spec.to_ruby
63
+ end
64
+ end
65
+
66
+ Rake::GemPackageTask.new(spec) do |p|
67
+ p.gem_spec = spec
68
+ p.need_tar = true
69
+ p.need_zip = true
70
+ end
71
+
72
+ desc 'Publish the beta gem.'
73
+ task :pgem => [:package] do
74
+ Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
75
+ end
76
+
77
+ desc 'Publish the API documentation.'
78
+ task :pdoc => [:rdoc] do
79
+ Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
80
+ end
81
+
82
+ desc 'Publish the API docs and gem'
83
+ task :publish => [:pgem, :pdoc, :release]
84
+
85
+ desc 'Publish the release files to RubyForge.'
86
+ task :release => [:gem, :package] do
87
+ require 'rubyforge'
88
+
89
+ ruby_forge = RubyForge.new.configure
90
+ ruby_forge.login
91
+
92
+ %w(gem tgz zip).each do |ext|
93
+ file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
94
+ puts "Releasing #{File.basename(file)}..."
95
+
96
+ ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
97
+ end
98
+ end
@@ -0,0 +1,14 @@
1
+ # Represents an email which has been sent to one or more recipients. This is
2
+ # essentially the same as the Message class, but changes how the it is
3
+ # delivered.
4
+ class Email < Message
5
+ state_machine :state do
6
+ after_transition :on => :deliver, :do => :deliver_email
7
+ end
8
+
9
+ private
10
+ # Actually delivers the email to the recipients using ActionMailer
11
+ def deliver_email
12
+ ActionMailer::Base.deliver_email(self)
13
+ end
14
+ end
@@ -0,0 +1,62 @@
1
+ # Represents a valid RFC822 email address. See http://www.w3.org/Protocols/rfc822/
2
+ # for more information about the entire specification.
3
+ #
4
+ # Email addresses are directly associated with emails and, therefore, should be
5
+ # used for building and delivering new e-mails.
6
+ #
7
+ # == Associations
8
+ #
9
+ # Email addresses have the following associations defined as a result of using
10
+ # the +has_emails+ macro:
11
+ # * +emails+ - Emails that were composed and are visible to the owner. Emails
12
+ # may have been sent or unsent.
13
+ # * +received_emails+ - Emails that have been received from others and are
14
+ # visible. Emails may have been read or unread.
15
+ # * +unsent_emails+ - Emails that have not yet been delivered
16
+ # * +sent_emails+ - Emails that have already been delivered
17
+ class EmailAddress < ActiveRecord::Base
18
+ has_emails
19
+
20
+ validates_presence_of :spec
21
+ validates_as_email_address :spec
22
+ validates_uniqueness_of :spec, :scope => 'name'
23
+
24
+ class << self
25
+ # Finds or create an email address based on the given value
26
+ def find_or_create_by_address(address)
27
+ name, spec = split_address(address)
28
+ find_or_create_by_name_and_spec(name, spec)
29
+ end
30
+
31
+ # Splits the given address into a name and spec. For example,
32
+ #
33
+ # EmailAddress.split_address("John Smith <john.smith@gmail.com") # => ["John Smith", "john.smith@gmail.com"]
34
+ # EmailAddress.split_address("john.smith@gmail.com") # => [nil, "john.smith@gmail.com"]
35
+ def split_address(address)
36
+ if match = /^(\S.*)\s+<(.*)>$/.match(address)
37
+ name = match[1]
38
+ spec = match[2]
39
+ else
40
+ spec = address
41
+ end
42
+
43
+ return name, spec
44
+ end
45
+ end
46
+
47
+ # Sets the value to be used for this email address. This can come in two formats:
48
+ # * With name - John Doe <john.doe@gmail.com>
49
+ # * Without name - john.doe@gmail.com
50
+ def address=(address)
51
+ self.name, self.spec = self.class.split_address(address)
52
+ end
53
+
54
+ # Generates the value for the email address, including the name associated with
55
+ # it (if provided). For example,
56
+ #
57
+ # e = EmailAddress.new(:name => 'John Doe', :spec => 'john.doe@gmail.com')
58
+ # e.with_name # => "John Doe <john.doe@gmail.com>"
59
+ def with_name
60
+ name.blank? ? spec : "#{name} <#{spec}>"
61
+ end
62
+ end
@@ -0,0 +1,14 @@
1
+ class CreateEmailAddresses < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :email_addresses do |t|
4
+ t.string :spec, :null => false, :limit => 382
5
+ t.string :name
6
+ t.timestamps
7
+ end
8
+ add_index :email_addresses, [:spec, :name], :unique => true
9
+ end
10
+
11
+ def self.down
12
+ drop_table :email_addresses
13
+ end
14
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'has_emails'
data/lib/has_emails.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'has_messages'
2
+ require 'validates_as_email_address'
3
+ require 'has_emails/extensions/action_mailer'
4
+
5
+ # Adds a generic implementation for sending emails
6
+ module HasEmails
7
+ module MacroMethods
8
+ # Creates the following email associations:
9
+ # * +emails+ - Emails that were composed and are visible to the owner.
10
+ # Emails may have been sent or unsent.
11
+ # * +received_emails - Emails that have been received from others and are
12
+ # visible. Emails may have been read or unread.
13
+ #
14
+ # == Creating new emails
15
+ #
16
+ # To create a new email, the +emails+ association should be used. For
17
+ # example:
18
+ #
19
+ # address = EmailAddress.find(123)
20
+ # email = user.emails.build
21
+ # email.subject = 'Hello'
22
+ # email.body = 'How are you?'
23
+ # email.to EmailAddress.find(456)
24
+ # email.save!
25
+ # email.deliver!
26
+ def has_emails
27
+ has_many :emails,
28
+ :as => :sender,
29
+ :class_name => 'Email',
30
+ :conditions => {:hidden_at => nil},
31
+ :order => 'messages.created_at ASC'
32
+ has_many :received_emails,
33
+ :as => :receiver,
34
+ :class_name => 'MessageRecipient',
35
+ :include => :message,
36
+ :conditions => ['message_recipients.hidden_at IS NULL AND messages.state = ?', 'sent'],
37
+ :order => 'messages.created_at ASC'
38
+
39
+ include HasEmails::InstanceMethods
40
+ end
41
+ end
42
+
43
+ module InstanceMethods
44
+ # Composed emails that have not yet been sent. These consists of all
45
+ # emails that are currently in the "unsent" state.
46
+ def unsent_emails
47
+ emails.with_state(:unsent)
48
+ end
49
+
50
+ # Composed emails that have already been sent. These consist of all emails
51
+ # that are currently in the "queued" or "sent states.
52
+ def sent_emails
53
+ emails.with_states(:queued, :sent)
54
+ end
55
+ end
56
+ end
57
+
58
+ ActiveRecord::Base.class_eval do
59
+ extend HasEmails::MacroMethods
60
+ end
@@ -0,0 +1,117 @@
1
+ module HasEmails
2
+ module Extensions #:nodoc:
3
+ # Adds support for queueing emails so that they can be procssed in the
4
+ # background. Emails are stored in the database using the Email ActiveRecord
5
+ # model.
6
+ #
7
+ # == Queueing mail
8
+ #
9
+ # Once a mailer action and template are defined, you can queue your message
10
+ # for background processing like so:
11
+ #
12
+ # Notifier.queue_signup_notification(john_smith) # Queues the email
13
+ #
14
+ # If you were to deliver the mail immediately, the normal process would be
15
+ # used like so:
16
+ #
17
+ # Notifier.deliver_signup_notification(john_smith) # Delivers the email
18
+ module ActionMailer
19
+ def self.included(base) #:nodoc:
20
+ base.class_eval do
21
+ @@default_subject_prefix = "[#{File.basename(File.expand_path(Rails.root)).camelize}] "
22
+ cattr_accessor :default_subject_prefix
23
+
24
+ # Specify the prefix to use for the subject. This defaults to the
25
+ # +default_subject_prefix+ specified for ActionMailer::Base.
26
+ adv_attr_accessor :subject_prefix
27
+
28
+ include HasEmails::Extensions::ActionMailer::InstanceMethods
29
+ extend HasEmails::Extensions::ActionMailer::ClassMethods
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+ def self.extended(base) #:nodoc:
35
+ class << base
36
+ alias_method_chain :method_missing, :has_emails
37
+ end
38
+ end
39
+
40
+ # Handles calls to queue_*
41
+ def method_missing_with_has_emails(method_symbol, *parameters)
42
+ case method_symbol.id2name
43
+ when /^queue_([_a-z]\w*)/
44
+ # Queues the mail so that it's processed in the background
45
+ new($1, *parameters).queue
46
+ else
47
+ # Handle the mail delivery as normal
48
+ method_missing_without_has_emails(method_symbol, *parameters)
49
+ end
50
+ end
51
+ end
52
+
53
+ module InstanceMethods
54
+ def self.included(base) #:nodoc:
55
+ base.class_eval do
56
+ alias_method_chain :initialize_defaults, :subject_prefix
57
+ alias_method_chain :subject, :prefix
58
+ alias_method :subject=, :subject_with_prefix
59
+ end
60
+ end
61
+
62
+ # Sets or gets the subject of the email. All subjects are prefixed with a
63
+ # value indicating the application it is coming from.
64
+ def subject_with_prefix(*parameters)
65
+ if parameters.empty?
66
+ subject_without_prefix
67
+ else
68
+ subject_without_prefix(subject_prefix + subject_without_prefix(*parameters))
69
+ end
70
+ end
71
+
72
+ # Sets the default subject prefix
73
+ def initialize_defaults_with_subject_prefix(method_name) #:nodoc
74
+ initialize_defaults_without_subject_prefix(method_name)
75
+ @subject_prefix ||= ::ActionMailer::Base.default_subject_prefix.dup
76
+ end
77
+
78
+ # Delivers an email based on the content in the specified email
79
+ def email(email)
80
+ @from = email.sender.with_name
81
+ @recipients = email.to.map(&:with_name)
82
+ @cc = email.cc.map(&:with_name)
83
+ @bcc = email.bcc.map(&:with_name)
84
+ @subject = email.subject || ''
85
+ @body = email.body || ''
86
+ @sent_on = email.updated_at
87
+ end
88
+
89
+ # Queues the current e-mail that has been constructed
90
+ def queue
91
+ Email.transaction do
92
+ # Create the main email
93
+ email = EmailAddress.find_or_create_by_address(from).emails.build(
94
+ :subject => subject,
95
+ :body => body,
96
+ :to => email_addresses_for(:recipients),
97
+ :cc => email_addresses_for(:cc),
98
+ :bcc => email_addresses_for(:bcc)
99
+ )
100
+ email.queue!
101
+ end
102
+ end
103
+
104
+ private
105
+ # Finds or creates all of the email addresses for the given type of
106
+ # recipient (can be :recipients, :cc, or :bcc)
107
+ def email_addresses_for(kind)
108
+ [send(kind)].flatten.collect {|address| EmailAddress.find_or_create_by_address(address)}
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ ActionMailer::Base.class_eval do
116
+ include HasEmails::Extensions::ActionMailer
117
+ end
@@ -0,0 +1,10 @@
1
+ require 'config/boot'
2
+
3
+ Rails::Initializer.run do |config|
4
+ config.plugin_paths << '..'
5
+ config.plugins = %w(plugin_tracker state_machine has_messages validates_as_email_address has_emails)
6
+ config.cache_classes = false
7
+ config.whiny_nils = true
8
+ config.action_mailer.delivery_method = :test
9
+ config.action_controller.session = {:key => 'rails_session', :secret => 'd229e4d22437432705ab3985d4d246'}
10
+ end
@@ -0,0 +1,18 @@
1
+ class MigrateHasMessagesToVersion2 < ActiveRecord::Migration
2
+ def self.up
3
+ ActiveRecord::Migrator.new(:up, "#{directory}/db/migrate", 0).migrations.each do |migration|
4
+ migration.migrate(:up)
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ ActiveRecord::Migrator.new(:up, "#{directory}/db/migrate", 0).migrations.each do |migration|
10
+ migration.migrate(:down)
11
+ end
12
+ end
13
+
14
+ private
15
+ def self.directory
16
+ Rails.plugins.find {|plugin| plugin.name == 'has_messages'}.directory
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ class MigrateHasEmailsToVersion1 < ActiveRecord::Migration
2
+ def self.up
3
+ ActiveRecord::Migrator.new(:up, "#{Rails.root}/../../db/migrate", 0).migrations.each do |migration|
4
+ migration.migrate(:up)
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ ActiveRecord::Migrator.new(:up, "#{Rails.root}/../../db/migrate", 0).migrations.each do |migration|
10
+ migration.migrate(:down)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class << Rails
2
+ attr_accessor :plugins
3
+ end
4
+
5
+ Rails.plugins = initializer.loaded_plugins
data/test/factory.rb ADDED
@@ -0,0 +1,58 @@
1
+ module Factory
2
+ # Build actions for the model
3
+ def self.build(model, &block)
4
+ name = model.to_s.underscore
5
+
6
+ define_method("#{name}_attributes", block)
7
+ define_method("valid_#{name}_attributes") {|*args| valid_attributes_for(model, *args)}
8
+ define_method("new_#{name}") {|*args| new_record(model, *args)}
9
+ define_method("create_#{name}") {|*args| create_record(model, *args)}
10
+ end
11
+
12
+ # Get valid attributes for the model
13
+ def valid_attributes_for(model, attributes = {})
14
+ name = model.to_s.underscore
15
+ send("#{name}_attributes", attributes)
16
+ attributes.stringify_keys!
17
+ attributes
18
+ end
19
+
20
+ # Build an unsaved record
21
+ def new_record(model, *args)
22
+ attributes = valid_attributes_for(model, *args)
23
+ record = model.new(attributes)
24
+ attributes.each {|attr, value| record.send("#{attr}=", value) if model.accessible_attributes && !model.accessible_attributes.include?(attr) || model.protected_attributes && model.protected_attributes.include?(attr)}
25
+ record
26
+ end
27
+
28
+ # Build and save/reload a record
29
+ def create_record(model, *args)
30
+ record = new_record(model, *args)
31
+ record.save!
32
+ record.reload
33
+ record
34
+ end
35
+
36
+ build Email do |attributes|
37
+ attributes[:sender] = create_user unless attributes.include?(:sender)
38
+ attributes.reverse_merge!(
39
+ :subject => 'New features',
40
+ :body => 'Lots of new things to talk about... come to the meeting tonight to find out!'
41
+ )
42
+ end
43
+
44
+ build EmailAddress do |attributes|
45
+ attributes.reverse_merge!(
46
+ :name => 'John Smith',
47
+ :spec => 'john.smith@gmail.com'
48
+ )
49
+ end
50
+
51
+ build MessageRecipient do |attributes|
52
+ attributes[:message] = create_message unless attributes.include?(:message)
53
+ attributes[:receiver] = create_user(:login => 'me') unless attributes.include?(:receiver)
54
+ attributes.reverse_merge!(
55
+ :kind => 'to'
56
+ )
57
+ end
58
+ end
@@ -0,0 +1,139 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class EmailAddressByDefaultFunctionalTest < ActiveSupport::TestCase
4
+ def setup
5
+ @email_address = create_email_address
6
+ end
7
+
8
+ def test_should_not_have_any_emails
9
+ assert @email_address.emails.empty?
10
+ end
11
+
12
+ def test_should_not_have_any_unsent_emails
13
+ assert @email_address.unsent_emails.empty?
14
+ end
15
+
16
+ def test_should_not_have_any_sent_emails
17
+ assert @email_address.sent_emails.empty?
18
+ end
19
+
20
+ def test_should_not_have_any_received_emails
21
+ assert @email_address.received_emails.empty?
22
+ end
23
+ end
24
+
25
+ class EmailAddressFunctionalTest < ActiveSupport::TestCase
26
+ def setup
27
+ @email_address = create_email_address
28
+ end
29
+
30
+ def test_should_be_able_to_create_new_emails
31
+ email = @email_address.emails.build
32
+ assert_instance_of Email, email
33
+ assert_equal @email_address, email.sender
34
+ end
35
+
36
+ def test_should_be_able_to_send_new_emails
37
+ email = @email_address.emails.build
38
+ email.to create_email_address(:spec => 'jane.smith@gmail.com')
39
+ assert email.deliver
40
+ end
41
+ end
42
+
43
+ class EmailAddressWithUnsentEmails < ActiveSupport::TestCase
44
+ def setup
45
+ @email_address = create_email_address
46
+ @sent_email = create_email(:sender => @email_address, :to => create_email_address(:spec => 'jane.smith@gmail.com'))
47
+ @sent_email.deliver
48
+ @first_draft = create_email(:sender => @email_address)
49
+ @second_draft = create_email(:sender => @email_address)
50
+ end
51
+
52
+ def test_should_have_unsent_emails
53
+ assert_equal [@first_draft, @second_draft], @email_address.unsent_emails
54
+ end
55
+
56
+ def test_should_include_unsent_emails_in_emails
57
+ assert_equal [@sent_email, @first_draft, @second_draft], @email_address.emails
58
+ end
59
+ end
60
+
61
+ class EmailAddressWithSentEmails < ActiveSupport::TestCase
62
+ def setup
63
+ @email_address = create_email_address
64
+ @to = create_email_address(:spec => 'jane.smith@gmail.com')
65
+ @draft = create_email(:sender => @email_address)
66
+
67
+ @first_sent_email = create_email(:sender => @email_address, :to => @to)
68
+ @first_sent_email.deliver
69
+
70
+ @second_sent_email = create_email(:sender => @email_address, :to => @to)
71
+ @second_sent_email.deliver
72
+ end
73
+
74
+ def test_should_have_sent_emails
75
+ assert_equal [@first_sent_email, @second_sent_email], @email_address.sent_emails
76
+ end
77
+
78
+ def test_should_include_sent_emails_in_emails
79
+ assert_equal [@draft, @first_sent_email, @second_sent_email], @email_address.emails
80
+ end
81
+ end
82
+
83
+ class EmailAddressWithReceivedEmails < ActiveSupport::TestCase
84
+ def setup
85
+ @sender = create_email_address
86
+ @email_address = create_email_address(:spec => 'jane.smith@gmail.com')
87
+
88
+ @unsent_email = create_email(:sender => @sender, :to => @email_address)
89
+
90
+ @first_sent_email = create_email(:sender => @sender, :to => @email_address)
91
+ @first_sent_email.deliver
92
+
93
+ @second_sent_email = create_email(:sender => @sender, :to => @email_address)
94
+ @second_sent_email.deliver
95
+ end
96
+
97
+ def test_should_have_received_emails
98
+ assert_equal [@first_sent_email, @second_sent_email], @email_address.received_emails.map(&:message)
99
+ end
100
+ end
101
+
102
+ class EmailAddressWithHiddenEmailsTest < ActiveSupport::TestCase
103
+ def setup
104
+ @email_address = create_email_address
105
+ @friend = create_email_address(:spec => 'jane.smith@gmail.com')
106
+
107
+ hidden_unsent_email = create_email(:sender => @email_address)
108
+ hidden_unsent_email.hide
109
+ @unsent_email = create_email(:sender => @email_address)
110
+
111
+ hidden_sent_email = create_email(:sender => @email_address, :to => @friend)
112
+ hidden_sent_email.deliver
113
+ hidden_sent_email.hide
114
+ @sent_email = create_email(:sender => @email_address, :to => @friend)
115
+ @sent_email.deliver
116
+
117
+ hidden_received_email = create_email(:sender => @friend, :to => @email_address)
118
+ hidden_received_email.deliver
119
+ hidden_received_email.recipients.first.hide
120
+ @received_email = create_email(:sender => @friend, :to => @email_address)
121
+ @received_email.deliver
122
+ end
123
+
124
+ def test_should_not_include_hidden_emails_in_emails
125
+ assert_equal [@unsent_email, @sent_email], @email_address.emails
126
+ end
127
+
128
+ def test_should_not_include_hidden_emails_in_unsent_emails
129
+ assert_equal [@unsent_email], @email_address.unsent_emails
130
+ end
131
+
132
+ def test_should_not_include_hidden_emails_in_sent_emails
133
+ assert_equal [@sent_email], @email_address.sent_emails
134
+ end
135
+
136
+ def test_should_not_include_hidden_emails_in_received_emails
137
+ assert_equal [@received_email], @email_address.received_emails.map(&:message)
138
+ end
139
+ end
@@ -0,0 +1,13 @@
1
+ # Load the plugin testing framework
2
+ $:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
3
+ require 'rubygems'
4
+ require 'plugin_test_helper'
5
+
6
+ # Run the migrations
7
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
8
+
9
+ # Mixin the factory helper
10
+ require File.expand_path("#{File.dirname(__FILE__)}/factory")
11
+ Test::Unit::TestCase.class_eval do
12
+ include Factory
13
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class TestMailer < ActionMailer::Base
4
+ def signed_up(recipient)
5
+ subject 'Thanks for signing up'
6
+ from 'MyWebApp <welcome@mywebapp.com>'
7
+ recipients recipient
8
+ cc 'Nobody <nobody@mywebapp.com>'
9
+ bcc 'root@mywebapp.com'
10
+ body 'Congratulations!'
11
+ end
12
+ end
13
+
14
+ class TestMailerTest < ActionMailer::TestCase
15
+ def test_should_use_camelized_application_name_for_default_subject_prefix
16
+ assert_equal '[AppRoot] ', ActionMailer::Base.default_subject_prefix
17
+ end
18
+
19
+ def test_should_queue_email
20
+ assert_nothing_raised {TestMailer.queue_signed_up('john.smith@gmail.com')}
21
+ assert_equal 1, Email.count
22
+
23
+ email = Email.find(1)
24
+ assert_equal '[AppRoot] Thanks for signing up', email.subject
25
+ assert_equal 'Congratulations!', email.body
26
+ assert_equal 'MyWebApp <welcome@mywebapp.com>', email.sender.with_name
27
+ assert_equal ['john.smith@gmail.com'], email.to.map(&:with_name)
28
+ assert_equal ['Nobody <nobody@mywebapp.com>'], email.cc.map(&:with_name)
29
+ assert_equal ['root@mywebapp.com'], email.bcc.map(&:with_name)
30
+ end
31
+
32
+ def test_should_deliver_email
33
+ email = create_email(
34
+ :subject => 'Hello',
35
+ :body => 'How are you?',
36
+ :sender => create_email_address(:name => 'MyWebApp', :spec => 'welcome@mywebapp.com'),
37
+ :to => create_email_address(:spec => 'john.smith@gmail.com'),
38
+ :cc => create_email_address(:name => 'Nobody', :spec => 'nobody@mywebapp.com'),
39
+ :bcc => create_email_address(:spec => 'root@mywebapp.com')
40
+ )
41
+
42
+ assert_nothing_raised {ActionMailer::Base.deliver_email(email)}
43
+ assert_equal 1, ActionMailer::Base.deliveries.size
44
+
45
+ delivery = ActionMailer::Base.deliveries.first
46
+ assert_equal 'Hello', delivery.subject
47
+ assert_equal 'How are you?', delivery.body
48
+ assert_equal ['welcome@mywebapp.com'], delivery.from
49
+ assert_equal ['john.smith@gmail.com'], delivery.to
50
+ assert_equal ['nobody@mywebapp.com'], delivery.cc
51
+ assert_equal ['root@mywebapp.com'], delivery.bcc
52
+ end
53
+ end
@@ -0,0 +1,160 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class EmailAddressByDefaultTest < ActiveSupport::TestCase
4
+ def setup
5
+ @email_address = EmailAddress.new
6
+ end
7
+
8
+ def test_should_not_have_a_name
9
+ assert @email_address.name.blank?
10
+ end
11
+
12
+ def test_should_not_have_a_spec
13
+ assert @email_address.name.blank?
14
+ end
15
+ end
16
+
17
+ class EmailAddressTest < ActiveSupport::TestCase
18
+ def test_should_be_valid_with_a_set_of_valid_attributes
19
+ email_address = new_email_address
20
+ assert email_address.valid?
21
+ end
22
+
23
+ def test_should_require_a_spec
24
+ email_address = new_email_address(:spec => nil)
25
+ assert !email_address.valid?
26
+ assert email_address.errors.invalid?(:spec)
27
+ end
28
+
29
+ def test_should_require_a_properly_formatted_email
30
+ email_address = new_email_address(:spec => '!@@!@@!')
31
+ assert !email_address.valid?
32
+ assert email_address.errors.invalid?(:spec)
33
+ end
34
+
35
+ def test_should_not_allow_emails_less_than_3_characters
36
+ email_address = new_email_address(:spec => 'aa')
37
+ assert !email_address.valid?
38
+ assert email_address.errors.invalid?(:spec)
39
+
40
+ email_address.spec = 'a@a'
41
+ assert email_address.valid?
42
+ end
43
+
44
+ def test_should_not_allow_emails_longer_than_320_characters
45
+ email_address = new_email_address(:spec => 'a' * 314 + '@a.com')
46
+ assert email_address.valid?
47
+
48
+ email_address.spec += 'a'
49
+ assert !email_address.valid?
50
+ assert email_address.errors.invalid?(:spec)
51
+ end
52
+
53
+ def test_should_require_a_unique_spec_scoped_by_name
54
+ email_address = create_email_address(:spec => 'john.smith@gmail.com', :name => 'John Smith')
55
+
56
+ second_email_address = new_email_address(:spec => 'john.smith@gmail.com', :name => 'John Smith II')
57
+ assert second_email_address.valid?
58
+
59
+ second_email_address = new_email_address(:spec => 'john.smith@gmail.com', :name => 'John Smith')
60
+ assert !second_email_address.valid?
61
+ assert second_email_address.errors.invalid?(:spec)
62
+ end
63
+
64
+ def test_should_not_require_a_name
65
+ email_address = new_email_address(:name => nil)
66
+ assert email_address.valid?
67
+ end
68
+
69
+ def test_should_protect_attributes_from_mass_assignment
70
+ email_address = EmailAddress.new(
71
+ :id => 1,
72
+ :name => 'John Smith',
73
+ :spec => 'john.smith@gmail.com'
74
+ )
75
+
76
+ assert_nil email_address.id
77
+ assert_equal 'John Smith', email_address.name
78
+ assert_equal 'john.smith@gmail.com', email_address.spec
79
+ end
80
+ end
81
+
82
+ class EmailAddressFromAddressTest < ActiveSupport::TestCase
83
+ def setup
84
+ @email_address = EmailAddress.new(:address => 'John Smith <john.smith@gmail.com>')
85
+ end
86
+
87
+ def test_should_be_valid
88
+ assert @email_address.valid?
89
+ end
90
+
91
+ def test_should_find_a_name
92
+ assert_equal 'John Smith', @email_address.name
93
+ end
94
+
95
+ def test_should_find_a_spec
96
+ assert_equal 'john.smith@gmail.com', @email_address.spec
97
+ end
98
+ end
99
+
100
+ class EmailAddressFromAddressWithoutNameTest < ActiveSupport::TestCase
101
+ def setup
102
+ @email_address = EmailAddress.new(:address => 'john.smith@gmail.com')
103
+ end
104
+
105
+ def test_should_be_valid
106
+ assert @email_address.valid?
107
+ end
108
+
109
+ def test_should_not_find_a_name
110
+ assert @email_address.name.blank?
111
+ end
112
+
113
+ def test_should_find_a_spec
114
+ assert_equal 'john.smith@gmail.com', @email_address.spec
115
+ end
116
+ end
117
+
118
+ class EmailAddressAfterBeingCreatedTest < ActiveSupport::TestCase
119
+ def setup
120
+ @email_address = create_email_address(:name => 'John Smith', :spec => 'john.smith@gmail.com')
121
+ end
122
+
123
+ def test_should_record_when_it_was_created
124
+ assert_not_nil @email_address.created_at
125
+ end
126
+
127
+ def test_should_record_when_it_was_updated
128
+ assert_not_nil @email_address.updated_at
129
+ end
130
+
131
+ def test_should_generate_an_address_with_the_name
132
+ assert_equal 'John Smith <john.smith@gmail.com>', @email_address.with_name
133
+ end
134
+ end
135
+
136
+ class EmailAddressAsAClassTest < ActiveSupport::TestCase
137
+ def test_should_be_able_to_split_address_containing_name
138
+ name, spec = EmailAddress.split_address('John Smith <john.smith@gmail.com>')
139
+ assert_equal 'John Smith', name
140
+ assert_equal 'john.smith@gmail.com', spec
141
+ end
142
+
143
+ def test_should_be_able_to_split_address_not_containing_name
144
+ name, spec = EmailAddress.split_address('john.smith@gmail.com')
145
+ assert_nil name
146
+ assert_equal 'john.smith@gmail.com', spec
147
+ end
148
+
149
+ def test_should_be_able_to_find_an_existing_email_by_address
150
+ email_address = create_email_address(:address => 'John Smith <john.smith@gmail.com>')
151
+ assert_equal email_address, EmailAddress.find_or_create_by_address('John Smith <john.smith@gmail.com>')
152
+ end
153
+
154
+ def test_should_be_able_to_create_from_a_new_address
155
+ email_address = EmailAddress.find_or_create_by_address('John Smith <john.smith@gmail.com>')
156
+ assert !email_address.new_record?
157
+ assert_equal 'John Smith', email_address.name
158
+ assert_equal 'john.smith@gmail.com', email_address.spec
159
+ end
160
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class EmailAfterBeingDeliveredTest < ActiveSupport::TestCase
4
+ def setup
5
+ ActionMailer::Base.deliveries = []
6
+
7
+ @email = new_email(
8
+ :subject => 'Hello',
9
+ :body => 'How are you?',
10
+ :sender => create_email_address(:spec => 'webmaster@localhost'),
11
+ :to => create_email_address(:spec => 'partners@localhost'),
12
+ :cc => create_email_address(:spec => 'support@localhost'),
13
+ :bcc => create_email_address(:spec => 'feedback@localhost')
14
+ )
15
+ assert @email.deliver
16
+ end
17
+
18
+ def test_should_send_mail
19
+ assert ActionMailer::Base.deliveries.any?
20
+
21
+ delivery = ActionMailer::Base.deliveries.first
22
+ assert_equal 'Hello', delivery.subject
23
+ assert_equal 'How are you?', delivery.body
24
+ assert_equal ['webmaster@localhost'], delivery.from
25
+ assert_equal ['partners@localhost'], delivery.to
26
+ assert_equal ['support@localhost'], delivery.cc
27
+ assert_equal ['feedback@localhost'], delivery.bcc
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pluginaweek-has_emails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Pfeifer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: has_messages
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.4.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: validates_as_email_address
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.2
34
+ version:
35
+ description: Demonstrates a reference implementation for sending emails with logging and asynchronous support in ActiveRecord
36
+ email: aaron@pluginaweek.org
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - app/models
45
+ - app/models/email_address.rb
46
+ - app/models/email.rb
47
+ - db/migrate
48
+ - db/migrate/001_create_email_addresses.rb
49
+ - lib/has_emails.rb
50
+ - lib/has_emails
51
+ - lib/has_emails/extensions
52
+ - lib/has_emails/extensions/action_mailer.rb
53
+ - test/unit
54
+ - test/unit/email_address_test.rb
55
+ - test/unit/email_test.rb
56
+ - test/unit/action_mailer_test.rb
57
+ - test/factory.rb
58
+ - test/app_root
59
+ - test/app_root/vendor
60
+ - test/app_root/vendor/plugins
61
+ - test/app_root/vendor/plugins/plugin_tracker
62
+ - test/app_root/vendor/plugins/plugin_tracker/init.rb
63
+ - test/app_root/db
64
+ - test/app_root/db/migrate
65
+ - test/app_root/db/migrate/002_migrate_has_emails_to_version_1.rb
66
+ - test/app_root/db/migrate/001_migrate_has_messages_to_version_2.rb
67
+ - test/app_root/config
68
+ - test/app_root/config/environment.rb
69
+ - test/test_helper.rb
70
+ - test/functional
71
+ - test/functional/has_emails_test.rb
72
+ - CHANGELOG.rdoc
73
+ - init.rb
74
+ - LICENSE
75
+ - Rakefile
76
+ - README.rdoc
77
+ has_rdoc: true
78
+ homepage: http://www.pluginaweek.org
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project: pluginaweek
99
+ rubygems_version: 1.2.0
100
+ signing_key:
101
+ specification_version: 2
102
+ summary: Demonstrates a reference implementation for sending emails with logging and asynchronous support in ActiveRecord
103
+ test_files:
104
+ - test/unit/email_address_test.rb
105
+ - test/unit/email_test.rb
106
+ - test/unit/action_mailer_test.rb
107
+ - test/functional/has_emails_test.rb