malechimp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ spec/config.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in malechimp.gemspec
4
+ gemspec
@@ -0,0 +1,19 @@
1
+ # MaleChimp
2
+
3
+ The MailChimp API leaves a lot to be desired for modern application development. This gem provides very basic functionality to add users to a mail list:
4
+
5
+ ```ruby
6
+ joe = MaleChimp::Subscriber.new do |s|
7
+ s.first_name = 'Joe'
8
+ s.last_name = 'Plumber'
9
+ s.email = 'joe@plumber.com'
10
+ s.fields['last_logged_in_at'] = Time.now
11
+ s.fields['occupation'] = "Plumber"
12
+ end
13
+
14
+ Chimp = MaleChimp.new(ENV['MAIL_CHIMP_API_KEY'])
15
+
16
+ Chimp.list('The Awesome List').subscribe(joe)
17
+ ```
18
+
19
+ Consider this an ugly hack on top of an ugly RPC API, but we run integration tests on this nightly to make sure the API is in working order.
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Copy over configuration files needed to run integration specs"
4
+ task :setup do
5
+ cp 'spec/config.yml.sample', 'spec/config.yml'
6
+ puts "Be sure to add your MailChimp API key to spec/config.yml"
7
+ end
@@ -0,0 +1,54 @@
1
+ require "malechimp/version"
2
+ require 'xmlrpc/client'
3
+
4
+ module MaleChimp
5
+ module API
6
+ VERSION = "1.3".freeze
7
+ URL = "http://api.mailchimp.com/#{API::VERSION}/".freeze
8
+ end
9
+
10
+ autoload :List, 'malechimp/list'
11
+ autoload :Subscriber, 'malechimp/subscriber'
12
+ autoload :Exception, 'malechimp/exception'
13
+ autoload :Utils, 'malechimp/utils'
14
+
15
+ class Base
16
+ attr_reader :url, :api_key
17
+
18
+ # Try to grab the API key out of env and use the default end-point URL
19
+ def initialize(api_key, url=API::URL)
20
+ @api_key, @url = api_key, url
21
+ end
22
+
23
+ # Returns all of the mailing lists in Chimp.
24
+ def lists
25
+ # In MailChimps infinite wisdom, they implemented REST inside of RPC, so you see stupid stuff like the .data root key.
26
+ @lists ||= call('lists')['data'].map do |list|
27
+ List.new(self, list)
28
+ end
29
+ end
30
+
31
+ # Find a list by name.
32
+ def list(name)
33
+ lists.find{ |l| l.name == name }
34
+ end
35
+
36
+ # Zie oh so wunderful API shinannigans
37
+ def server
38
+ @server ||= XMLRPC::Client.new2(url)
39
+ end
40
+
41
+ protected
42
+ # This is a stupid way of trying to make calls to MailChimps RPC api (Really poopy & crappy)
43
+ def call(*args)
44
+ begin
45
+ args = args.insert(1, api_key) # gotta shove the API key in there
46
+ puts "Calling with #{args.map{|arg| "#{arg.inspect}" }.join(', ')}"
47
+ server.call(*args)
48
+ rescue XMLRPC::FaultException => ex
49
+ puts "XMLRPC Fault #{ex.faultCode}: #{ex.faultString}"
50
+ raise Exception.lookup(ex.faultCode) || ex
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ # Error mappings from http://www.mailchimp.com/api/rtfm/exceptions.field.php
2
+ # This is NOT a comprehensive list errors from the API
3
+ module MaleChimp
4
+ module Exception
5
+ # Exception hierarchy for good ol' MaleChimp
6
+ MaleChimpError = Class.new(StandardError)
7
+ AuthorizationError = Class.new(MaleChimpError)
8
+ ListError = Class.new(MaleChimpError)
9
+ ListInvalidInterestFieldType = Class.new(ListError)
10
+ ListInvalidOption = Class.new(ListError)
11
+ ListInvalidUnsubMember = Class.new(ListError)
12
+ ListInvalidBounceMember = Class.new(ListError)
13
+ ListAlreadySubscribed = Class.new(ListError)
14
+ ListNotSubscribed = Class.new(ListError)
15
+ ListInvalidImport = Class.new(ListError)
16
+ EmailError = Class.new(MaleChimpError)
17
+ EmailAlreadySubscribed = Class.new(EmailError)
18
+ EmailAlreadyUnsubscribed = Class.new(EmailError)
19
+ EmailNotExists = Class.new(EmailError)
20
+ EmailNotSubscribed = Class.new(EmailError)
21
+
22
+ # Map our fancy exception hierarchy to MaleChimps XML RPC fault codes
23
+ FAULT_CODE_MAPPING = {
24
+ 210 => ListInvalidInterestFieldType,
25
+ 211 => ListInvalidOption,
26
+ 212 => ListInvalidUnsubMember,
27
+ 213 => ListInvalidBounceMember,
28
+ 214 => ListAlreadySubscribed,
29
+ 215 => ListNotSubscribed,
30
+ 220 => ListInvalidImport,
31
+ 230 => EmailAlreadySubscribed,
32
+ 231 => EmailAlreadyUnsubscribed,
33
+ 232 => EmailNotExists,
34
+ 233 => EmailNotSubscribed
35
+ }
36
+
37
+ def self.lookup(err)
38
+ if err.respond_to?(:faultCode)
39
+ code = err.faultCode.to_i
40
+ else
41
+ code = err.to_i
42
+ end
43
+ FAULT_CODE_MAPPING[code]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,89 @@
1
+ module MaleChimp
2
+ class List
3
+ DEFAULT_MAIL_FORMAT = 'html'
4
+
5
+ attr_accessor :base
6
+ attr_reader :size, :list_id, :name, :created_at
7
+
8
+ # The construct will return
9
+ # [{
10
+ # "name"=>"New Feature Newsletter (APITEST)",
11
+ # "date_created"=>"2008-12-12 03:09:17",
12
+ # "member_count"=>7.0,
13
+ # "unsubscribe_count"=>4.0,
14
+ # "id"=>"5757f43cda",
15
+ # "cleaned_count"=>2.0,
16
+ # "web_id"=>17191
17
+ # }]
18
+
19
+ def initialize(base, opts={})
20
+ @base = base
21
+ @name = opts['name']
22
+ @created_at = opts['created_at'] || opts['date_created']
23
+ @size = opts['member_count'] || opts['size']
24
+ @list_id = opts['id'] || opts['list_id']
25
+ end
26
+
27
+ def subscribe(user, opts={})
28
+ opts = {:email_type => DEFAULT_MAIL_FORMAT, :double_optin => false, :update_existing => false, :replace_interests => true, :send_welcome => false}.merge(opts)
29
+
30
+ email_type = opts[:email_type]
31
+ double_optin = opts[:double_optin]
32
+ update_existing = opts[:update_existing]
33
+ replace_interests = opts[:replace_interests]
34
+ send_welcome = opts[:send_welcome]
35
+
36
+ # listSubscribe(string email_address, array merge_vars, string email_type, boolean double_optin, boolean update_existing, boolean replace_interests)
37
+ call('listSubscribe', user.email, user.merge_fields, DEFAULT_MAIL_FORMAT, double_optin, update_existing, replace_interests, send_welcome)
38
+ end
39
+
40
+ def unsubscribe(user, opts={})
41
+ opts = {:destroy => false, :send_goodbye => false, :send_notify => false}.merge(opts)
42
+
43
+ destroy = opts[:destroy]
44
+ send_goodbye = opts[:send_goodbye]
45
+ send_notify = opts[:send_notify]
46
+
47
+ surpress_faults Exception::ListNotSubscribed, Exception::EmailNotExists do
48
+ call('listUnsubscribe', user.email, destroy, send_goodbye, send_notify)
49
+ end
50
+ end
51
+
52
+ def update(user, opts={})
53
+ opts = {:email_type => DEFAULT_MAIL_FORMAT, :replace_interests => true}.merge(opts)
54
+
55
+ email_type = opts[:email_type]
56
+ replace_interests = opts[:replace_interests]
57
+
58
+ call('listUpdateMember', user.email, user.merge_fields, email_type, replace_interests)
59
+ end
60
+
61
+ def subscriber(email)
62
+ call('listMemberInfo', email)
63
+ end
64
+
65
+ def subscribers
66
+ # listMembers(string apikey, string id, string status, integer since, integer start, integer limit)
67
+ # returns [{"timestamp"=>"2008-12-12 03:24:05", "email"=>"jeff@jeffvyduna.com"} ...]
68
+ call('listMembers').map do |s|
69
+ Subscriber.new(s)
70
+ end
71
+ end
72
+
73
+ protected
74
+ # Curry these puppies some more! Woo yeah! XML RPC IS AWESOME!
75
+ def call(*args)
76
+ args = args.insert(1, @list_id) # Gotta shove the list ID in there
77
+ @base.send(:call, *args)
78
+ end
79
+
80
+ # Run a method and surpress certain acceptable errors.
81
+ def surpress_faults(*faults, &block)
82
+ begin
83
+ yield block
84
+ rescue XMLRPC::FaultException => ex
85
+ faults.include?(Exception.lookup(ex.faultCode)) ? true : raise
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,59 @@
1
+ module MaleChimp
2
+ class Subscriber
3
+ DEFAULT_FIELDS = %w(first_name last_name email id)
4
+ MERGE_FIELD_SIZE = 10 # Another bullshit constraint imposed by MaleChimp
5
+
6
+ attr_accessor *DEFAULT_FIELDS
7
+ attr_accessor :fields
8
+
9
+ # Map these default fields out into the function
10
+ DEFAULT_FIELDS.each do |field|
11
+ eval(%{
12
+ def #{field}
13
+ @fields['#{field}']
14
+ end
15
+
16
+ def #{field}=(val)
17
+ @fields['#{field}'] = val
18
+ end
19
+ })
20
+ end
21
+
22
+ def initialize(fields={})
23
+ # Setup our default fields hash
24
+ @fields = DEFAULT_FIELDS.inject({}) do |h,f|
25
+ h[f] = '' # Default values to be blank
26
+ h # Gotta yield this for inject to work
27
+ end
28
+ @fields.merge!(fields)
29
+ yield self if block_given?
30
+ end
31
+
32
+ # Guess what guys? Chimp wants all upper case field names and
33
+ # no spaces for merge fields. Fine, we'll give 'em that!
34
+ def merge_fields
35
+ fields.inject({}) do |hash, field|
36
+ key, value = encode_merge_field(field)
37
+ hash[key] = value
38
+ hash
39
+ end
40
+ end
41
+
42
+ private
43
+ # Chimp complains if non-ascii chars.
44
+ def encode_merge_field_value(val)
45
+ val.to_s.gsub(/[^\x00-\x7F]/n,'')
46
+ end
47
+
48
+ # Replaces spaces with an underline and truncate
49
+ def encode_merge_field_name(key)
50
+ key.upcase.gsub(/\s/, '_')[0...MERGE_FIELD_SIZE]
51
+ end
52
+
53
+ # Clean up the merge field so that chimp and Ruby RPC don't cry
54
+ def encode_merge_field(field)
55
+ key, val = field
56
+ [ encode_merge_field_name(key), encode_merge_field_value(val) ]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,16 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+
4
+ module MaleChimp
5
+ module TestHelper
6
+ # Load the MailChimp API key
7
+ def self.config
8
+ @config ||= begin
9
+ OpenStruct.new(YAML.load(File.read(File.expand_path('../../../spec/config.yml', __FILE__))))
10
+ rescue Errno::ENOENT
11
+ puts "Oops! You didn't run `rake setup` to configure your integration test settings."
12
+ exit 1
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,45 @@
1
+ # Random utilities and scripts for MaleChimp
2
+ require 'rubygems'
3
+ require 'hpricot'
4
+ require 'open-uri'
5
+ require 'pp'
6
+
7
+ module MaleChimp
8
+ module Utils
9
+ module Exceptions
10
+ EXCEPTIONS_SPEC = 'http://www.mailchimp.com/api/rtfm/exceptions.field.php'
11
+
12
+ class << self
13
+ # Screen scrapes the faults from the Chimp API
14
+ def faults_list(url=EXCEPTIONS_SPEC)
15
+ doc = Hpricot(open(url))
16
+ # change the CSS class on links
17
+ (doc/"table tr").inject({}) do |faults, row|
18
+ code, error = (row/"td").map{ |cell| cell.inner_text }
19
+ if code =~ /[0-9-]+/
20
+ faults[code.to_i] = error
21
+ end
22
+ faults
23
+ end
24
+ end
25
+
26
+ DEFAULT_GROUP = 'General'
27
+
28
+ def group_faults(faults)
29
+ groups = { DEFAULT_GROUP => [] }
30
+ faults.map do |code, message|
31
+ group, error = message.split(/_/, 2)
32
+
33
+ group ||= DEFAULT_GROUP
34
+ groups[group] ||= []
35
+
36
+ groups[group] << [code, error]
37
+ end
38
+ groups
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ include MaleChimp::Utils
@@ -0,0 +1,3 @@
1
+ module MaleChimp
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "malechimp/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "malechimp"
7
+ s.version = MaleChimp::VERSION
8
+ s.authors = ["Brad Gessler"]
9
+ s.email = ["brad@bradgessler.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{A crappy MailChimp API integration for a crappy API}
12
+ s.description = %q{MailChimps XMP RPC API is insanity if you're a modern developer. This provides a more OO approach to dealing with the MCAPI.}
13
+
14
+ s.rubyforge_project = "malechimp"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ end
@@ -0,0 +1,8 @@
1
+ # API key for your MailChimp account.
2
+ api_key: <your api key here>
3
+
4
+ # You have to log-in to MailChimp and create this list.
5
+ list: <my list here>
6
+
7
+ # A junky email address will do.
8
+ email: john.deer@something.com
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe MaleChimp::Subscriber do
4
+ before(:each) do
5
+ @subscriber = MaleChimp::Subscriber.new do |s|
6
+ s.first_name = 'Brad'
7
+ s.last_name = 'Gessler'
8
+ s.email = MaleChimp::TestHelper.config.email
9
+ s.fields['last_logged_in_at'] = Time.now
10
+ s.fields['crap'] = "?hey"
11
+ end
12
+ end
13
+
14
+ it "should have fields" do
15
+ @subscriber.should respond_to(:fields)
16
+ @subscriber.fields.values.should include('Gessler', 'Brad', MaleChimp::TestHelper.config.email)
17
+ end
18
+
19
+ it "should have first_name" do
20
+ @subscriber.should respond_to(:first_name)
21
+ @subscriber.first_name.should eql('Brad')
22
+ end
23
+
24
+ it "should have last_name" do
25
+ @subscriber.should respond_to(:last_name)
26
+ @subscriber.last_name.should eql('Gessler')
27
+ end
28
+
29
+ it "should have email" do
30
+ @subscriber.should respond_to(:email)
31
+ @subscriber.email.should eql(MaleChimp::TestHelper.config.email)
32
+ end
33
+
34
+ it "should have merge_fields" do
35
+ @subscriber.should respond_to(:merge_fields)
36
+ end
37
+
38
+ it "should upcase and strip spaces from merge fields" do
39
+ @subscriber.merge_fields.keys.should include('FIRST_NAME', 'LAST_NAME', 'EMAIL')
40
+ end
41
+
42
+ it "should truncate long merge fields to 10 characters" do
43
+ @subscriber.merge_fields.should include('LAST_LOGGE')
44
+ end
45
+ end
46
+
47
+ describe MaleChimp::List, "initialization" do
48
+ before(:all) do
49
+ @chimp = MaleChimp::Base.new(MaleChimp::TestHelper.config.api_key)
50
+ @subscriber = MaleChimp::Subscriber.new do |s|
51
+ s.first_name = 'Brad'
52
+ s.last_name = 'Gessler'
53
+ s.email = MaleChimp::TestHelper.config.email
54
+ end
55
+ @list = @chimp.list(MaleChimp::TestHelper.config.list)
56
+ end
57
+
58
+ it "should add new user to list" do
59
+ @list.subscribe(@subscriber)
60
+ end
61
+
62
+ it "should update user on list" do
63
+ @subscriber.email = MaleChimp::TestHelper.config.email
64
+ @list.update(@subscriber)
65
+ end
66
+
67
+ it "should remove user from list" do
68
+ @list.unsubscribe(@subscriber)
69
+ end
70
+
71
+ it "should remove user from list silently" do
72
+ @list.subscribe(@subscriber)
73
+ @list.unsubscribe(@subscriber, :send_goodbye => false)
74
+ end
75
+ end
76
+
77
+ describe MaleChimp::List do
78
+ before(:all) do
79
+ @chimp = MaleChimp::Base.new(MaleChimp::TestHelper.config.api_key)
80
+ @hash = {
81
+ "name" => "New Feature Newsletter (APITEST)",
82
+ "date_created" => "2008-12-12 03:09:17",
83
+ "member_count" => 7.0,
84
+ "unsubscribe_count" => 4.0,
85
+ "id" =>"5757f43cda",
86
+ "cleaned_count" => 2.0,
87
+ "web_id" => 17191
88
+ }
89
+ end
90
+
91
+ it "should initialize with arguments from MaleChimp::Base#list" do
92
+ MaleChimp::List.new(@chimp, @hash).should be_instance_of(MaleChimp::List)
93
+ end
94
+
95
+ it "should have created_at" do
96
+ MaleChimp::List.new(@chimp, @hash).created_at.should eql(@hash['date_created'])
97
+ end
98
+
99
+ it "should have size" do
100
+ MaleChimp::List.new(@chimp, @hash).size.should eql(@hash['member_count'])
101
+ end
102
+
103
+ it "should have name" do
104
+ MaleChimp::List.new(@chimp, @hash).name.should eql(@hash['name'])
105
+ end
106
+
107
+ it "should have list_id" do
108
+ MaleChimp::List.new(@chimp, @hash).list_id.should eql(@hash['id'])
109
+ end
110
+ end
111
+
112
+ describe MaleChimp::Base do
113
+ before(:all) do
114
+ @chimp = MaleChimp::Base.new(MaleChimp::TestHelper.config.api_key)
115
+ end
116
+
117
+ it "should have lists" do
118
+ @chimp.should respond_to(:lists)
119
+ @lists = @chimp.lists
120
+ @lists.first.should be_instance_of(MaleChimp::List)
121
+ end
122
+
123
+ it "should have server" do
124
+ @chimp.should respond_to(:server)
125
+ end
126
+ end
@@ -0,0 +1,16 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require 'rubygems'
9
+ require 'malechimp'
10
+ require 'malechimp/test_helper'
11
+
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: malechimp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brad Gessler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70308222140160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70308222140160
25
+ description: MailChimps XMP RPC API is insanity if you're a modern developer. This
26
+ provides a more OO approach to dealing with the MCAPI.
27
+ email:
28
+ - brad@bradgessler.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - .gitignore
34
+ - .rspec
35
+ - Gemfile
36
+ - README.md
37
+ - Rakefile
38
+ - lib/malechimp.rb
39
+ - lib/malechimp/exception.rb
40
+ - lib/malechimp/list.rb
41
+ - lib/malechimp/subscriber.rb
42
+ - lib/malechimp/test_helper.rb
43
+ - lib/malechimp/utils/utils.rb
44
+ - lib/malechimp/version.rb
45
+ - malechimp.gemspec
46
+ - spec/config.yml.sample
47
+ - spec/malechimp_spec.rb
48
+ - spec/spec_helper.rb
49
+ homepage: ''
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project: malechimp
69
+ rubygems_version: 1.8.11
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: A crappy MailChimp API integration for a crappy API
73
+ test_files:
74
+ - spec/config.yml.sample
75
+ - spec/malechimp_spec.rb
76
+ - spec/spec_helper.rb
77
+ has_rdoc: