bronto 0.0.1

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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bronto.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Martin Gordon
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # Bronto
2
+
3
+ This is a handy library that wraps the Bronto SOAP API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'bronto'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install bronto
18
+
19
+ ## Usage
20
+
21
+ Let's setup some contacts, add them to a list, and then send them a message.
22
+
23
+ 1. Require the library and specify the API Key on the Base class:
24
+
25
+ ```
26
+ require 'bronto'
27
+ Bronto::Base.api_key = "..."
28
+ ```
29
+
30
+ 2. Create two contacts:
31
+
32
+ ```
33
+ contact_1 = Bronto::Contact.new(email: "test_1@example.com")
34
+ contact_2 = Bronto::Contact.new(email: "test_2@example.com")
35
+
36
+ puts contact_1.id
37
+ # => nil
38
+
39
+ puts contact_2.id
40
+ # => nil
41
+
42
+ # You can save multiple objects with one API call using the class `save` method.
43
+ Bronto::Contact.save(contact_1, contact_2)
44
+
45
+ # Both contacts should now have ids.
46
+ puts contact_1.id
47
+ # => "32da24c..."
48
+
49
+ puts contact_2.id
50
+ # => "98cd453..."
51
+ ```
52
+
53
+ 3. Create a list and the contacts:
54
+
55
+ ```
56
+ list = Bronto::List.new(name: "A Test List", label: "This is a test list.")
57
+ list.save
58
+
59
+ list.add_to_list(contact_1, contact_2)
60
+ ```
61
+
62
+ 4. Create a new message and add content:
63
+
64
+ message = Bronto::Message.new(name: "Test Message")
65
+ message.add_content("html", "HTML Subject", "HTML Content")
66
+ message.add_content("text", "Text Subject", "Text Content")
67
+ message.save
68
+
69
+ 5. Create a new delivery with a message and recipients and send it ASAP:
70
+
71
+ delivery = Bronto::Delivery.new(start: Time.now, type: "normal", from_name: "Test", from_email: "test@example.com")
72
+ delivery.message_id = message.id
73
+ delivery.add_recipient(list)
74
+ delivery.save
75
+
76
+ ## Contributing
77
+
78
+ 1. Fork it
79
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
80
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
81
+ 4. Push to the branch (`git push origin my-new-feature`)
82
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
3
+
4
+ require "bundler/gem_tasks"
5
+ require "rake/testtask"
6
+ require "./lib/bronto"
7
+
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.ruby_opts = ["-rubygems"] if defined? Gem
10
+ test.libs << "lib" << "test"
11
+ test.pattern = "test/**/*_test.rb"
12
+ end
data/bronto.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/bronto/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Martin Gordon"]
6
+ gem.email = ["martingordon@gmail.com"]
7
+ gem.description = %q{A Ruby wrapper for the Bronto SOAP API}
8
+ gem.summary = %q{A Ruby wrapper for the Bronto SOAP API}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "bronto"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Bronto::VERSION
17
+
18
+ gem.add_dependency "savon"
19
+
20
+ gem.add_development_dependency "debugger"
21
+ gem.add_development_dependency "turn"
22
+ gem.add_development_dependency "shoulda"
23
+ end
data/lib/bronto.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "savon"
2
+
3
+ require "bronto/base"
4
+ require "bronto/contact"
5
+ require "bronto/delivery"
6
+ require "bronto/field"
7
+ require "bronto/filter"
8
+ require "bronto/list"
9
+ require "bronto/message"
10
+ require "bronto/version"
11
+
12
+ require "core_ext/array"
13
+ require "core_ext/object"
14
+ require "core_ext/string"
15
+
16
+ module Bronto
17
+ class Errors
18
+ attr_accessor :messages
19
+
20
+ def initialize
21
+ self.messages = {}
22
+ end
23
+
24
+ def add(code, message)
25
+ messages[code] = message
26
+ end
27
+
28
+ def clear; messages.clear; end
29
+ def length; messages.length; end
30
+ def count; messages.count; end
31
+
32
+ def to_a(include_codes = true)
33
+ messages.map { |k,v| include_codes ? "#{v} (#{k})" : v }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,198 @@
1
+ module Bronto
2
+ class Base
3
+ attr_accessor :id, :errors
4
+
5
+ # Getter/Setter for global API Key.
6
+ def self.api_key=(api_key)
7
+ @@api_key = api_key
8
+ end
9
+
10
+ def self.api_key
11
+ @@api_key
12
+ end
13
+
14
+ # Simple helper method to convert class name to downcased pluralized version (e.g., Field -> fields).
15
+ def self.plural_class_name
16
+ self.to_s.split("::").last.downcase.pluralize
17
+ end
18
+
19
+ # The primary method used to interface with the SOAP API.
20
+ # This method automatically adds the required session header and returns the actual response section of the SOAP response body.
21
+ #
22
+ # If a symbol is passed in, it is converted to "method_plural_class_name" (e.g., :read => read_lists). A string
23
+ # method is used as-is.
24
+ # Pass in a block and assign a hash to soap.body with a structure appropriate to the method call.
25
+ def self.request(method, refresh_header = false, &_block)
26
+ _soap_header = self.soap_header(refresh_header)
27
+
28
+ method = "#{method}_#{plural_class_name}" if method.is_a? Symbol
29
+
30
+ resp = api.request(:v4, method.to_sym) do
31
+ soap.header = _soap_header
32
+ evaluate(&_block) if _block # See Savon::Client#evaluate; necessary to preserve scope.
33
+ end
34
+
35
+ resp.body["#{method}_response".to_sym]
36
+ end
37
+
38
+ # Sets up the Savon SOAP client object (if necessary) and returns it.
39
+ def self.api
40
+ return @api unless @api.nil?
41
+
42
+ @api = Savon::Client.new do
43
+ wsdl.endpoint = "https://api.bronto.com/v4"
44
+ wsdl.namespace = "http://api.bronto.com/v4"
45
+ end
46
+ end
47
+
48
+ # Helper method to retrieve the session ID and return a SOAP header.
49
+ # Will return a header with the same initial session ID unless the `refresh` argument is `true`.
50
+ def self.soap_header(refresh = false)
51
+ return @soap_header if !refresh and @soap_header.present?
52
+
53
+ resp = api.request(:v4, :login) do
54
+ soap.body = { api_token: self.api_key }
55
+ end
56
+
57
+ @soap_header = { "v4:sessionHeader" => { session_id: resp.body[:login_response][:return] } }
58
+ end
59
+
60
+ # Saves a collection of Bronto::Base objects.
61
+ # Objects without IDs are considered new and are `create`d; objects with IDs are considered existing and are `update`d.
62
+ def self.save(*objs)
63
+ objs = objs.flatten
64
+ update(objs.select { |o| o.id.present? })
65
+ create(objs.select { |o| o.id.blank? })
66
+ objs
67
+ end
68
+
69
+ # Finds objects matching the `filter` (a Bronto::Filter instance).
70
+ def self.find(filter = Bronto::Filter.new, page_number = 1)
71
+ resp = request(:read) do
72
+ soap.body = { filter: filter.to_hash, page_number: page_number }
73
+ end
74
+
75
+ Array.wrap(resp[:return]).map { |hash| new(hash) }
76
+ end
77
+
78
+ # Tells the remote server to create the passed in collection of Bronto::Base objects.
79
+ # The object should implement `to_hash` to return a hash in the format expected by the SOAP API.
80
+ #
81
+ # Returns the same collection of objects that was passed in. Objects whose creation succeeded will be assigned the
82
+ # ID returned from Bronto.
83
+ def self.create(*objs)
84
+ objs = objs.flatten
85
+
86
+ resp = request(:add) do
87
+ soap.body = {
88
+ plural_class_name => objs.map(&:to_hash)
89
+ }
90
+ end
91
+
92
+ objs.each { |o| o.errors.clear }
93
+
94
+ Array.wrap(resp[:return][:results]).each_with_index do |result, i|
95
+ if result[:is_new] and !result[:is_error]
96
+ objs[i].id = result[:id]
97
+ elsif result[:is_error]
98
+ objs[i].errors.add(result[:error_code], result[:error_string])
99
+ end
100
+ end
101
+
102
+ objs
103
+ end
104
+
105
+ # Updates a collection of Bronto::Base objects. The objects should exist on the remote server.
106
+ # The object should implement `to_hash` to return a hash in the format expected by the SOAP API.
107
+ def self.update(*objs)
108
+ objs = objs.flatten
109
+
110
+ resp = request(:update) do
111
+ soap.body = {
112
+ plural_class_name => objs.map(&:to_hash)
113
+ }
114
+ end
115
+
116
+ objs.each { |o| o.errors.clear }
117
+ objs
118
+ end
119
+
120
+ # Destroys a collection of Bronto::Base objects on the remote server.
121
+ #
122
+ # Returns the same collection of objects that was passed in. Objects whose destruction succeeded will
123
+ # have a nil ID.
124
+ def self.destroy(*objs)
125
+ objs = objs.flatten
126
+
127
+ resp = request(:delete) do
128
+ soap.body = {
129
+ plural_class_name => objs.map { |o| { id: o.id }}
130
+ }
131
+ end
132
+
133
+ Array.wrap(resp[:return][:results]).each_with_index do |result, i|
134
+ if result[:is_error]
135
+ objs[i].errors.add(result[:error_code], result[:error_string])
136
+ else
137
+ objs[i].id = nil
138
+ end
139
+ end
140
+
141
+ objs
142
+ end
143
+
144
+ # Accepts a hash whose keys should be setters on the object.
145
+ def initialize(options = {})
146
+ self.errors = Errors.new
147
+ options.each { |k,v| send("#{k}=", v) if respond_to?("#{k}=") }
148
+ end
149
+
150
+ # `to_hash` should be overridden to provide a hash whose structure matches the structure expected by the API.
151
+ def to_hash
152
+ {}
153
+ end
154
+
155
+ # Convenience instance method that calls the class `request` method.
156
+ def request(method, &block)
157
+ self.class.request(method, &block)
158
+ end
159
+
160
+ def reload
161
+ return if self.id.blank?
162
+
163
+ # The block below is evaluated in a weird scope so we need to capture self as _self for use inside the block.
164
+ _self = self
165
+
166
+ resp = request(:read) do
167
+ soap.body = { filter: { id: _self.id } }
168
+ end
169
+
170
+ resp[:return].each do |k, v|
171
+ self.send("#{k}=", v) if self.respond_to? "#{k}="
172
+ end
173
+
174
+ nil
175
+ end
176
+
177
+ # Saves the object. If the object has an ID, it is updated. Otherwise, it is created.
178
+ def save
179
+ id.blank? ? create : update
180
+ end
181
+
182
+ # Creates the object. See `Bronto::Base.create` for more info.
183
+ def create
184
+ res = self.class.create(self)
185
+ res.first
186
+ end
187
+
188
+ # Updates the object. See `Bronto::Base.update` for more info.
189
+ def update
190
+ self.class.update(self).first
191
+ end
192
+
193
+ # Destroys the object. See `Bronto::Base.destroy` for more info.
194
+ def destroy
195
+ self.class.destroy(self).first
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,62 @@
1
+ module Bronto
2
+ class Contact < Base
3
+ attr_accessor :email, :fields, :lists
4
+
5
+ # Finds contacts based on the `filter` (Bronto::Filter object).
6
+ # * `page_number` is the page of contacts to request. Bronto doesn't specify how many contacts are returned per page,
7
+ # only that you should keep increasing the number until no more contacts are returned.
8
+ # * `fields` can be an array of field IDs or an array of Field objects.
9
+ # * `include_lists` determines whether to include the list IDs each contact belongs to.
10
+ def self.find(filter = Bronto::Filter.new, page_number = 1, fields = nil, include_lists = false)
11
+ body = { filter: filter.to_hash, page_number: page_number }
12
+
13
+ body[:fields] = Array.wrap(fields).map { |f| f.is_a?(Bronto::Field) ? f.id : f } if Array(fields).length > 0
14
+ body[:include_lists] = include_lists
15
+
16
+ resp = request(:read) do
17
+ soap.body = body
18
+ end
19
+
20
+ Array.wrap(resp[:return]).map { |hash| new(hash) }
21
+ end
22
+
23
+ def initialize(options = {})
24
+ self.fields = {}
25
+ fields = options.delete(:fields)
26
+ Array.wrap(fields).each { |field| set_field(field[:field_id], field[:content]) }
27
+
28
+ super(options)
29
+ end
30
+
31
+ def to_hash
32
+ if id.present?
33
+ { id: id, email: email, fields: fields.values.map(&:to_hash) }
34
+ else
35
+ { email: email, fields: fields.values.map(&:to_hash) }
36
+ end
37
+ end
38
+
39
+ def set_field(field, value)
40
+ id = field.is_a?(Bronto::Field) ? field.id : field
41
+ self.fields[id] = Field.new(id, value)
42
+ end
43
+
44
+ def get_field(field)
45
+ id = field.is_a?(Bronto::Field) ? field.id : field
46
+ self.fields[id].try(:content)
47
+ end
48
+
49
+ class Field
50
+ attr_accessor :field_id, :content
51
+
52
+ def initialize(id, content)
53
+ self.field_id = id
54
+ self.content = content
55
+ end
56
+
57
+ def to_hash
58
+ { field_id: field_id, content: content }
59
+ end
60
+ end
61
+ end
62
+ end