outbox 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.
- 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
|