pluginaweek-has_emails 0.3.0

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.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