has_emails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ class AddEmailRecipientSpecs < ActiveRecord::Migration
2
+ def self.up
3
+ # Workaround change_column not allowing change to :null => true
4
+ remove_column :message_recipients, :receiver_id
5
+ remove_column :message_recipients, :receiver_type
6
+
7
+ add_column :message_recipients, :receiver_id, :integer, :null => true, :default => nil, :references => nil
8
+ add_column :message_recipients, :receiver_type, :string, :null => true, :default => nil
9
+ add_column :message_recipients, :receiver_spec, :string, :limit => 320
10
+ end
11
+
12
+ def self.down
13
+ remove_column :message_recipients, :receiver_spec
14
+
15
+ change_column :message_recipients, :receiver_id, :integer, :null => false, :references => nil
16
+ change_column :message_recipients, :receiver_type, :string, :null => false
17
+ end
18
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'has_emails'
data/lib/has_emails.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'has_messages'
2
+ require 'validates_as_email_address'
3
+ require 'acts_as_tokenized'
4
+ require 'nested_has_many_through'
5
+
6
+ module PluginAWeek #:nodoc:
7
+ module Has #:nodoc:
8
+ # Adds support for sending emails to models
9
+ module Emails
10
+ def self.included(base) #:nodoc:
11
+ base.extend(MacroMethods)
12
+ end
13
+
14
+ module MacroMethods
15
+ # Adds support for emailing instances of this model through multiple
16
+ # email addresses.
17
+ #
18
+ # == Generated associations
19
+ #
20
+ # The following +has_many+ associations are created for models that support
21
+ # emailing:
22
+ # * +email_addresses+ - The email addresses of this model
23
+ # * +emails+ - A collection of Emails of which this model was the sender
24
+ # * +email_recipients+ - A collection of EmailRecipients in which this record is a receiver
25
+ def has_email_addresses
26
+ has_many :email_addresses,
27
+ :class_name => 'EmailAddress',
28
+ :as => :emailable,
29
+ :dependent => :destroy
30
+
31
+ # Add associations for all emails the model has sent and received
32
+ has_many :emails,
33
+ :through => :email_addresses
34
+ has_many :email_recipients,
35
+ :through => :email_addresses
36
+
37
+ include PluginAWeek::Has::Emails::InstanceMethods
38
+ end
39
+
40
+ # Adds support for emailing instances of this model through a single
41
+ # email address.
42
+ #
43
+ # == Generated associations
44
+ #
45
+ # The following associations are created for models that support emailing:
46
+ # * +email_address+ - The email address of this model
47
+ # * +emails+ - A collection of Emails of which this model was the sender
48
+ # * +email_recipients+ - A collection of EmailRecipients in which this record is a receiver
49
+ def has_email_address
50
+ has_one :email_address,
51
+ :class_name => 'EmailAddress',
52
+ :as => :emailable,
53
+ :dependent => :destroy
54
+
55
+ delegate :emails,
56
+ :email_recipients,
57
+ :to => :email_address
58
+
59
+ include PluginAWeek::Has::Emails::InstanceMethods
60
+ end
61
+ end
62
+
63
+ module InstanceMethods
64
+ # All emails this model has received
65
+ def received_emails
66
+ email_recipients.active.find_in_states(:all, :unread, :read, :include => :message, :conditions => ['messages.state_id = ?', Message.states.find_by_name('sent').id]).collect do |recipient|
67
+ ReceivedMessage.new(recipient)
68
+ end
69
+ end
70
+
71
+ # All emails that have not yet been sent by this model (excluding any that have been deleted)
72
+ def unsent_emails(*args)
73
+ emails.active.unsent(*args)
74
+ end
75
+
76
+ # All emails that have been sent by this model (excluding any that have been deleted)
77
+ def sent_emails(*args)
78
+ emails.active.find_in_states(:all, :queued, :sent, *args)
79
+ end
80
+
81
+ # Contains all of the emails that have been sent and received
82
+ def email_box
83
+ @email_box ||= MessageBox.new(received_emails, unsent_emails, sent_emails)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ActiveRecord::Base.class_eval do
91
+ include PluginAWeek::Has::Emails
92
+ end
@@ -0,0 +1,2 @@
1
+ class Department < ActiveRecord::Base
2
+ end
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ has_email_addresses
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'config/boot'
2
+
3
+ $:.unshift("#{RAILS_ROOT}/../../../../../rails/plugin_dependencies/lib")
4
+ begin
5
+ require 'plugin_dependencies'
6
+ rescue Exception => e
7
+ end
8
+
9
+ Rails::Initializer.run do |config|
10
+ config.plugin_paths.concat([
11
+ "#{RAILS_ROOT}/../../..",
12
+ "#{RAILS_ROOT}/../../../../migrations",
13
+ "#{RAILS_ROOT}/../../../../../rails",
14
+ "#{RAILS_ROOT}/../../../../../test",
15
+ "#{RAILS_ROOT}/../../../../../third_party"
16
+ ])
17
+ config.plugins = [
18
+ 'loaded_plugins',
19
+ 'appable_plugins',
20
+ 'plugin_migrations',
21
+ 'has_states',
22
+ 'has_finder',
23
+ 'has_messages',
24
+ 'acts_as_tokenized',
25
+ 'nested_has_many_through',
26
+ File.basename(File.expand_path("#{RAILS_ROOT}/../..")),
27
+ 'dry_validity_assertions'
28
+ ]
29
+ config.cache_classes = false
30
+ config.whiny_nils = true
31
+ end
32
+
33
+ Plugin.mix_code_from(:mailers => /.+_mailer/)
@@ -0,0 +1,11 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.column :login, :string, :null => false
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :users
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class CreateDepartments < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :departments do |t|
4
+ t.column :name, :string, :null => false
5
+ t.column :email_address, :string, :null => false
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ drop_table :departments
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ marketing:
2
+ id: 1
3
+ name: Marketing
4
+ email_address: marketing@companyxyz.com
5
+
6
+ testing:
7
+ id: 2
8
+ name: Testing
9
+ email_address: testing@companyxyz.com
@@ -0,0 +1,32 @@
1
+ bob:
2
+ id: 1
3
+ emailable_id: 1
4
+ emailable_type: User
5
+ spec: bob@bob.com
6
+ verification_code: c2e4f499bf610fa95b8ee3a4e8013dbe
7
+ code_expiry: <%= Time.now.to_s(:db) %>
8
+ created_at: <%= Time.now.to_s(:db) %>
9
+ updated_at: <%= Time.now.to_s(:db) %>
10
+ state_id: 601
11
+
12
+ john:
13
+ id: 2
14
+ emailable_id: 2
15
+ emailable_type: User
16
+ spec: john@john.com
17
+ verification_code: 1d3955ee0d05ce5bb847f29d56b990cc
18
+ code_expiry: <%= Time.now.to_s(:db) %>
19
+ created_at: <%= Time.now.to_s(:db) %>
20
+ updated_at: <%= Time.now.to_s(:db) %>
21
+ state_id: 602
22
+
23
+ mary:
24
+ id: 3
25
+ emailable_id: 3
26
+ emailable_type: User
27
+ spec: mary@mary.com
28
+ verification_code: 8323145e816cb063d405f5e365d14807
29
+ code_expiry: <%= Time.now.to_s(:db) %>
30
+ created_at: <%= Time.now.to_s(:db) %>
31
+ updated_at: <%= Time.now.to_s(:db) %>
32
+ state_id: 602
@@ -0,0 +1,85 @@
1
+ bob_to_john:
2
+ id: 1
3
+ message_id: 1
4
+ receiver_id: 2
5
+ receiver_type: EmailAddress
6
+ receiver_spec: john@john.com
7
+ kind: to
8
+ position: 1
9
+ state_id: 511
10
+ type: EmailRecipient
11
+
12
+ bob_to_mary:
13
+ id: 2
14
+ message_id: 1
15
+ receiver_id: 3
16
+ receiver_type: EmailAddress
17
+ receiver_spec: mary@mary.com
18
+ kind: cc
19
+ position: 1
20
+ state_id: 510
21
+ type: EmailRecipient
22
+
23
+ bob_to_random:
24
+ id: 3
25
+ message_id: 1
26
+ receiver_spec: random@random.com
27
+ kind: cc
28
+ position: 2
29
+ state_id: 510
30
+ type: EmailRecipient
31
+
32
+ bob_to_marketing:
33
+ id: 4
34
+ message_id: 1
35
+ receiver_id: 1
36
+ receiver_type: Department
37
+ receiver_spec: marketing@companyxyz.com
38
+ kind: cc
39
+ position: 3
40
+ state_id: 510
41
+ type: EmailRecipient
42
+
43
+ mary_to_john:
44
+ id: 5
45
+ message_id: 2
46
+ receiver_id: 2
47
+ receiver_type: EmailAddress
48
+ receiver_spec: john@john.com
49
+ kind: bcc
50
+ position: 1
51
+ state_id: 510
52
+ type: EmailRecipient
53
+
54
+ unsent_bob_to_john:
55
+ id: 6
56
+ message_id: 3
57
+ receiver_id: 2
58
+ receiver_type: EmailAddress
59
+ receiver_spec: john@john.com
60
+ kind: to
61
+ position: 1
62
+ state_id: 510
63
+ type: EmailRecipient
64
+
65
+ unsent_bob_to_mary:
66
+ id: 7
67
+ message_id: 3
68
+ receiver_id: 3
69
+ receiver_type: EmailAddress
70
+ receiver_spec: mary@mary.com
71
+ kind: cc
72
+ position: 1
73
+ state_id: 510
74
+ type: EmailRecipient
75
+
76
+ queued_bob_to_john:
77
+ id: 8
78
+ message_id: 4
79
+ receiver_id: 2
80
+ receiver_type: User
81
+ receiver_spec: john@john.com
82
+ kind: to
83
+ position: 1
84
+ state_id: 510
85
+ type: EmailRecipient
@@ -0,0 +1,52 @@
1
+ sent_from_bob:
2
+ id: 1
3
+ sender_id: 1
4
+ sender_type: EmailAddress
5
+ sender_spec: bob@bob.com
6
+ subject: "Funny joke"
7
+ body: "Why can't dinosaurs talk? ...Because they're dead!"
8
+ created_at: <%= Time.now.to_s(:db) %>
9
+ state_id: 502
10
+ type: Email
11
+
12
+ sent_from_mary:
13
+ id: 2
14
+ sender_id: 3
15
+ sender_type: EmailAddress
16
+ sender_spec: mary@mary.com
17
+ subject: "FW: Funny joke"
18
+ body: "> Why can't dinosaurs talk? ...Because they're dead!"
19
+ created_at: <%= Time.now.to_s(:db) %>
20
+ state_id: 502
21
+ type: Email
22
+
23
+ unsent_from_bob:
24
+ id: 3
25
+ sender_id: 1
26
+ sender_type: EmailAddress
27
+ sender_spec: bob@bob.com
28
+ subject: "Another funny joke"
29
+ body: "Where do cows go on a date? ...To the moovies!"
30
+ created_at: <%= Time.now.to_s(:db) %>
31
+ state_id: 500
32
+ type: Email
33
+
34
+ queued_from_bob:
35
+ id: 4
36
+ sender_id: 1
37
+ sender_type: EmailAddress
38
+ sender_spec: bob@bob.com
39
+ subject: ""
40
+ body: ""
41
+ created_at: <%= Time.now.to_s(:db) %>
42
+ state_id: 501
43
+ type: Email
44
+
45
+ unsent_from_stranger:
46
+ id: 5
47
+ sender_spec: stranger@somewhere.com
48
+ subject: "Do you know where I am?"
49
+ body: "Just wondering..."
50
+ created_at: <%= Time.now.to_s(:db) %>
51
+ state_id: 500
52
+ type: Email
@@ -0,0 +1,128 @@
1
+ # Emails
2
+ sent_from_bob_unsent:
3
+ id: 1
4
+ stateful_id: 1
5
+ stateful_type: Email
6
+ to_state_id: 500
7
+ occurred_at: <%= Time.now.to_s(:db) %>
8
+
9
+ sent_from_bob_sent:
10
+ id: 2
11
+ stateful_id: 1
12
+ stateful_type: Email
13
+ from_state_id: 500
14
+ to_state_id: 502
15
+ event_id: 500
16
+ occurred_at: <%= Time.now.to_s(:db) %>
17
+
18
+ sent_from_mary_unsent:
19
+ id: 3
20
+ stateful_id: 2
21
+ stateful_type: Email
22
+ to_state_id: 500
23
+ occurred_at: <%= Time.now.to_s(:db) %>
24
+
25
+ sent_from_mary_sent:
26
+ id: 4
27
+ stateful_id: 2
28
+ stateful_type: Email
29
+ from_state_id: 500
30
+ to_state_id: 502
31
+ event_id: 500
32
+ occurred_at: <%= Time.now.to_s(:db) %>
33
+
34
+ unsent_from_bob_unsent:
35
+ id: 5
36
+ stateful_id: 3
37
+ stateful_type: Email
38
+ to_state_id: 500
39
+ occurred_at: <%= Time.now.to_s(:db) %>
40
+
41
+ queued_from_bob_unsent:
42
+ id: 6
43
+ stateful_id: 4
44
+ stateful_type: Email
45
+ to_state_id: 500
46
+ occurred_at: <%= Time.now.to_s(:db) %>
47
+
48
+ queued_from_bob_queued:
49
+ id: 7
50
+ stateful_id: 4
51
+ stateful_type: Email
52
+ from_state_id: 500
53
+ to_state_id: 501
54
+ event_id: 501
55
+ occurred_at: <%= Time.now.to_s(:db) %>
56
+
57
+ unsent_from_stranger_unsent:
58
+ id: 8
59
+ stateful_id: 5
60
+ stateful_type: Email
61
+ to_state_id: 500
62
+ occurred_at: <%= Time.now.to_s(:db) %>
63
+
64
+ # EmailRecipients
65
+ bob_to_john_unread:
66
+ id: 9
67
+ stateful_id: 1
68
+ stateful_type: EmailRecipient
69
+ to_state_id: 510
70
+ occurred_at: <%= Time.now.to_s(:db) %>
71
+
72
+ bob_to_john_read:
73
+ id: 10
74
+ stateful_id: 1
75
+ stateful_type: EmailRecipient
76
+ from_state_id: 510
77
+ to_state_id: 511
78
+ event_id: 510
79
+ occurred_at: <%= Time.now.to_s(:db) %>
80
+
81
+ bob_to_mary_unread:
82
+ id: 11
83
+ stateful_id: 2
84
+ stateful_type: EmailRecipient
85
+ to_state_id: 510
86
+ occurred_at: <%= Time.now.to_s(:db) %>
87
+
88
+ bob_to_random_unread:
89
+ id: 12
90
+ stateful_id: 3
91
+ stateful_type: EmailRecipient
92
+ to_state_id: 510
93
+ occurred_at: <%= Time.now.to_s(:db) %>
94
+
95
+ bob_to_marketing_unread:
96
+ id: 13
97
+ stateful_id: 4
98
+ stateful_type: EmailRecipient
99
+ to_state_id: 510
100
+ occurred_at: <%= Time.now.to_s(:db) %>
101
+
102
+ mary_to_john_unread:
103
+ id: 14
104
+ stateful_id: 5
105
+ stateful_type: EmailRecipient
106
+ to_state_id: 510
107
+ occurred_at: <%= Time.now.to_s(:db) %>
108
+
109
+ unsent_bob_to_john_unread:
110
+ id: 15
111
+ stateful_id: 6
112
+ stateful_type: EmailRecipient
113
+ to_state_id: 510
114
+ occurred_at: <%= Time.now.to_s(:db) %>
115
+
116
+ unsent_bob_to_mary_unread:
117
+ id: 16
118
+ stateful_id: 7
119
+ stateful_type: EmailRecipient
120
+ to_state_id: 510
121
+ occurred_at: <%= Time.now.to_s(:db) %>
122
+
123
+ queued_bob_to_john_unread:
124
+ id: 17
125
+ stateful_id: 8
126
+ stateful_type: EmailRecipient
127
+ to_state_id: 510
128
+ occurred_at: <%= Time.now.to_s(:db) %>
@@ -0,0 +1,11 @@
1
+ bob:
2
+ id: 1
3
+ login: bob
4
+
5
+ john:
6
+ id: 2
7
+ login: john
8
+
9
+ mary:
10
+ id: 3
11
+ login: mary
@@ -0,0 +1,76 @@
1
+ # Load local repository plugin paths
2
+ $:.unshift("#{File.dirname(__FILE__)}/../../../associations/class_associations/lib")
3
+ $:.unshift("#{File.dirname(__FILE__)}/../../../has/has_messages/lib")
4
+ $:.unshift("#{File.dirname(__FILE__)}/../../../has/has_states/lib")
5
+ $:.unshift("#{File.dirname(__FILE__)}/../../../miscellaneous/custom_callbacks/lib")
6
+ $:.unshift("#{File.dirname(__FILE__)}/../../../miscellaneous/dry_transaction_rollbacks/lib")
7
+ $:.unshift("#{File.dirname(__FILE__)}/../../../validations/validates_as_email_address/lib")
8
+ $:.unshift("#{File.dirname(__FILE__)}/../../../../ruby/object/eval_call/lib")
9
+ $:.unshift("#{File.dirname(__FILE__)}/../../../../third_party/acts_as_tokenized/lib")
10
+ $:.unshift("#{File.dirname(__FILE__)}/../../../../third_party/nested_has_many_through/lib")
11
+
12
+ # Load the plugin testing framework
13
+ $:.unshift("#{File.dirname(__FILE__)}/../../../../test/plugin_test_helper/lib")
14
+ require 'rubygems'
15
+ require 'plugin_test_helper'
16
+
17
+ # Run the plugin migrations
18
+ %w(has_states has_messages has_emails).each do |plugin|
19
+ Rails.plugins[plugin].migrate
20
+ end
21
+
22
+ # Run the test app migrations
23
+ ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate")
24
+
25
+ # Bootstrap the database
26
+ %w(has_messages has_emails).each do |plugin|
27
+ plugin = Rails.plugins[plugin]
28
+ bootstrap_path = "#{plugin.migration_path}/../bootstrap"
29
+
30
+ Dir.glob("#{bootstrap_path}/*.{yml,csv}").each do |fixture_file|
31
+ table_name = File.basename(fixture_file, '.*')
32
+ Fixtures.new(ActiveRecord::Base.connection, table_name, nil, File.join(bootstrap_path, table_name)).insert_fixtures
33
+ end
34
+ end
35
+
36
+ class Test::Unit::TestCase #:nodoc:
37
+ def self.require_fixture_classes(table_names=nil)
38
+ # Don't allow fixture classes to be required because classes like Message are
39
+ # going to throw an error since the states and events have not yet been
40
+ # loaded
41
+ end
42
+
43
+ # Freezes time for running email tests
44
+ def freeze_time(frozen_time = 946702800)
45
+ Time.instance_eval do
46
+ frozen_now = (frozen_time)
47
+ alias :original_now :now
48
+ alias :now :frozen_now
49
+ end
50
+
51
+ if block_given?
52
+ begin
53
+ yield
54
+ ensure
55
+ unfreeze_time
56
+ end
57
+ end
58
+ end
59
+
60
+ # Restores the original method for time
61
+ def unfreeze_time
62
+ Time.instance_eval do
63
+ alias :now :original_now
64
+ end
65
+ end
66
+ end
67
+
68
+ class Time
69
+ def self.frozen_now=(val)
70
+ @frozen_now = val
71
+ end
72
+
73
+ def self.frozen_now
74
+ Time.at(@frozen_now || 946702800)
75
+ end
76
+ end
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class ApplicationMailerTest < Test::Unit::TestCase
4
+ fixtures :users, :email_addresses, :messages, :message_recipients, :state_changes
5
+
6
+ class TestMailer < ApplicationMailer
7
+ def signed_up(recipient)
8
+ recipients recipient
9
+ subject 'Thanks for signing up'
10
+ from 'welcome@mywebapp.com'
11
+ cc 'nobody@mywebapp.com'
12
+ bcc 'root@mywebapp.com'
13
+ body 'Congratulations!'
14
+ end
15
+ end
16
+
17
+ def setup
18
+ ActionMailer::Base.delivery_method = :test
19
+ ActionMailer::Base.perform_deliveries = true
20
+ ActionMailer::Base.raise_delivery_errors = true
21
+ ActionMailer::Base.deliveries = []
22
+
23
+ @original_logger = TestMailer.logger
24
+ @recipient = email_addresses(:bob)
25
+ end
26
+
27
+ def test_should_use_camelized_application_name_for_default_subject_prefix
28
+ assert_equal '[AppRoot] ', ApplicationMailer.default_subject_prefix
29
+ end
30
+
31
+ def test_should_queue_email
32
+ assert_nothing_raised { TestMailer.queue_signed_up(@recipient) }
33
+ assert_equal 6, Email.count
34
+
35
+ email = Email.find(6)
36
+ assert_equal '[AppRoot] Thanks for signing up', email.subject
37
+ assert_equal 'Congratulations!', email.body
38
+ assert_equal [@recipient], email.to.map(&:receiver)
39
+ assert_equal 'welcome@mywebapp.com', email.sender
40
+ assert_equal ['nobody@mywebapp.com'], email.cc.map(&:receiver)
41
+ assert_equal ['root@mywebapp.com'], email.bcc.map(&:receiver)
42
+ end
43
+
44
+ def test_should_deliver_email
45
+ freeze_time do
46
+ expected = new_mail
47
+ expected.to = 'john@john.com'
48
+ expected.cc = 'mary@mary.com'
49
+ expected.subject = 'Another funny joke'
50
+ expected.body = 'Where do cows go on a date? ...To the moovies!'
51
+ expected.from = 'bob@bob.com'
52
+ expected.date = Time.now
53
+
54
+ assert_nothing_raised { ApplicationMailer.deliver_email(messages(:unsent_from_bob)) }
55
+ assert_not_nil ActionMailer::Base.deliveries.first
56
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
57
+ end
58
+ end
59
+
60
+ private
61
+ def new_mail( charset="utf-8" )
62
+ mail = TMail::Mail.new
63
+ mail.mime_version = "1.0"
64
+ if charset
65
+ mail.set_content_type "text", "plain", { "charset" => charset }
66
+ end
67
+ mail
68
+ end
69
+ end