has_emails 0.0.1 → 0.1.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.
Files changed (40) hide show
  1. data/CHANGELOG +8 -0
  2. data/README +32 -71
  3. data/Rakefile +8 -8
  4. data/app/models/email.rb +7 -79
  5. data/app/models/email_address.rb +32 -110
  6. data/db/migrate/001_create_email_addresses.rb +4 -13
  7. data/lib/has_emails/extensions/action_mailer.rb +106 -0
  8. data/lib/has_emails.rb +50 -77
  9. data/test/app_root/app/models/empty +0 -0
  10. data/test/app_root/config/environment.rb +4 -27
  11. data/test/app_root/db/migrate/001_migrate_has_messages_to_version_2.rb +9 -0
  12. data/test/app_root/db/migrate/002_migrate_has_emails_to_version_1.rb +9 -0
  13. data/test/factory.rb +48 -0
  14. data/test/functional/has_emails_test.rb +139 -0
  15. data/test/test_helper.rb +5 -68
  16. data/test/unit/action_mailer_test.rb +57 -0
  17. data/test/unit/email_address_test.rb +100 -152
  18. data/test/unit/email_test.rb +26 -86
  19. metadata +78 -87
  20. data/app/mailers/application_mailer.rb +0 -85
  21. data/app/models/email_recipient.rb +0 -78
  22. data/app/models/email_recipient_build_extension.rb +0 -7
  23. data/db/bootstrap/events.yml +0 -4
  24. data/db/bootstrap/states.yml +0 -9
  25. data/db/migrate/002_add_email_specs.rb +0 -18
  26. data/db/migrate/003_add_email_recipient_specs.rb +0 -18
  27. data/test/app_root/app/models/department.rb +0 -2
  28. data/test/app_root/app/models/user.rb +0 -3
  29. data/test/app_root/db/migrate/001_create_users.rb +0 -11
  30. data/test/app_root/db/migrate/002_create_departments.rb +0 -12
  31. data/test/fixtures/departments.yml +0 -9
  32. data/test/fixtures/email_addresses.yml +0 -32
  33. data/test/fixtures/message_recipients.yml +0 -85
  34. data/test/fixtures/messages.yml +0 -52
  35. data/test/fixtures/state_changes.yml +0 -128
  36. data/test/fixtures/users.yml +0 -11
  37. data/test/unit/application_mailer_test.rb +0 -69
  38. data/test/unit/email_recipient_test.rb +0 -79
  39. data/test/unit/has_emails_test.rb +0 -36
  40. data/test/unit/recipient_extension_test.rb +0 -99
data/lib/has_emails.rb CHANGED
@@ -1,92 +1,65 @@
1
1
  require 'has_messages'
2
2
  require 'validates_as_email_address'
3
- require 'acts_as_tokenized'
4
- require 'nested_has_many_through'
3
+ require 'has_emails/extensions/action_mailer'
5
4
 
6
5
  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)
6
+ # Adds a generic implementation for sending emails
7
+ module HasEmails
8
+ def self.included(base) #:nodoc:
9
+ base.class_eval do
10
+ extend PluginAWeek::HasEmails::MacroMethods
12
11
  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
