outbox 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +83 -2
- data/lib/outbox.rb +19 -2
- data/lib/outbox/accessor.rb +71 -0
- data/lib/outbox/clients/base.rb +39 -0
- data/lib/outbox/clients/mail_client.rb +40 -0
- data/lib/outbox/clients/test_client.rb +20 -0
- data/lib/outbox/errors.rb +5 -0
- data/lib/outbox/message.rb +47 -0
- data/lib/outbox/message_clients.rb +70 -0
- data/lib/outbox/message_fields.rb +209 -0
- data/lib/outbox/message_types.rb +62 -0
- data/lib/outbox/messages/base.rb +46 -0
- data/lib/outbox/messages/email.rb +77 -0
- data/lib/outbox/version.rb +1 -1
- data/outbox.gemspec +3 -2
- data/spec/outbox/accessor_spec.rb +58 -0
- data/spec/outbox/clients/base_spec.rb +29 -0
- data/spec/outbox/clients/mail_client_spec.rb +43 -0
- data/spec/outbox/clients/test_client_spec.rb +26 -0
- data/spec/outbox/message_spec.rb +103 -0
- data/spec/outbox/messages/base_spec.rb +212 -0
- data/spec/outbox/messages/email_spec.rb +135 -0
- data/spec/outbox_spec.rb +0 -4
- data/spec/spec_helper.rb +9 -0
- metadata +48 -8
@@ -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
|
data/lib/outbox/version.rb
CHANGED
data/outbox.gemspec
CHANGED
@@ -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
|
12
|
-
spec.summary = %q{Outbox is a
|
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
|