outbox 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ module Outbox
2
+ module MessageTypes
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ # Registers a message type for sending & creates accessors.
9
+ #
10
+ # Message.register_message_type :telepathy, TelepathyMessage
11
+ # message = Message.new do
12
+ # telepathy do
13
+ # thought 'Hello world.'
14
+ # end
15
+ # end
16
+ # message.telepathy.thought #=> 'Hello world.'
17
+ #
18
+ # Upon deliver the audience object will be checked for the registered
19
+ # message type.
20
+ #
21
+ # message.deliver telepathy: 'Bob'
22
+ def register_message_type(name, message_type)
23
+ message_types[name.to_sym] = message_type
24
+ define_message_type_reader(name, message_type)
25
+ define_message_type_writer(name)
26
+ end
27
+
28
+ # Returns a hash of the registred message types, where the key is the name
29
+ # of the message type and the value is the message type class.
30
+ def message_types
31
+ @message_types ||= {}
32
+ end
33
+
34
+ protected
35
+
36
+ def define_message_type_reader(name, message_type)
37
+ ivar_name = "@#{name}"
38
+ define_method(name) do |options = nil, &block|
39
+ if options || block
40
+ instance_variable_set(ivar_name, message_type.new(options, &block))
41
+ else
42
+ instance_variable_get(ivar_name)
43
+ end
44
+ end
45
+ end
46
+
47
+ def define_message_type_writer(name)
48
+ define_method("#{name}=") do |value|
49
+ instance_variable_set("@#{name}", value)
50
+ end
51
+ end
52
+ end
53
+
54
+ protected
55
+
56
+ def assign_message_type_values(values)
57
+ values.each do |key, value|
58
+ self.public_send(key, value) if self.respond_to?(key)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,46 @@
1
+ module Outbox
2
+ module Messages
3
+ class Base
4
+ include MessageClients
5
+ include MessageFields
6
+
7
+ # Make a new message. Every message type can be created using a hash,
8
+ # block, or direct assignment.
9
+ #
10
+ # message = Email.new to: 'someone@example.com', from: 'company@example.com'
11
+ # message = Email.new do
12
+ # to 'someone@example.com'
13
+ # from 'company@example.com'
14
+ # end
15
+ # message = Email.new
16
+ # message.to = 'someone@example.com'
17
+ # message.from = 'company@example.com'
18
+ def initialize(fields = nil, &block)
19
+ @fields = {}
20
+ @client = self.class.default_client and self.class.default_client.dup
21
+
22
+ self.fields = self.class.defaults
23
+
24
+ if block_given?
25
+ instance_eval(&block)
26
+ else
27
+ self.fields = fields unless fields.nil?
28
+ end
29
+ end
30
+
31
+ # Sets the 'audience' for this message. All message types must implement
32
+ # this method. By default, this is an alias for a 'to' field if present.
33
+ def audience=(audience)
34
+ self.to = audience if self.respond_to?(:to=)
35
+ end
36
+
37
+ # Validates the current message and delivers the message using the
38
+ # defined client.
39
+ def deliver(audience = nil)
40
+ self.audience = audience if audience
41
+ validate_fields
42
+ client.deliver(self)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,77 @@
1
+ require 'mail'
2
+
3
+ module Outbox
4
+ module Messages
5
+ # Email messages use the same interface for composing emails as
6
+ # Mail::Message from the mail gem. The only difference is the abstraction
7
+ # of the client interface, which allows you to send the email using
8
+ # whatever client you wish.
9
+ #
10
+ # email = Outbox::Messages::Email.new do
11
+ # to 'nicolas@test.lindsaar.net.au'
12
+ # from 'Mikel Lindsaar <mikel@test.lindsaar.net.au>'
13
+ # subject 'First multipart email sent with Mail'
14
+ #
15
+ # text_part do
16
+ # body 'This is plain text'
17
+ # end
18
+ #
19
+ # html_part do
20
+ # content_type 'text/html; charset=UTF-8'
21
+ # body '<h1>This is HTML</h1>'
22
+ # end
23
+ # end
24
+ # email.client :mandrill, api_key: '...'
25
+ # email.deliver
26
+ class Email < Base
27
+ register_client_alias :mail, Outbox::Clients::MailClient
28
+
29
+ default_client :mail
30
+
31
+ required_fields :smtp_envelope_from, :smtp_envelope_to,
32
+ :encoded, accessor: false
33
+
34
+ fields :bcc, :cc, :content_description, :content_disposition,
35
+ :content_id, :content_location, :content_transfer_encoding,
36
+ :content_type, :date, :from, :in_reply_to, :keywords,
37
+ :message_id, :mime_version, :received, :references, :reply_to,
38
+ :resent_bcc, :resent_cc, :resent_date, :resent_from,
39
+ :resent_message_id, :resent_sender, :resent_to, :return_path,
40
+ :sender, :to, :comments, :subject, accessor: false
41
+
42
+ def initialize(fields = nil, &block)
43
+ @message = ::Mail::Message.new
44
+ super
45
+ end
46
+
47
+ # Returns the internal Mail::Message instance
48
+ def message_object
49
+ @message
50
+ end
51
+
52
+ def audience=(audience)
53
+ case audience
54
+ when String, Array
55
+ self.to = audience
56
+ else
57
+ audience = Outbox::Accessor.new(audience)
58
+ self.to = audience[:to]
59
+ self.cc = audience[:cc]
60
+ self.bcc = audience[:bcc]
61
+ end
62
+ end
63
+
64
+ def method_missing(method, *args, &block)
65
+ if @message.respond_to?(method)
66
+ @message.public_send(method, *args, &block)
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ def respond_to_missing?(method, include_private = false)
73
+ @message.respond_to?(method, include_private) || super
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,3 +1,3 @@
1
1
  module Outbox
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Outbox::VERSION
9
9
  spec.authors = ['Pete Browne']
10
10
  spec.email = ['pete.browne@localmed.com']
11
- spec.description = %q{A Rails engine and generic interface for sending email, SMS, & push notifications.}
12
- spec.summary = %q{Outbox is a Rails engine and generic interface for sending notifications using a variety of delivery methods, with built-in support for the most popular SaaS solutions.}
11
+ spec.description = %q{A generic interface for sending email, SMS, & push notifications.}
12
+ spec.summary = %q{Outbox is a generic interface for sending notifications using a variety of protocols, with built-in support for the most popular SaaS solutions.}
13
13
  spec.homepage = 'https://github.com/localmed/outbox'
14
14
  spec.license = 'MIT'
15
15
 
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
+ spec.add_runtime_dependency 'mail', '~> 2.5.3'
21
22
  spec.add_development_dependency 'bundler', '~> 1.3'
22
23
  spec.add_development_dependency 'rake'
23
24
  spec.add_development_dependency 'rspec'
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Outbox::Accessor do
4
+ context 'with a hash' do
5
+ it 'reads properties via symbol' do
6
+ hash = { 'a' => 1, :b => 2 }
7
+ hash_accessor = Outbox::Accessor.new(hash)
8
+ expect(hash_accessor[:a]).to eq(1)
9
+ expect(hash_accessor[:b]).to eq(2)
10
+ expect(hash_accessor[:c]).to be_nil
11
+ end
12
+
13
+ it 'reads properties via string' do
14
+ hash = { 'a' => 1, :b => 2 }
15
+ hash_accessor = Outbox::Accessor.new(hash)
16
+ expect(hash_accessor['a']).to eq(1)
17
+ expect(hash_accessor['b']).to eq(2)
18
+ expect(hash_accessor['c']).to be_nil
19
+ end
20
+
21
+ it 'writes arbitrary properties' do
22
+ hash = {}
23
+ hash_accessor = Outbox::Accessor.new(hash)
24
+ hash_accessor[:a] = 1
25
+ hash_accessor['b'] = 2
26
+ expect(hash_accessor.object[:a]).to eq(1)
27
+ expect(hash_accessor.object[:b]).to eq(2)
28
+ end
29
+ end
30
+
31
+ context 'with an object' do
32
+ class MockObj
33
+ attr_accessor :a, :b
34
+ end
35
+
36
+ it 'reads properties it responds to' do
37
+ object = MockObj.new
38
+ object.a = 1
39
+ object.b = 2
40
+ object_accessor = Outbox::Accessor.new(object)
41
+ expect(object_accessor[:a]).to eq(1)
42
+ expect(object_accessor[:b]).to eq(2)
43
+ expect(object_accessor[:c]).to be_nil
44
+ end
45
+
46
+ it 'writes properties it responds to' do
47
+ object = MockObj.new
48
+ object.a = 1
49
+ object_accessor = Outbox::Accessor.new(object)
50
+ expect(object_accessor[:a]).to eq(1)
51
+ object_accessor[:a] = 2
52
+ expect(object_accessor.object.a).to eq(2)
53
+ expect {
54
+ object_accessor[:c] = 2
55
+ }.not_to raise_error()
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Outbox::Clients::Base do
4
+ class Client < Outbox::Clients::Base
5
+ defaults foo: 10
6
+ end
7
+
8
+ describe '.defaults' do
9
+ it 'defines default settings' do
10
+ client = Client.new
11
+ expect(client.settings[:foo]).to eq(10)
12
+ end
13
+ end
14
+
15
+ describe '.new' do
16
+ it 'initializes settings' do
17
+ client = Client.new foo: 1, bar: 2
18
+ expect(client.settings[:foo]).to eq(1)
19
+ expect(client.settings[:bar]).to eq(2)
20
+ end
21
+ end
22
+
23
+ describe '#deliver' do
24
+ it 'raises an error' do
25
+ client = Client.new
26
+ expect{client.deliver(Outbox::Messages::Base.new)}.to raise_error(NotImplementedError)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Outbox::Clients::MailClient do
4
+ describe '.new' do
5
+ it 'configures the delivery method' do
6
+ client = Outbox::Clients::MailClient.new delivery_method: :smtp, smtp_settings: {
7
+ address: 'smtp.mockup.com',
8
+ port: 587
9
+ }
10
+ expect(client.delivery_method).to eq(:smtp)
11
+ expect(client.delivery_method_settings[:address]).to eq('smtp.mockup.com')
12
+ expect(client.delivery_method_settings[:port]).to eq(587)
13
+ end
14
+ end
15
+
16
+ describe '#deliver' do
17
+ before do
18
+ @client = Outbox::Clients::MailClient.new delivery_method: :smtp, smtp_settings: {
19
+ address: 'smtp.mockup.com',
20
+ port: 587
21
+ }
22
+ @email = Outbox::Messages::Email.new do
23
+ to 'bob@gmail.com'
24
+ from 'john@gmail.com'
25
+ body 'Hello world.'
26
+ end
27
+ end
28
+
29
+ it 'configures the delivery method' do
30
+ message = double(:message, deliver: true)
31
+ expect(@email.message_object).to receive(:dup) { message }
32
+ expect(message).to receive(:delivery_method).with(:smtp, address: 'smtp.mockup.com', port: 587)
33
+ @client.deliver(@email)
34
+ end
35
+
36
+ it 'delivers the email' do
37
+ message = double(:message, delivery_method: true)
38
+ expect(@email.message_object).to receive(:dup) { message }
39
+ expect(message).to receive(:deliver)
40
+ @client.deliver(@email)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Outbox::Clients::TestClient do
4
+ class Message < Outbox::Messages::Base
5
+ default_client :test
6
+ fields :to, :body
7
+ end
8
+
9
+ before { Outbox::Clients::TestClient.deliveries.clear }
10
+
11
+ it 'defaults to no deliveries' do
12
+ expect(Outbox::Clients::TestClient.deliveries).to be_empty
13
+ end
14
+
15
+ it 'appends the delivery to to deliveries array' do
16
+ message = Message.new(to: 'Bob', body: 'Hi Bob')
17
+ message.deliver
18
+ expect(Outbox::Clients::TestClient.deliveries.length).to eq(1)
19
+ expect(Outbox::Clients::TestClient.deliveries.first).to be(message)
20
+ end
21
+
22
+ it 'saves configuration' do
23
+ client = Outbox::Clients::TestClient.new foo: 1, bar: 2
24
+ expect(client.settings).to eq(foo: 1, bar: 2)
25
+ end
26
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe Outbox::Message do
4
+ class Telepathy < Outbox::Messages::Base
5
+ default_client :test
6
+ fields :to, :from, :thought
7
+ end
8
+
9
+ class MessageInABottle < Outbox::Messages::Base
10
+ default_client :test
11
+ fields :to, :bottle, :message
12
+ end
13
+
14
+ before do
15
+ @original_message_types = Outbox::Message.message_types
16
+ Outbox::Message.register_message_type :telepathy, Telepathy
17
+ Outbox::Message.register_message_type :bottle, MessageInABottle
18
+ end
19
+
20
+ after do
21
+ Outbox::Message.instance_variable_set :@message_types, @original_message_types
22
+ end
23
+
24
+ describe '.message_types' do
25
+ it 'includes email' do
26
+ expect(Outbox::Message.message_types[:email]).to eq(Outbox::Messages::Email)
27
+ end
28
+ end
29
+
30
+ describe '.register_message_type' do
31
+ it 'adds a message type' do
32
+ expect(Outbox::Message.message_types[:telepathy]).to eq(Telepathy)
33
+ end
34
+
35
+ it 'defines a block accessor for that type' do
36
+ message = Outbox::Message.new
37
+ message.telepathy do
38
+ thought 'Hello world.'
39
+ end
40
+ expect(message.telepathy.thought).to eq('Hello world.')
41
+ end
42
+
43
+ it 'defines a writer for that type' do
44
+ message = Outbox::Message.new
45
+ message.telepathy = Telepathy.new thought: 'Hello world.'
46
+ expect(message.telepathy.thought).to eq('Hello world.')
47
+ end
48
+ end
49
+
50
+ describe '.new' do
51
+ it 'initializes with a block' do
52
+ message = Outbox::Message.new do
53
+ telepathy do
54
+ thought 'Hello world.'
55
+ end
56
+ end
57
+ expect(message.telepathy.thought).to eq('Hello world.')
58
+ end
59
+
60
+ it 'initializes with a hash' do
61
+ message = Outbox::Message.new telepathy: { thought: 'Hello world.' }
62
+ expect(message.telepathy.thought).to eq('Hello world.')
63
+ end
64
+ end
65
+
66
+ describe '#deliver' do
67
+ before do
68
+ @message = Outbox::Message.new do
69
+ telepathy do
70
+ thought 'Hello world.'
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'with a hash' do
76
+ it 'deilvers the message to that audience' do
77
+ @message.deliver telepathy: 'Bob', bottle: 'John'
78
+ message = Outbox::Clients::TestClient.deliveries.last
79
+ expect(Outbox::Clients::TestClient.deliveries.length).to eq(1)
80
+ expect(message).to be(@message.telepathy)
81
+ expect(message.to).to eq('Bob')
82
+ end
83
+ end
84
+
85
+ context 'with an audience object' do
86
+ it 'delivers the messages to the audience' do
87
+ audience = OpenStruct.new
88
+ audience.telepathy = 'Bob'
89
+ audience.bottle = 'John'
90
+
91
+ @message.bottle do
92
+ bottle 'Coke'
93
+ message 'Hello world.'
94
+ end
95
+
96
+ @message.deliver(audience)
97
+ expect(Outbox::Clients::TestClient.deliveries.length).to eq(2)
98
+ expect(@message.telepathy.to).to eq('Bob')
99
+ expect(@message.bottle.to).to eq('John')
100
+ end
101
+ end
102
+ end
103
+ end