active_esp 0.1.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +81 -0
- data/Rakefile +27 -0
- data/active_esp.gemspec +28 -0
- data/lib/active_esp/configuration.rb +125 -0
- data/lib/active_esp/inflections.rb +3 -0
- data/lib/active_esp/list.rb +42 -0
- data/lib/active_esp/providers/base.rb +51 -0
- data/lib/active_esp/providers/icontact.rb +164 -0
- data/lib/active_esp/providers/interface.rb +15 -0
- data/lib/active_esp/providers/mail_chimp.rb +45 -0
- data/lib/active_esp/providers.rb +18 -0
- data/lib/active_esp/rfc822.rb +34 -0
- data/lib/active_esp/subscriber.rb +126 -0
- data/lib/active_esp/version.rb +3 -0
- data/lib/active_esp.rb +28 -0
- data/spec/factories.rb +7 -0
- data/spec/models/subscriber_spec.rb +142 -0
- data/spec/providers/icontact_spec.rb +5 -0
- data/spec/providers/mail_chimp_spec.rb +5 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/matchers/implement_interface.rb +13 -0
- metadata +178 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Brian Morton
|
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,81 @@
|
|
1
|
+
# ActiveESP
|
2
|
+
|
3
|
+
**NOTE**: ActiveESP is still a very early project with limited support. Be careful when using it as implementations are still changing rapidly. Feel free to contribute and help us with our first official release!
|
4
|
+
|
5
|
+
ActiveESP is an abstraction library for managing subscribers, campaigns, and other email marketing facilities. It aims to provide a consistent interface to interact with the numerous ESPs operating with different terminologies and strategies.
|
6
|
+
|
7
|
+
This framework provides some common classes for managing email marketing data structures as well as the adapters for interfacing with the providers' APIs.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'active_esp'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install active_esp
|
22
|
+
|
23
|
+
## Basic usage
|
24
|
+
|
25
|
+
For basic usage, an instance of any of the supported providers may be instantiated and used independently by calling methods on the provider directly.
|
26
|
+
|
27
|
+
```
|
28
|
+
subscriber = ActiveESP::Subscriber.new(
|
29
|
+
:email => 'brian@xq3.net',
|
30
|
+
:name => 'Brian Morton'
|
31
|
+
)
|
32
|
+
|
33
|
+
provider = ActiveESP::Providers::MailChimp.new(
|
34
|
+
:api_key => '12345678901234567890-us4'
|
35
|
+
)
|
36
|
+
|
37
|
+
list = ActiveESP::List.new(:id => '03b3b0f203')
|
38
|
+
|
39
|
+
provider.subscribe(subscriber, list) # => true
|
40
|
+
```
|
41
|
+
|
42
|
+
## Configuring and using a shared provider
|
43
|
+
|
44
|
+
For a better integration into an application, using a shared provider tries to hide as much of the provider's specific implementation details as it can. After the provider is configured, as many calls as possible are genericized to work across all implementations.
|
45
|
+
|
46
|
+
```
|
47
|
+
ActiveESP.configure do |c|
|
48
|
+
c.provider :mail_chimp
|
49
|
+
c.credentials :api_key => '12345678901234567890-us4'
|
50
|
+
end
|
51
|
+
|
52
|
+
ActiveESP.provider # => #<ActiveESP::Providers::MailChimp:0x007f868b33fb30 @api_key="12345678901234567890-us4">
|
53
|
+
```
|
54
|
+
|
55
|
+
## Using convenience methods
|
56
|
+
|
57
|
+
Convenience methods are defined on the basic Subscriber and List classes for sending themselves in an API request to the shared provider. After configuring a provider, these methods can be fired on any subscriber or list object.
|
58
|
+
|
59
|
+
```
|
60
|
+
ActiveESP.configure do |c|
|
61
|
+
c.provider :mail_chimp
|
62
|
+
c.credentials :api_key => '12345678901234567890-us4'
|
63
|
+
end
|
64
|
+
|
65
|
+
member = ActiveESP::Subscriber.new(
|
66
|
+
:email => 'brian@xq3.net',
|
67
|
+
:name => 'Brian Morton'
|
68
|
+
)
|
69
|
+
|
70
|
+
list = ActiveESP::List.new(:id => '123456')
|
71
|
+
|
72
|
+
member.subscribe!(list)
|
73
|
+
```
|
74
|
+
|
75
|
+
## Contributing
|
76
|
+
|
77
|
+
1. Fork it
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
81
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new('spec')
|
6
|
+
|
7
|
+
# If you want to make this the default task
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
desc 'Execute a console with the environment preloaded'
|
11
|
+
task :console do
|
12
|
+
exec 'irb -rubygems -I lib -r active_esp.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
namespace :docs do
|
16
|
+
task :generate do
|
17
|
+
exec 'yard doc'
|
18
|
+
end
|
19
|
+
|
20
|
+
task :server do
|
21
|
+
exec 'yard server --reload --server thin'
|
22
|
+
end
|
23
|
+
|
24
|
+
task :stats do
|
25
|
+
exec 'yard stats'
|
26
|
+
end
|
27
|
+
end
|
data/active_esp.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/active_esp/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Brian Morton"]
|
6
|
+
gem.email = ["bmorton@sdreader.com"]
|
7
|
+
gem.description = %q{Framework and tools for managing email service providers.}
|
8
|
+
gem.summary = %q{ActiveESP is an abstraction library for managing subscribers, campaigns, and other email marketing facilities. It provides a consistent interface to interact with the numerous ESPs.}
|
9
|
+
gem.homepage = "http://1703india.st/active_esp"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "active_esp"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = ActiveESP::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency "json", "> 1.4.0"
|
19
|
+
gem.add_runtime_dependency "httparty", "~> 0.8.1"
|
20
|
+
gem.add_runtime_dependency "activesupport", ">= 3.0.0"
|
21
|
+
gem.add_runtime_dependency "shuber-interface", "~> 0.0.4"
|
22
|
+
|
23
|
+
gem.add_development_dependency "rspec", "~> 2.8.0"
|
24
|
+
gem.add_development_dependency "factory_girl", "~> 2.5.1"
|
25
|
+
gem.add_development_dependency "yard", "~> 0.7.5"
|
26
|
+
gem.add_development_dependency "thin", "~> 1.3.1"
|
27
|
+
gem.add_development_dependency "redcarpet", "~> 2.1.0"
|
28
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
module Configuration
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Instantiates a shared provider singleton for access through ActiveESP.provider
|
7
|
+
#
|
8
|
+
# To use the configuration method, call it with a block and specify a provider and
|
9
|
+
# any credentials that provider expects as a hash.
|
10
|
+
#
|
11
|
+
# == Example Usage
|
12
|
+
# ActiveESP.configure do |c|
|
13
|
+
# c.provider :mail_chimp
|
14
|
+
# c.credentials :api_key => '12345678901234567890-us4'
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @param [Block] A block of method calls to set up a provider
|
18
|
+
# @return [Provider] Returns the shared provider that was instantiated due to
|
19
|
+
# the provided configuration
|
20
|
+
def configure(&block)
|
21
|
+
yield self
|
22
|
+
reset_provider
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the shared provider and optionally sets up the provider with the class
|
26
|
+
# passed as its only argument.
|
27
|
+
#
|
28
|
+
# If a provider_class is provided, the method will set the class to use when
|
29
|
+
# instantiating the shared provider. Since provider and credentials could
|
30
|
+
# potentially be set in any order in the configuration block, we only want
|
31
|
+
# to try to instantiate a provider if the credentials have already been set.
|
32
|
+
# If the credentials have not been set, a new provider will not be returned
|
33
|
+
# until the method is called after credentials have been set.
|
34
|
+
#
|
35
|
+
# == Example Usage
|
36
|
+
# ActiveESP.provider :mail_chimp # => nil
|
37
|
+
#
|
38
|
+
# If the method is called with no arguments, it will return the shared provider,
|
39
|
+
# instantiating it if necessary. Again, this relies on the provider class and
|
40
|
+
# credentials being previously set.
|
41
|
+
#
|
42
|
+
# == Example Usage
|
43
|
+
# ActiveESP.provider # => #<ActiveESP::Providers::MailChimp:0x007f868b33fb30 @api_key="test">
|
44
|
+
#
|
45
|
+
# @param [Symbol] An optional symbol representing the provider class to use
|
46
|
+
# @return [Provider] The shared provider object or nil if it can't be instantiated
|
47
|
+
def provider(provider_class = nil)
|
48
|
+
# If the provider hasn't been instantiated yet and we're not using this method
|
49
|
+
# to set the provider's class, we need to reset the provider so that it can be
|
50
|
+
# returned properly.
|
51
|
+
reset_provider if !@provider && !provider_class
|
52
|
+
return @provider unless provider_class
|
53
|
+
self.provider_class = provider_class
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the shared provider to any custom instantiated provider
|
57
|
+
#
|
58
|
+
# When necessary, the configuration method can be skipped and a provider can be
|
59
|
+
# directly assigned to be the shared provider.
|
60
|
+
#
|
61
|
+
# == Example Usage
|
62
|
+
# ActiveESP.provider = ActiveESP::Providers::MailChimp.new(
|
63
|
+
# :api_key => '12345678901234567890-us4'
|
64
|
+
# )
|
65
|
+
#
|
66
|
+
# @param [Provider] An instance of an object in the ActiveESP::Providers namespace
|
67
|
+
# @return [Provider] The passed provider object
|
68
|
+
def provider=(shared_provider)
|
69
|
+
@provider = shared_provider
|
70
|
+
end
|
71
|
+
|
72
|
+
# Assigns and/or returns the provider's credentials when configuring via the
|
73
|
+
# configuration method
|
74
|
+
#
|
75
|
+
# This is used to help the configuration method assign credentials conveniently.
|
76
|
+
# It works outside of the configuration block as well.
|
77
|
+
#
|
78
|
+
# == Example Usage
|
79
|
+
# ActiveESP.credentials :api_key => '12345678901234567890-us4'
|
80
|
+
#
|
81
|
+
# @param [Hash] provider_credentials An optional hash of values needed to
|
82
|
+
# instantiate a provider object
|
83
|
+
# @return [Hash] The credentials required to instantiate a provider object
|
84
|
+
def credentials(provider_credentials = nil)
|
85
|
+
@credentials = provider_credentials if provider_credentials
|
86
|
+
@credentials
|
87
|
+
end
|
88
|
+
|
89
|
+
# Assigns the provider's credentials
|
90
|
+
#
|
91
|
+
# == Example Usage
|
92
|
+
# ActiveESP.credentials = { :api_key => '12345678901234567890-us4' }
|
93
|
+
#
|
94
|
+
# @param [Hash] provider_credentials A hash of values needed to instantiate a
|
95
|
+
# provider object
|
96
|
+
# @return [Hash] The provided credentials
|
97
|
+
def credentials=(provider_credentials)
|
98
|
+
@credentials = provider_credentials
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Defines which provider class to use and resets the shared provider
|
104
|
+
#
|
105
|
+
# @return nil
|
106
|
+
def provider_class=(klass)
|
107
|
+
@provider_class = klass
|
108
|
+
@provider = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
# Clears the shared provider, reinstantiates it, and returns it
|
112
|
+
#
|
113
|
+
# @return [Provider] shared provider instance
|
114
|
+
def reset_provider
|
115
|
+
begin
|
116
|
+
full_provider_class_name = "ActiveESP::Providers::#{@provider_class.to_s.classify}".constantize
|
117
|
+
rescue NameError
|
118
|
+
raise ActiveESP::ProviderNotSupportedException.new("#{@provider_class.to_s} is not a supported provider.")
|
119
|
+
end
|
120
|
+
|
121
|
+
@provider = full_provider_class_name.new(@credentials)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
class List
|
3
|
+
# Returns or sets the list's identifier
|
4
|
+
#
|
5
|
+
# @return [String]
|
6
|
+
attr_accessor :id
|
7
|
+
|
8
|
+
# Returns or sets the list's name
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
attr_accessor :name
|
12
|
+
|
13
|
+
# Initialize object with an optional attributes hash
|
14
|
+
#
|
15
|
+
# @param [Hash] attributes An optional hash of attributes to assign to the new instance
|
16
|
+
def initialize(attributes = nil)
|
17
|
+
if attributes.is_a? Hash
|
18
|
+
attributes.each do |key, value|
|
19
|
+
self.send(key.to_s + "=", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Accessing commonly used API calls
|
25
|
+
|
26
|
+
# Add the given subscriber to this list.
|
27
|
+
#
|
28
|
+
# @see ActiveESP::Providers::Interface#subscribe
|
29
|
+
def subscribe!(subscriber)
|
30
|
+
raise ActiveESP::ProviderNotConfiguredException unless ActiveESP.provider
|
31
|
+
ActiveESP.provider.subscribe(subscriber, self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Remove the given subscriber from this list.
|
35
|
+
#
|
36
|
+
# @see ActiveESP::Providers::Interface#unsubscribe
|
37
|
+
def unsubscribe!(subscriber)
|
38
|
+
raise ActiveESP::ProviderNotConfiguredException unless ActiveESP.provider
|
39
|
+
ActiveESP.provider.unsubscribe(subscriber, self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
# A +Provider+ in ActiveESP is an individual ESP class that implements the required
|
3
|
+
# methods to conform to a +Provider+ protocol.
|
4
|
+
#
|
5
|
+
# To make things work smoothly, the framework expects a +Provider+ to extend from
|
6
|
+
# +ActiveESP::Providers::Base+. This allows common functionality to be shared
|
7
|
+
# amongst the providers as well as exceptions to be raised when the provider
|
8
|
+
# doesn't implement a required method.
|
9
|
+
#
|
10
|
+
module Providers
|
11
|
+
class Base
|
12
|
+
implements ActiveESP::Providers::Interface
|
13
|
+
# Returns or sets the API key
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
attr_accessor :api_key
|
17
|
+
|
18
|
+
# Sets the endpoint base URL without a trailing slash
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
attr_writer :endpoint
|
22
|
+
cattr_accessor :endpoint
|
23
|
+
|
24
|
+
# Initialize object with an optional attributes hash
|
25
|
+
#
|
26
|
+
# @param [Hash] attributes An optional hash of attributes to assign to the new instance
|
27
|
+
def initialize(attributes = nil)
|
28
|
+
if attributes.is_a? Hash
|
29
|
+
attributes.each do |key, value|
|
30
|
+
self.send(key.to_s + "=", value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def endpoint
|
36
|
+
if defined?(@endpoint)
|
37
|
+
@endpoint
|
38
|
+
elsif self.class.endpoint
|
39
|
+
self.class.endpoint
|
40
|
+
elsif superclass != Object && superclass.endpoint
|
41
|
+
superclass.endpoint
|
42
|
+
else
|
43
|
+
@endpoint ||= ''
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class MethodNotImplementedException < Exception; end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
module Providers
|
3
|
+
# This is the implementation for version 2.0 of iContact's API.
|
4
|
+
#
|
5
|
+
# == Example Usage
|
6
|
+
# ActiveESP.configure do |c|
|
7
|
+
# c.provider :icontact
|
8
|
+
# c.credentials :app_id => '1234567890',
|
9
|
+
# :username => 'testuser',
|
10
|
+
# :password => 'password',
|
11
|
+
# :api_version => '2.0'
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# list = ActiveESP::List.new(:id => 123)
|
15
|
+
# subscriber = ActiveESP::Subscriber.new(
|
16
|
+
# :email => 'user@example.com',
|
17
|
+
# :name => 'Brian Morton'
|
18
|
+
# )
|
19
|
+
#
|
20
|
+
# ActiveESP.provider.subscribe(subscriber, list)
|
21
|
+
#
|
22
|
+
class IContact < Base
|
23
|
+
include HTTParty
|
24
|
+
format :plain
|
25
|
+
self.endpoint = 'https://app.icontact.com/icp'
|
26
|
+
|
27
|
+
# Returns or sets the application ID provided by iContact
|
28
|
+
#
|
29
|
+
# @see https://app.icontact.com/icp/core/registerapp/
|
30
|
+
# @return [String]
|
31
|
+
attr_accessor :app_id
|
32
|
+
|
33
|
+
# Returns or sets the username of the account associated with
|
34
|
+
# the application ID.
|
35
|
+
#
|
36
|
+
# @see https://app.icontact.com/icp/core/registerapp/
|
37
|
+
# @see http://developer.icontact.com/documentation/authenticate-requests/
|
38
|
+
# @return [String]
|
39
|
+
attr_accessor :username
|
40
|
+
|
41
|
+
# Returns or sets the password of the username or application ID
|
42
|
+
# provided.
|
43
|
+
#
|
44
|
+
# @see https://app.icontact.com/icp/core/registerapp/
|
45
|
+
# @see http://developer.icontact.com/documentation/authenticate-requests/
|
46
|
+
# @return [String]
|
47
|
+
attr_accessor :password
|
48
|
+
|
49
|
+
# The version of the iContact API that is being accessed.
|
50
|
+
#
|
51
|
+
# Currently only version 2.0 is supported. This is for backporting
|
52
|
+
# functionality for 1.0 or supporting future versions of the API.
|
53
|
+
#
|
54
|
+
# @see http://developer.icontact.com/documentation/authenticate-requests/
|
55
|
+
# @return [String]
|
56
|
+
attr_accessor :api_version
|
57
|
+
|
58
|
+
# Interface implementation
|
59
|
+
|
60
|
+
# Create a contact and optionally subscribe them to a provided list.
|
61
|
+
#
|
62
|
+
# Note: On iContact, a user cannot be subscribed to a list that they have
|
63
|
+
# previously unsubscribed from.
|
64
|
+
#
|
65
|
+
# @see ActiveESP::Providers::Interface#subscribe
|
66
|
+
def subscribe(subscriber, list = nil)
|
67
|
+
response = call(:post, "/a/#{account_id}/c/#{client_folder_id}/contacts", [{:email => subscriber.email, :firstName => subscriber.first_name, :lastName => subscriber.last_name}])
|
68
|
+
subscriber.id = response['contacts'].first['contactId']
|
69
|
+
if list
|
70
|
+
list_response = call(:post, "/a/#{account_id}/c/#{client_folder_id}/subscriptions", [{:listId => list.id, :contactId => subscriber.id, :status => 'normal' }])
|
71
|
+
raise ActiveESP::Providers::CouldNotSubscribeToListException, list_response['warnings'] if list_response['warnings']
|
72
|
+
end
|
73
|
+
subscriber
|
74
|
+
end
|
75
|
+
|
76
|
+
# Unsubscribe a subscriber from the provided list.
|
77
|
+
#
|
78
|
+
# @see ActiveESP::Providers::Interface#unsubscribe
|
79
|
+
def unsubscribe(subscriber, list)
|
80
|
+
response = call(:post, "/a/#{account_id}/c/#{client_folder_id}/subscriptions/#{list.id}_#{subscriber.id}", :status => 'unsubscribed')
|
81
|
+
|
82
|
+
raise ActiveESP::Providers::CouldNotUnsubscribeFromListException, response['warnings'] if response['warnings']
|
83
|
+
raise ActiveESP::Providers::CouldNotUnsubscribeFromListException, response['errors'] if response['errors']
|
84
|
+
end
|
85
|
+
|
86
|
+
# Retrieve an array of lists
|
87
|
+
#
|
88
|
+
# @see ActiveESP::Providers::Interface#lists
|
89
|
+
def lists
|
90
|
+
call(:get, "/a/#{account_id}/c/#{client_folder_id}/lists")
|
91
|
+
end
|
92
|
+
|
93
|
+
# Getting iContact-specific account information
|
94
|
+
|
95
|
+
# Retrieves the iContact account information attached to the credentials
|
96
|
+
# provided.
|
97
|
+
#
|
98
|
+
# @see http://developer.icontact.com/documentation/request-your-accountid-and-clientfolderid/
|
99
|
+
# @return [Array] An array with hashes of the associated account information
|
100
|
+
def account
|
101
|
+
call(:get, '/a')
|
102
|
+
end
|
103
|
+
|
104
|
+
# Retrieves the iContact client folders for the account.
|
105
|
+
#
|
106
|
+
# @see http://developer.icontact.com/documentation/request-your-accountid-and-clientfolderid/
|
107
|
+
# @return [Array] An array with hashes of the client folders
|
108
|
+
def client_folders
|
109
|
+
call(:get, "/a/#{account_id}/c")
|
110
|
+
end
|
111
|
+
|
112
|
+
# Retrieving account information necessary for API calls
|
113
|
+
|
114
|
+
# Retrieves the account ID associated with the credentials in cases where it
|
115
|
+
# is not provided with the credentials.
|
116
|
+
#
|
117
|
+
# Note: You should *always* specify the account ID manually by setting
|
118
|
+
# account_id so that an additional API request doesn't need to be made
|
119
|
+
# to retrieve it.
|
120
|
+
#
|
121
|
+
# @see http://developer.icontact.com/documentation/request-your-accountid-and-clientfolderid/
|
122
|
+
# @return [Array] An array with hashes of the client folders
|
123
|
+
def account_id
|
124
|
+
@account_id ||= account['accounts'].first['accountId']
|
125
|
+
end
|
126
|
+
attr_writer :account_id
|
127
|
+
|
128
|
+
# Retrieves the client folder ID of the first folder returned.
|
129
|
+
#
|
130
|
+
# This ID is required to make API calls. It is recommended that it is
|
131
|
+
# set with the credentials, but if it is not, an attempt will be made
|
132
|
+
# to determine it via an API call.
|
133
|
+
#
|
134
|
+
# Note: You should *always* specify the folder ID manually by setting
|
135
|
+
# client_folder_id so that an additional API request doesn't need to be
|
136
|
+
# made to retrieve it.
|
137
|
+
#
|
138
|
+
# @see http://developer.icontact.com/documentation/request-your-accountid-and-clientfolderid/
|
139
|
+
# @return [Array] An array with hashes of the client folders
|
140
|
+
def client_folder_id
|
141
|
+
@client_folder_id ||= client_folders['clientfolders'].first['clientFolderId']
|
142
|
+
end
|
143
|
+
attr_writer :client_folder_id
|
144
|
+
|
145
|
+
private
|
146
|
+
def call(method, resource, params = {})
|
147
|
+
self.class.base_uri self.endpoint
|
148
|
+
body = params.to_json
|
149
|
+
response = self.class.send(method, resource, :headers => headers, :body => body)
|
150
|
+
|
151
|
+
JSON.parse(response.body)
|
152
|
+
end
|
153
|
+
|
154
|
+
def headers
|
155
|
+
{ 'Accept' => 'application/json',
|
156
|
+
'Content-Type' => 'application/json',
|
157
|
+
'API-Version' => api_version,
|
158
|
+
'API-AppId' => app_id,
|
159
|
+
'API-Username' => username,
|
160
|
+
'API-Password' => password }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
module Providers
|
3
|
+
module Interface
|
4
|
+
# Subscription methods
|
5
|
+
|
6
|
+
def subscribe(subscriber, list = nil); end
|
7
|
+
def unsubscribe(subscriber, list = nil); end
|
8
|
+
def subscribed?(subscriber, list = nil); end
|
9
|
+
|
10
|
+
# List methods
|
11
|
+
|
12
|
+
def lists; end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
module Providers
|
3
|
+
class MailChimp < Base
|
4
|
+
include HTTParty
|
5
|
+
format :plain
|
6
|
+
|
7
|
+
def endpoint
|
8
|
+
"https://#{dc_from_api_key}api.mailchimp.com/1.3"
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(subscriber, list)
|
12
|
+
call(:list_subscribe, { :id => list.id, :email_address => subscriber.email, :merge_vars => { :FNAME => subscriber.first_name, :LNAME => subscriber.last_name }})
|
13
|
+
end
|
14
|
+
|
15
|
+
def lists
|
16
|
+
call(:lists)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def call(method, params = {})
|
21
|
+
api_url = endpoint + '/?method=' + method.to_s.camelize(:lower)
|
22
|
+
params = api_params(params)
|
23
|
+
response = self.class.post(api_url, :body => CGI::escape(params.to_json), :timeout => 30)
|
24
|
+
|
25
|
+
# Some calls (e.g. listSubscribe) return json fragments
|
26
|
+
# (e.g. true) so wrap in an array prior to parsing
|
27
|
+
response = JSON.parse('['+response.body+']').first
|
28
|
+
|
29
|
+
if @throws_exceptions && response.is_a?(Hash) && response["error"]
|
30
|
+
raise "Error from MailChimp API: #{response["error"]} (code #{response["code"]})"
|
31
|
+
end
|
32
|
+
|
33
|
+
response
|
34
|
+
end
|
35
|
+
|
36
|
+
def api_params(additional_params = {})
|
37
|
+
{ :apikey => api_key }.merge(additional_params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dc_from_api_key
|
41
|
+
(!@api_key.present? || @api_key !~ /-/) ? '' : "#{@api_key.split("-").last}."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
module Providers
|
3
|
+
# Thanks to ActiveMerchant for this autoloading code!
|
4
|
+
Dir[File.dirname(__FILE__) + '/providers/*.rb'].each do |f|
|
5
|
+
# Get camelized class name
|
6
|
+
filename = File.basename(f, '.rb')
|
7
|
+
|
8
|
+
# Camelize the string to get the class name
|
9
|
+
gateway_class = filename.camelize.to_sym
|
10
|
+
|
11
|
+
# Register for autoloading
|
12
|
+
autoload gateway_class, f
|
13
|
+
end
|
14
|
+
|
15
|
+
class CouldNotSubscribeToListException < Exception; end
|
16
|
+
class CouldNotUnsubscribeFromListException < Exception; end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
module ActiveESP #:nodoc:
|
4
|
+
# RFC822 Email Address Regex
|
5
|
+
#
|
6
|
+
# Originally written by Cal Henderson
|
7
|
+
# c.f. http://iamcal.com/publish/articles/php/parsing_email/
|
8
|
+
#
|
9
|
+
# Translated to Ruby by Tim Fletcher, with changes suggested by Dan Kubb.
|
10
|
+
#
|
11
|
+
# Licensed under a Creative Commons Attribution-ShareAlike 2.5 License
|
12
|
+
# http://creativecommons.org/licenses/by-sa/2.5/
|
13
|
+
#
|
14
|
+
# Source: http://tfletcher.com/lib/rfc822.rb
|
15
|
+
# RFC822: http://www.w3.org/Protocols/rfc822/#z8
|
16
|
+
module RFC822
|
17
|
+
EmailAddress = begin
|
18
|
+
qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
|
19
|
+
dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]'
|
20
|
+
atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-' +
|
21
|
+
'\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+'
|
22
|
+
quoted_pair = '\\x5c[\\x00-\\x7f]'
|
23
|
+
domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d"
|
24
|
+
quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22"
|
25
|
+
domain_ref = atom
|
26
|
+
sub_domain = "(?:#{domain_ref}|#{domain_literal})"
|
27
|
+
word = "(?:#{atom}|#{quoted_string})"
|
28
|
+
domain = "#{sub_domain}(?:\\x2e#{sub_domain})*"
|
29
|
+
local_part = "#{word}(?:\\x2e#{word})*"
|
30
|
+
addr_spec = "#{local_part}\\x40#{domain}"
|
31
|
+
pattern = /\A#{addr_spec}\z/
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module ActiveESP
|
2
|
+
# A +Subscriber+ object represents an email address stored within a service provider's
|
3
|
+
# system.
|
4
|
+
#
|
5
|
+
# At the very least, an email address is required to have a valid subscriber. If a name
|
6
|
+
# is also necessary, a +requires_name+ flag can be set on the class to indicate to the
|
7
|
+
# validator that it needs to check for a name as well.
|
8
|
+
#
|
9
|
+
# Email address are validated against the spec set forth in RFC822:
|
10
|
+
# http://www.w3.org/Protocols/rfc822/#z8
|
11
|
+
#
|
12
|
+
# == Example Usage
|
13
|
+
# subscriber = ActiveESP::Subscriber.new(
|
14
|
+
# :first_name => 'Billie Joe',
|
15
|
+
# :last_name => 'Armstong',
|
16
|
+
# :email => 'billie.joe@example.com'
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
# subscriber.name # => 'Billie Joe Armstrong'
|
20
|
+
# subscriber.valid? # => true
|
21
|
+
#
|
22
|
+
class Subscriber
|
23
|
+
include RFC822
|
24
|
+
|
25
|
+
# Forces a first and last name to be present for the object to be valid.
|
26
|
+
# If true, the name is required. The default value is false.
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
cattr_accessor :requires_name
|
30
|
+
self.requires_name = false
|
31
|
+
|
32
|
+
# Returns or sets the subscriber's ID as determined by the provider
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
attr_accessor :id
|
36
|
+
|
37
|
+
# Returns or sets the subscriber's email address
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
attr_accessor :email
|
41
|
+
|
42
|
+
# Returns or sets the subscriber's first name
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
attr_accessor :first_name
|
46
|
+
|
47
|
+
# Returns or sets the subscriber's last name
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
attr_accessor :last_name
|
51
|
+
|
52
|
+
# Initialize object with an optional attributes hash
|
53
|
+
#
|
54
|
+
# @param [Hash] attributes An optional hash of attributes to assign to the new instance
|
55
|
+
def initialize(attributes = nil)
|
56
|
+
if attributes.is_a? Hash
|
57
|
+
attributes.each do |key, value|
|
58
|
+
self.send(key.to_s + "=", value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the full name of the subscriber.
|
64
|
+
#
|
65
|
+
# @return [String] the full name of the subscriber
|
66
|
+
def name
|
67
|
+
[@first_name, @last_name].compact.join(' ')
|
68
|
+
end
|
69
|
+
|
70
|
+
# Assigns first and last names based on specifiying a full name string
|
71
|
+
#
|
72
|
+
# @param [String] full_name The subscriber's full name to be split programatically
|
73
|
+
def name=(full_name)
|
74
|
+
names = full_name.split
|
75
|
+
self.last_name = names.pop
|
76
|
+
self.first_name = names.join(" ")
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns whether either the +first_name+ or the +last_name+ attributes has been set.
|
80
|
+
def name?
|
81
|
+
first_name? || last_name?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns whether the +first_name+ attribute has been set.
|
85
|
+
def first_name?
|
86
|
+
@first_name.present?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns whether the +last_name+ attribute has been set.
|
90
|
+
def last_name?
|
91
|
+
@last_name.present?
|
92
|
+
end
|
93
|
+
|
94
|
+
def valid?
|
95
|
+
valid_email? && valid_name?
|
96
|
+
end
|
97
|
+
|
98
|
+
def valid_email?
|
99
|
+
@email =~ EmailAddress
|
100
|
+
end
|
101
|
+
|
102
|
+
def valid_name?
|
103
|
+
return false if self.class.requires_name && !name?
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
|
107
|
+
# Accessing commonly used API calls
|
108
|
+
|
109
|
+
# Add the subscriber to the provider and optionally subscribe them to the
|
110
|
+
# given list.
|
111
|
+
#
|
112
|
+
# @see ActiveESP::Providers::Interface#subscribe
|
113
|
+
def subscribe!(list = nil)
|
114
|
+
raise ActiveESP::ProviderNotConfiguredException unless ActiveESP.provider
|
115
|
+
ActiveESP.provider.subscribe(self, list)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Remove the subscriber from the given list.
|
119
|
+
#
|
120
|
+
# @see ActiveESP::Providers::Interface#unsubscribe
|
121
|
+
def unsubscribe!(list)
|
122
|
+
raise ActiveESP::ProviderNotConfiguredException unless ActiveESP.provider
|
123
|
+
ActiveESP.provider.unsubscribe(self, list)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/active_esp.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "interface"
|
2
|
+
require "httparty"
|
3
|
+
require "json"
|
4
|
+
require "cgi"
|
5
|
+
|
6
|
+
require "active_support/core_ext"
|
7
|
+
require "active_support/inflector"
|
8
|
+
require "active_esp/inflections"
|
9
|
+
require "active_esp/version"
|
10
|
+
require "active_esp/configuration"
|
11
|
+
require "active_esp/rfc822"
|
12
|
+
require "active_esp/subscriber"
|
13
|
+
require "active_esp/list"
|
14
|
+
|
15
|
+
# ActiveESP is an abstraction library for managing subscribers, campaigns, and other email
|
16
|
+
# marketing facilities. It aims to provide a consistent interface to interact with the
|
17
|
+
# numerous ESPs operating with different terminologies and strategies.
|
18
|
+
#
|
19
|
+
# This framework provides some common classes for managing email marketing data structures
|
20
|
+
# as well as the adapters for interfacing with the providers' APIs.
|
21
|
+
module ActiveESP
|
22
|
+
autoload :Providers, "active_esp/providers"
|
23
|
+
|
24
|
+
include Configuration
|
25
|
+
|
26
|
+
class ProviderNotSupportedException < Exception; end
|
27
|
+
class ProviderNotConfiguredException < Exception; end
|
28
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveESP::Subscriber do
|
4
|
+
before(:each) do
|
5
|
+
ActiveESP::Subscriber.requires_name = false
|
6
|
+
@attributes = Factory.attributes_for(:subscriber)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should properly assign attributes" do
|
10
|
+
subscriber = ActiveESP::Subscriber.new(@attributes)
|
11
|
+
subscriber.first_name.should eq(@attributes[:first_name])
|
12
|
+
subscriber.last_name.should eq(@attributes[:last_name])
|
13
|
+
subscriber.email.should eq(@attributes[:email])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should respond with the subscriber's full name" do
|
17
|
+
subscriber = ActiveESP::Subscriber.new(:first_name => 'Kris', :last_name => 'Roe')
|
18
|
+
subscriber.name.should eq('Kris Roe')
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ".first_name?" do
|
22
|
+
it "should return true if a first name has been assigned" do
|
23
|
+
subscriber = ActiveESP::Subscriber.new(:first_name => 'Haushinka')
|
24
|
+
subscriber.first_name?.should be_true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return false if a first name has not been assigned" do
|
28
|
+
subscriber = ActiveESP::Subscriber.new
|
29
|
+
subscriber.first_name?.should be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return false if the first name is blank" do
|
33
|
+
subscriber = ActiveESP::Subscriber.new(:first_name => '')
|
34
|
+
subscriber.first_name?.should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe ".last_name?" do
|
39
|
+
it "should return true if a last name has been assigned" do
|
40
|
+
subscriber = ActiveESP::Subscriber.new(:last_name => 'Haushinka')
|
41
|
+
subscriber.last_name?.should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return false if a last name has not been assigned" do
|
45
|
+
subscriber = ActiveESP::Subscriber.new
|
46
|
+
subscriber.last_name?.should be_false
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return false if the last name is blank" do
|
50
|
+
subscriber = ActiveESP::Subscriber.new(:last_name => '')
|
51
|
+
subscriber.last_name?.should be_false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe ".name?" do
|
56
|
+
it "should return true if a first name has been assigned" do
|
57
|
+
subscriber = ActiveESP::Subscriber.new(:first_name => 'Haushinka')
|
58
|
+
subscriber.name?.should be_true
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return true if a last name has been assigned" do
|
62
|
+
subscriber = ActiveESP::Subscriber.new(:last_name => 'Haushinka')
|
63
|
+
subscriber.name?.should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should return false if neither a first name nor a last name has been assigned" do
|
67
|
+
subscriber = ActiveESP::Subscriber.new
|
68
|
+
subscriber.name?.should be_false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
describe ".name=" do
|
74
|
+
it "should assign the last word of a given full name as the last name" do
|
75
|
+
subscriber = ActiveESP::Subscriber.new
|
76
|
+
subscriber.name = "Some Really Long Name Morton"
|
77
|
+
subscriber.last_name.should eq("Morton")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should assign the all words except the last of a given full name as the first name" do
|
81
|
+
subscriber = ActiveESP::Subscriber.new
|
82
|
+
subscriber.name = "Billie Joe Armstrong"
|
83
|
+
subscriber.first_name.should eq("Billie Joe")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe ".valid_email?" do
|
88
|
+
it "should return true if the assigned email is valid" do
|
89
|
+
subscriber = ActiveESP::Subscriber.new(:email => 'user@example.com')
|
90
|
+
subscriber.should be_valid_email
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return false if the assigned email is not valid" do
|
94
|
+
subscriber = ActiveESP::Subscriber.new(:email => 'user')
|
95
|
+
subscriber.should_not be_valid_email
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ".valid_name?" do
|
100
|
+
it "should return true if the name isn't required" do
|
101
|
+
ActiveESP::Subscriber.requires_name = false
|
102
|
+
subscriber = ActiveESP::Subscriber.new
|
103
|
+
subscriber.valid_name?.should be_true
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should return true if the name is required and the name is present" do
|
107
|
+
ActiveESP::Subscriber.requires_name = true
|
108
|
+
subscriber = ActiveESP::Subscriber.new
|
109
|
+
subscriber.stub(:name?).and_return(true)
|
110
|
+
subscriber.valid_name?.should be_true
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should return false if the name is required and the name isn't present" do
|
114
|
+
ActiveESP::Subscriber.requires_name = true
|
115
|
+
subscriber = ActiveESP::Subscriber.new
|
116
|
+
subscriber.stub(:name?).and_return(false)
|
117
|
+
subscriber.valid_name?.should_not be_true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe ".valid?" do
|
122
|
+
it "should return true if the name and email address are valid" do
|
123
|
+
subscriber = ActiveESP::Subscriber.new
|
124
|
+
subscriber.stub(:valid_name?).and_return(true)
|
125
|
+
subscriber.stub(:valid_email?).and_return(true)
|
126
|
+
subscriber.valid?.should be_true
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should return false if the name isn't valid" do
|
130
|
+
subscriber = ActiveESP::Subscriber.new
|
131
|
+
subscriber.stub(:valid_name?).and_return(false)
|
132
|
+
subscriber.valid?.should_not be_true
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should return false if the email isn't valid" do
|
136
|
+
subscriber = ActiveESP::Subscriber.new
|
137
|
+
subscriber.stub(:valid_email?).and_return(false)
|
138
|
+
subscriber.valid?.should_not be_true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'active_esp'
|
4
|
+
require 'factory_girl'
|
5
|
+
require './spec/factories'
|
6
|
+
|
7
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
# some (optional) config here
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
RSpec::Matchers.define :implement_interface do |expected|
|
2
|
+
match do |object|
|
3
|
+
@unimplemented_methods = object.unimplemented_methods.reject do |interface, methods|
|
4
|
+
interface != expected
|
5
|
+
end
|
6
|
+
|
7
|
+
@unimplemented_methods.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
failure_message_for_should do |actual|
|
11
|
+
"did not implement #{@unimplemented_methods.inspect}"
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_esp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.alpha1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brian Morton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: &70366026630040 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>'
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.4.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70366026630040
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: httparty
|
27
|
+
requirement: &70366026629440 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.8.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70366026629440
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: activesupport
|
38
|
+
requirement: &70366026628960 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.0.0
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70366026628960
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: shuber-interface
|
49
|
+
requirement: &70366026628420 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.4
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70366026628420
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70366026627840 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 2.8.0
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70366026627840
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: factory_girl
|
71
|
+
requirement: &70366026627360 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.5.1
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70366026627360
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: yard
|
82
|
+
requirement: &70366026626880 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ~>
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 0.7.5
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70366026626880
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: thin
|
93
|
+
requirement: &70366026626380 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ~>
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.3.1
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70366026626380
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: redcarpet
|
104
|
+
requirement: &70366026625920 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.1.0
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70366026625920
|
113
|
+
description: Framework and tools for managing email service providers.
|
114
|
+
email:
|
115
|
+
- bmorton@sdreader.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- .rspec
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- active_esp.gemspec
|
127
|
+
- lib/active_esp.rb
|
128
|
+
- lib/active_esp/configuration.rb
|
129
|
+
- lib/active_esp/inflections.rb
|
130
|
+
- lib/active_esp/list.rb
|
131
|
+
- lib/active_esp/providers.rb
|
132
|
+
- lib/active_esp/providers/base.rb
|
133
|
+
- lib/active_esp/providers/icontact.rb
|
134
|
+
- lib/active_esp/providers/interface.rb
|
135
|
+
- lib/active_esp/providers/mail_chimp.rb
|
136
|
+
- lib/active_esp/rfc822.rb
|
137
|
+
- lib/active_esp/subscriber.rb
|
138
|
+
- lib/active_esp/version.rb
|
139
|
+
- spec/factories.rb
|
140
|
+
- spec/models/subscriber_spec.rb
|
141
|
+
- spec/providers/icontact_spec.rb
|
142
|
+
- spec/providers/mail_chimp_spec.rb
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/support/matchers/implement_interface.rb
|
145
|
+
homepage: http://1703india.st/active_esp
|
146
|
+
licenses: []
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ! '>='
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>'
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 1.3.1
|
163
|
+
requirements: []
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 1.8.16
|
166
|
+
signing_key:
|
167
|
+
specification_version: 3
|
168
|
+
summary: ActiveESP is an abstraction library for managing subscribers, campaigns,
|
169
|
+
and other email marketing facilities. It provides a consistent interface to interact
|
170
|
+
with the numerous ESPs.
|
171
|
+
test_files:
|
172
|
+
- spec/factories.rb
|
173
|
+
- spec/models/subscriber_spec.rb
|
174
|
+
- spec/providers/icontact_spec.rb
|
175
|
+
- spec/providers/mail_chimp_spec.rb
|
176
|
+
- spec/spec_helper.rb
|
177
|
+
- spec/support/matchers/implement_interface.rb
|
178
|
+
has_rdoc:
|