bronto 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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