has_emails 0.0.1

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