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