12
+ end
13
+
14
+ module MacroMethods
15
+ # Creates the following email associations:
16
+ # * +emails+ - Emails that were composed and are visible to the owner. Emails may have been sent or unsent.
17
+ # * +received_emails - Emails that have been received from others and are visible. Emails may have been read or unread.
18
+ #
19
+ # == Creating new emails
20
+ #
21
+ # To create a new email, the +emails+ association should be used, for example:
22
+ #
23
+ # address = EmailAddress.find(123)
24
+ # email = user.emails.build
25
+ # email.subject = 'Hello'
26
+ # email.body = 'How are you?'
27
+ # email.to User.EmailAddress(456)
28
+ # email.save!
29
+ # email.deliver!
30
+ #
31
+ # Alternatively,
32
+ def has_emails
33
+ has_many :emails,
34
+ :as => :sender,
35
+ :class_name => 'Email',
36
+ :conditions => {:hidden_at => nil},
37
+ :order => 'messages.created_at ASC'
38
+ has_many :received_emails,
39
+ :as => :receiver,
40
+ :class_name => 'MessageRecipient',
41
+ :include => :message,
42
+ :conditions => ['message_recipients.hidden_at IS NULL AND messages.state = ?', 'sent'],
43
+ :order => 'messages.created_at ASC'
39
44
 
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
45
+ include PluginAWeek::HasEmails::InstanceMethods
46
+ end
47
+ end
48
+
49
+ module InstanceMethods
50
+ # Composed emails that have not yet been sent
51
+ def unsent_emails
52
+ emails.with_state('unsent')
61
53
  end
62
54
 
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
55
+ # Composed emails that have already been sent
56
+ def sent_emails
57
+ emails.with_states(%w(queued sent))
85
58
  end
86
59
  end
87
60
  end
88
61
  end
89
62
 
90
63
  ActiveRecord::Base.class_eval do
91
- include PluginAWeek::Has::Emails
64
+ include PluginAWeek::HasEmails
92
65
  end
File without changes
@@ -1,33 +1,10 @@
1
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
2
+ require "#{File.dirname(__FILE__)}/../../../../plugins_plus/boot"
8
3
 
9
4
  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
- ]
5
+ config.plugin_paths << '..'
6
+ config.plugins = %w(plugins_plus state_machine has_messages validates_as_email_address has_emails)
29
7
  config.cache_classes = false
30
8
  config.whiny_nils = true
9
+ config.action_mailer.delivery_method = :test
31
10
  end
32
-
33
- Plugin.mix_code_from(:mailers => /.+_mailer/)
@@ -0,0 +1,9 @@
1
+ class MigrateHasMessagesToVersion2 < ActiveRecord::Migration
2
+ def self.up
3
+ Rails::Plugin.find(:has_messages).migrate(2)
4
+ end
5
+
6
+ def self.down
7
+ Rails::Plugin.find(:has_messages).migrate(0)
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class MigrateHasEmailsToVersion1 < ActiveRecord::Migration
2
+ def self.up
3
+ Rails::Plugin.find(:has_emails).migrate(1)
4
+ end
5
+
6
+ def self.down
7
+ Rails::Plugin.find(:has_emails).migrate(0)
8
+ end
9
+ end
data/test/factory.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Factory
2
+ # Build actions for the class
3
+ def self.build(klass, &block)
4
+ name = klass.to_s.underscore
5
+ define_method("#{name}_attributes", block)
6
+
7
+ module_eval <<-end_eval
8
+ def valid_#{name}_attributes(attributes = {})
9
+ #{name}_attributes(attributes)
10
+ attributes
11
+ end
12
+
13
+ def new_#{name}(attributes = {})
14
+ #{klass}.new(valid_#{name}_attributes(attributes))
15
+ end
16
+
17
+ def create_#{name}(*args)
18
+ record = new_#{name}(*args)
19
+ record.save!
20
+ record.reload
21
+ record
22
+ end
23
+ end_eval
24
+ end
25
+
26
+ build Email do |attributes|
27
+ attributes[:sender] = create_user unless attributes.include?(:sender)
28
+ attributes.reverse_merge!(
29
+ :subject => 'New features',
30
+ :body => 'Lots of new things to talk about... come to the meeting tonight to find out!'
31
+ )
32
+ end
33
+
34
+ build EmailAddress do |attributes|
35
+ attributes.reverse_merge!(
36
+ :name => 'John Smith',
37
+ :spec => 'john.smith@gmail.com'
38
+ )
39
+ end
40
+
41
+ build MessageRecipient do |attributes|
42
+ attributes[:message] = create_message unless attributes.include?(:message)
43
+ attributes[:receiver] = create_user(:login => 'me') unless attributes.include?(:receiver)
44
+ attributes.reverse_merge!(
45
+ :kind => 'to'
46
+ )
47
+ end
48
+ end
@@ -0,0 +1,139 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class EmailAddressByDefaultFunctionalTest < Test::Unit::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 EmailAddresFunctionalsTest < Test::Unit::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 < Test::Unit::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 < Test::Unit::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 < Test::Unit::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 < Test::Unit::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
data/test/test_helper.rb CHANGED
@@ -1,76 +1,13 @@
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
1
  # Load the plugin testing framework
