has_emails 0.0.1 → 0.1.0

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