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