13
- $:.unshift("#{File.dirname(__FILE__)}/../../../../test/plugin_test_helper/lib")
2
+ $:.unshift("#{File.dirname(__FILE__)}/../../plugin_test_helper/lib")
14
3
  require 'rubygems'
15
4
  require 'plugin_test_helper'
16
5
 
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
6
+ # Run the migrations
23
7
  ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate")
24
8
 
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
-
9
+ # Mixin the factory helper
10
+ require File.expand_path("#{File.dirname(__FILE__)}/factory")
36
11
  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
12
+ include Factory
76
13
  end
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class ActionMailerTest < Test::Unit::TestCase
4
+ class TestMailer < ActionMailer::Base
5
+ def signed_up(recipient)
6
+ subject 'Thanks for signing up'
7
+ from 'MyWebApp <welcome@mywebapp.com>'
8
+ recipients recipient
9
+ cc 'Nobody <nobody@mywebapp.com>'
10
+ bcc 'root@mywebapp.com'
11
+ body 'Congratulations!'
12
+ end
13
+ end
14
+
15
+ def setup
16
+ ActionMailer::Base.deliveries = []
17
+ end
18
+
19
+ def test_should_use_camelized_application_name_for_default_subject_prefix
20
+ assert_equal '[AppRoot] ', ActionMailer::Base.default_subject_prefix
21
+ end
22
+
23
+ def test_should_queue_email
24
+ assert_nothing_raised {TestMailer.queue_signed_up('john.smith@gmail.com')}
25
+ assert_equal 1, Email.count
26
+
27
+ email = Email.find(1)
28
+ assert_equal '[AppRoot] Thanks for signing up', email.subject
29
+ assert_equal 'Congratulations!', email.body
30
+ assert_equal 'MyWebApp <welcome@mywebapp.com>', email.sender.with_name
31
+ assert_equal ['john.smith@gmail.com'], email.to.map(&:with_name)
32
+ assert_equal ['Nobody <nobody@mywebapp.com>'], email.cc.map(&:with_name)
33
+ assert_equal ['root@mywebapp.com'], email.bcc.map(&:with_name)
34
+ end
35
+
36
+ def test_should_deliver_email
37
+ email = create_email(
38
+ :subject => 'Hello',
39
+ :body => 'How are you?',
40
+ :sender => create_email_address(:name => 'MyWebApp', :spec => 'welcome@mywebapp.com'),
41
+ :to => create_email_address(:spec => 'john.smith@gmail.com'),
42
+ :cc => create_email_address(:name => 'Nobody', :spec => 'nobody@mywebapp.com'),
43
+ :bcc => create_email_address(:spec => 'root@mywebapp.com')
44
+ )
45
+
46
+ assert_nothing_raised {ActionMailer::Base.deliver_email(email)}
47
+ assert_equal 1, ActionMailer::Base.deliveries.size
48
+
49
+ delivery = ActionMailer::Base.deliveries.first
50
+ assert_equal 'Hello', delivery.subject
51
+ assert_equal 'How are you?', delivery.body
52
+ assert_equal ['welcome@mywebapp.com'], delivery.from
53
+ assert_equal ['john.smith@gmail.com'], delivery.to
54
+ assert_equal ['nobody@mywebapp.com'], delivery.cc
55
+ assert_equal ['root@mywebapp.com'], delivery.bcc
56
+ end
57
+ end