monkey_wrench 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.mdown +18 -0
  2. data/Rakefile +11 -0
  3. data/lib/monkey_wrench.rb +10 -0
  4. data/lib/monkey_wrench/array.rb +9 -0
  5. data/lib/monkey_wrench/base.rb +67 -0
  6. data/lib/monkey_wrench/campaign.rb +0 -0
  7. data/lib/monkey_wrench/campaign_aim.rb +0 -0
  8. data/lib/monkey_wrench/campaign_stats.rb +0 -0
  9. data/lib/monkey_wrench/config.rb +34 -0
  10. data/lib/monkey_wrench/error.rb +104 -0
  11. data/lib/monkey_wrench/hash.rb +69 -0
  12. data/lib/monkey_wrench/helper.rb +0 -0
  13. data/lib/monkey_wrench/list.rb +309 -0
  14. data/lib/monkey_wrench/member.rb +33 -0
  15. data/lib/monkey_wrench/security.rb +0 -0
  16. data/test/fixtures/api_fail.json +1 -0
  17. data/test/fixtures/campaigns_success.json +24 -0
  18. data/test/fixtures/listBatchSubscribe10_success.json +1 -0
  19. data/test/fixtures/listBatchSubscribe5_success.json +1 -0
  20. data/test/fixtures/listBatchSubscribe_success.json +1 -0
  21. data/test/fixtures/listBatchSubscribe_with_error_success.json +1 -0
  22. data/test/fixtures/listBatchUnsubscribe_success.json +1 -0
  23. data/test/fixtures/listMemberInfo_fail.json +1 -0
  24. data/test/fixtures/listMemberInfo_success.json +1 -0
  25. data/test/fixtures/listMembers_none_success.json +1 -0
  26. data/test/fixtures/listMembers_success.json +8 -0
  27. data/test/fixtures/listSubscribe_success.json +1 -0
  28. data/test/fixtures/listUnsubscribe_success.json +0 -0
  29. data/test/fixtures/listUpdateMember_success.json +1 -0
  30. data/test/fixtures/lists_success.json +14 -0
  31. data/test/monkey_wrench/campaign_aim_test.rb +0 -0
  32. data/test/monkey_wrench/campaign_stats_test.rb +0 -0
  33. data/test/monkey_wrench/campaign_test.rb +0 -0
  34. data/test/monkey_wrench/hash_test.rb +47 -0
  35. data/test/monkey_wrench/helper_test.rb +0 -0
  36. data/test/monkey_wrench/list_test.rb +310 -0
  37. data/test/monkey_wrench/security_test.rb +0 -0
  38. data/test/test_helper.rb +67 -0
  39. metadata +134 -0
data/README.mdown ADDED
@@ -0,0 +1,18 @@
1
+ # MonkeyWrench
2
+ MonkeyWrench is a rubyesque API for interfacing with [Mailchimp](http://www.mailchimp.com) (or show me some love by registering [via my affiliate link](http://eepurl.com/Ge71)).
3
+
4
+ ## Getting Started
5
+
6
+ To get started, you need to first connect to the appropriate datacenter with your API key:
7
+
8
+ MonkeyWrench::Config.new(:datacenter => "us1",
9
+ :apikey => "your-api-key-goes-here")
10
+
11
+ From there you've got a rich API for managing Lists and Members. To subscribe a new user to a list simply do the following:
12
+
13
+ list = MonkeyWrench::List.find_by_name("My Example List")
14
+ list.subscribe("foo@bar.com")
15
+
16
+ ## Further Reading
17
+
18
+ For more information, [check the documentation](http://rdoc.info/projects/rubypond/monkeywrench)
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/*_test.rb'
8
+ test.verbose = false
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,10 @@
1
+ require "httparty"
2
+ base_dir = File.join(File.dirname(__FILE__), "monkey_wrench")
3
+ ["base", "config", "hash", "list", "error", "member"].each do |lib|
4
+ require File.join(base_dir, lib)
5
+ end
6
+
7
+ begin
8
+ OpenStruct.class_eval { undef :id, :type }
9
+ rescue NameError
10
+ end
@@ -0,0 +1,9 @@
1
+ require 'cgi'
2
+ module MonkeyWrench
3
+ module Array
4
+ end
5
+ end
6
+
7
+ class Array
8
+ include MonkeyWrench::Array
9
+ end
@@ -0,0 +1,67 @@
1
+ begin
2
+ require 'yajl'
3
+ rescue LoadError
4
+ require 'json'
5
+ end
6
+ require 'ostruct'
7
+ module MonkeyWrench
8
+ class Base < OpenStruct
9
+ include HTTParty
10
+
11
+ @@apikey = nil
12
+ @@datacenter = nil
13
+
14
+ class << self
15
+ def default_options
16
+ { :output => "json", :apikey=> @@apikey}
17
+ end
18
+
19
+ def base_uri
20
+ "http://#{datacenter}.api.mailchimp.com/1.2/"
21
+ end
22
+
23
+ def get(params)
24
+ response = super(base_uri, :query => params.merge(default_options))
25
+ handle_errors(response.parsed_response)
26
+ end
27
+
28
+ def post(params)
29
+ response = super(base_uri, :query => params.merge(default_options))
30
+ handle_errors(response.parsed_response)
31
+ end
32
+
33
+ def handle_errors(objects)
34
+ return objects unless objects.respond_to?(:has_key?)
35
+
36
+ if objects.has_key?("error")
37
+ objects.replace({ "error" => MonkeyWrench::Error.new(objects['error'], objects['code']) })
38
+ elsif objects.has_key?("errors")
39
+ objects["errors"] = objects["errors"].map do |err|
40
+ message = err.delete('message')
41
+ code = err.delete('code')
42
+ MonkeyWrench::Error.new(message, code, err)
43
+ end
44
+ end
45
+ objects
46
+ end
47
+
48
+ def apikey
49
+ @@apikey
50
+ end
51
+
52
+ def datacenter
53
+ @@datacenter
54
+ end
55
+ end
56
+
57
+ private
58
+ def get(params)
59
+ self.class.get(params)
60
+ end
61
+
62
+ def post(params)
63
+ self.class.post(params)
64
+ end
65
+
66
+ end
67
+ end
File without changes
File without changes
File without changes
@@ -0,0 +1,34 @@
1
+ require 'yaml'
2
+ module MonkeyWrench
3
+ class Config < MonkeyWrench::Base
4
+ # Establishes a connection to the Mailchimp API.
5
+ #
6
+ # Can be called with either a path to a file containing credentials, or
7
+ # a hash of credentials. The formats of each are:
8
+ #
9
+ # File:
10
+ # mailchimp:
11
+ # datacenter: us1 # Or whatever DC you use
12
+ # apikey: your-api-key-goes-here
13
+ # Hash:
14
+ # { :datacenter => "us1", :apikey => "your-api-key-goes-here"}
15
+ #
16
+ # @example
17
+ # MonkeyWrench::Config.new(:datacenter => "us1",
18
+ # :apikey => "your-api-key-goes-here")
19
+ #
20
+ # @param [String, Hash] credentials accepts either a String pointing to a file
21
+ # containing the credentials, or a hash of credentials.
22
+ def initialize(credentials)
23
+ if credentials.is_a?(String)
24
+ config = YAML.load_file(credentials)["mailchimp"]
25
+ config.collect_kv!{|k,v| [k.to_sym, v]}
26
+ else
27
+ config = credentials
28
+ end
29
+ @@apikey = config[:apikey]
30
+ @@datacenter = config[:datacenter]
31
+ super({})
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,104 @@
1
+ module MonkeyWrench
2
+ class Error < StandardError
3
+ attr_reader :code
4
+
5
+ def initialize(message, code, extra_fields = {})
6
+ super(message)
7
+ @code = code
8
+ @extra_fields = extra_fields
9
+ end
10
+
11
+ def type
12
+ types[@code]
13
+ end
14
+
15
+ def method_missing(sym, *args, &block)
16
+ return @extra_fields[sym.to_s] if @extra_fields.has_key?(sym.to_s)
17
+ end
18
+
19
+ def types
20
+ {
21
+ -32601 => 'ServerError_MethodUnknown',
22
+ -32602 => 'ServerError_InvalidParameters',
23
+ -99 => 'Unknown_Exception',
24
+ -98 => 'Request_TimedOut',
25
+ -92 => 'Zend_Uri_Exception',
26
+ -91 => 'PDOException',
27
+ -91 => 'Avesta_Db_Exception',
28
+ -90 => 'XML_RPC2_Exception',
29
+ -90 => 'XML_RPC2_FaultException',
30
+ -50 => 'Too_Many_Connections',
31
+ 0 => 'Parse_Exception',
32
+ 100 => 'User_Unknown',
33
+ 101 => 'User_Disabled',
34
+ 102 => 'User_DoesNotExist',
35
+ 103 => 'User_NotApproved',
36
+ 104 => 'Invalid_ApiKey',
37
+ 105 => 'User_UnderMaintenance',
38
+ 120 => 'User_InvalidAction',
39
+ 121 => 'User_MissingEmail',
40
+ 122 => 'User_CannotSendCampaign',
41
+ 123 => 'User_MissingModuleOutbox',
42
+ 124 => 'User_ModuleAlreadyPurchased',
43
+ 125 => 'User_ModuleNotPurchased',
44
+ 126 => 'User_NotEnoughCredit',
45
+ 127 => 'MC_InvalidPayment',
46
+ 200 => 'List_DoesNotExist',
47
+ 210 => 'List_InvalidInterestFieldType',
48
+ 211 => 'List_InvalidOption',
49
+ 212 => 'List_InvalidUnsubMember',
50
+ 213 => 'List_InvalidBounceMember',
51
+ 214 => 'List_AlreadySubscribed',
52
+ 215 => 'List_NotSubscribed',
53
+ 220 => 'List_InvalidImport',
54
+ 221 => 'MC_PastedList_Duplicate',
55
+ 222 => 'MC_PastedList_InvalidImport',
56
+ 230 => 'Email_AlreadySubscribed',
57
+ 231 => 'Email_AlreadyUnsubscribed',
58
+ 232 => 'Email_NotExists',
59
+ 233 => 'Email_NotSubscribed',
60
+ 250 => 'List_MergeFieldRequired',
61
+ 251 => 'List_CannotRemoveEmailMerge',
62
+ 252 => 'List_Merge_InvalidMergeID',
63
+ 253 => 'List_TooManyMergeFields',
64
+ 254 => 'List_InvalidMergeField',
65
+ 270 => 'List_InvalidInterestGroup',
66
+ 271 => 'List_TooManyInterestGroups',
67
+ 300 => 'Campaign_DoesNotExist',
68
+ 301 => 'Campaign_StatsNotAvailable',
69
+ 310 => 'Campaign_InvalidAbsplit',
70
+ 311 => 'Campaign_InvalidContent',
71
+ 312 => 'Campaign_InvalidOption',
72
+ 313 => 'Campaign_InvalidStatus',
73
+ 314 => 'Campaign_NotSaved',
74
+ 315 => 'Campaign_InvalidSegment',
75
+ 316 => 'Campaign_InvalidRss',
76
+ 317 => 'Campaign_InvalidAuto',
77
+ 318 => 'MC_ContentImport_InvalidArchive',
78
+ 330 => 'Invalid_EcommOrder',
79
+ 350 => 'Absplit_UnknownError',
80
+ 351 => 'Absplit_UnknownSplitTest',
81
+ 352 => 'Absplit_UnknownTestType',
82
+ 353 => 'Absplit_UnknownWaitUnit',
83
+ 354 => 'Absplit_UnknownWinnerType',
84
+ 355 => 'Absplit_WinnerNotSelected',
85
+ 500 => 'Invalid_Analytics',
86
+ 501 => 'Invalid_DateTime',
87
+ 502 => 'Invalid_Email',
88
+ 503 => 'Invalid_SendType',
89
+ 504 => 'Invalid_Template',
90
+ 505 => 'Invalid_TrackingOptions',
91
+ 506 => 'Invalid_Options',
92
+ 507 => 'Invalid_Folder',
93
+ 508 => 'Invalid_URL',
94
+ 550 => 'Module_Unknown',
95
+ 551 => 'MonthlyPlan_Unknown',
96
+ 552 => 'Order_TypeUnknown',
97
+ 553 => 'Invalid_PagingLimit',
98
+ 554 => 'Invalid_PagingStart'
99
+ }
100
+ end
101
+ end
102
+ end
103
+
104
+
@@ -0,0 +1,69 @@
1
+ require 'cgi'
2
+ module MonkeyWrench
3
+ module Hash
4
+
5
+ # Takes a block that returns a [key, value] pair
6
+ # and builds a new hash based on those pairs
7
+ # Courtesy of http://snuxoll.com/post/2009/02/13/ruby-better-hashcollect
8
+ def collect_kv
9
+ result = {}
10
+ each do |k,v|
11
+ new_k, new_v = yield k, v
12
+ result[new_k] = new_v
13
+ end
14
+ result
15
+ end
16
+
17
+ def collect_kv!(&blk)
18
+ replace(self.collect_kv(&blk))
19
+ end
20
+
21
+ def escape_keys!
22
+ collect_kv!{|k,v| [CGI.escape(k.to_s), v]}
23
+ end
24
+
25
+ def to_mailchimp(index = nil, parent_name = nil)
26
+ result = self.collect_kv do |k,v|
27
+ if v.is_a?(Array) && v.first.is_a?(Hash)
28
+ i = 0
29
+ v = v.inject({}) do |acc,hash|
30
+ acc.merge!(hash.to_mailchimp(i, k))
31
+ i += 1
32
+ acc
33
+ end
34
+ elsif v.is_a?(Hash)
35
+ if parent_name
36
+ v = v.collect_kv do |key,val|
37
+ keyname = CGI.escape("#{parent_name.to_s}[#{key.to_s.upcase}]")
38
+ [keyname, val]
39
+ end
40
+ else
41
+ v = { k => v }.to_mailchimp(nil, k)
42
+ end
43
+ elsif v.is_a?(Array)
44
+ results = {}
45
+ i = 0
46
+ v.each do |val|
47
+ keyname = CGI.escape("#{k}[#{i}]")
48
+ results[keyname] = val
49
+ i += 1
50
+ end
51
+ v = results
52
+ end
53
+ k = k.to_s
54
+ k = "[#{index}][#{k.upcase}]" if index
55
+ k = [parent_name, k].join if k != parent_name.to_s
56
+ [CGI.escape(k), v]
57
+ end
58
+ if result.detect{|k,v| v.is_a?(Hash)}
59
+ result.values.first
60
+ else
61
+ result
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ class Hash
68
+ include MonkeyWrench::Hash
69
+ end
File without changes
@@ -0,0 +1,309 @@
1
+ require 'cgi'
2
+
3
+ module MonkeyWrench
4
+ class List < MonkeyWrench::Base
5
+
6
+ # Finds a given list by name
7
+ #
8
+ # @example
9
+ # MonkeyWrench::List.find_by_name("My Example List")
10
+ #
11
+ # @param [String] list_name the list name
12
+ # @return [MonkeyWrench::List] the first list found with a matching name
13
+ def self.find_by_name(list_name)
14
+ lists = find_all.detect{|list| list.name == list_name}
15
+ end
16
+
17
+ # Will compare another list against the current one and return true if
18
+ # they are the same (based on list ID)
19
+ #
20
+ # @example
21
+ # list1 = MonkeyWrench::List.find("0a649eafc3")
22
+ # list2 = MonkeyWrench::List.find("9f9d54a0c4")
23
+ # list3 = MonkeyWrench::List.find("0a649eafc3") # Same as list1!!
24
+ # list1 == list2 # false
25
+ # list1 == list3 # true
26
+ # @param [MonkeyWrench::List] other_list Other list to compare against
27
+ # @return [Boolean]
28
+ def ==(other_list)
29
+ other_list.is_a?(self.class) && self.id == other_list.id
30
+ end
31
+
32
+ # Finds a given list by ID
33
+ #
34
+ # @example
35
+ # MonkeyWrench::List.find("0a649eafc3")
36
+ #
37
+ # @param [String] id the unique Mailchimp list ID
38
+ # @return [MonkeyWrench::List] the list
39
+ def self.find(id)
40
+ new(:id => id)
41
+ end
42
+
43
+ # Finds all lists
44
+ #
45
+ # @example
46
+ # MonkeyWrench::List.find_all
47
+ #
48
+ # @return [Array<MonkeyWrench::List>]
49
+ def self.find_all
50
+ lists = post({ :method => "lists" }).map do |list|
51
+ List.new(list)
52
+ end
53
+ end
54
+ class << self
55
+ alias :all :find_all
56
+ end
57
+
58
+ # Returns all members for this list
59
+ #
60
+ # @example Find all members that have unsubscribed in the last 24 hours:
61
+ # MonkeyWrench.members(:status => "unsubscribed",
62
+ # :since => Time.now - 86400)
63
+ #
64
+ # @param [Hash] options additional option to include when searching.
65
+ # @option options [String] :status ('subscribed') Filter the list members by status. Can be one of the following: "subscribed", "unsubscribed", "cleaned", "updated".
66
+ # @option options [DateTime] :since Return all members whose status has changed or whose profile has changed since this date/time (in GMT).
67
+ # @option options [Integer] :start (0) For large datasets, the page number to start at.
68
+ # @option options [Integer] :limit (100) For large datasets, the number of results to return. Upper limit is set at 15000.
69
+ # @option options [Boolean] :full_details (true) Return full member details and not just email address and timestamp.
70
+ # @return [Array<MonkeyWrench::Member>]
71
+ def members(options = {})
72
+ if options[:since]
73
+ options[:since] = options[:since].strftime("%Y-%m-%d %H:%M:%S")
74
+ end
75
+ options.merge!(:id => self.id, :method => "listMembers")
76
+ response = post(options)
77
+ if options[:full_details]
78
+ response.map do |response_user|
79
+ member(response_user["email"])
80
+ end
81
+ else
82
+ response.map do |response_user|
83
+ MonkeyWrench::Member.new(response_user)
84
+ end
85
+ end
86
+ end
87
+
88
+ # Enumerates over each member and executes the provided block. Will
89
+ # automatically page and batch requests for members.
90
+ #
91
+ # @example
92
+ # list = MonkeyWrench::List.find("0a649eafc3")
93
+ # emails = []
94
+ # list.each_member do |member|
95
+ # emails << member.email
96
+ # end
97
+ #
98
+ # @param [Proc] &block code to execute for each member
99
+ def each_member(&block)
100
+ page = 0
101
+ loop do
102
+ batch = members(:start => page, :limit => 15000)
103
+ break if batch.empty?
104
+ batch.each do |member|
105
+ yield member
106
+ end
107
+ page += 1
108
+ end
109
+ end
110
+
111
+ # Updates details of list members
112
+ #
113
+ # @example Update a single member's email address
114
+ # list = MonkeyWrench::List.find("0a649eafc3")
115
+ # member = {:email => "foo@bar.com", :new_email => "bar@foo.com"}
116
+ # list.update_members(member)
117
+ #
118
+ # @example Update multiple members' email addresses
119
+ # list = MonkeyWrench::List.find("0a649eafc3")
120
+ # members = [{:email => "foo@bar.com", :new_email => "bar@foo.com"},
121
+ # {:email => "bob@bar.com", :new_email => "bob@foo.com"}]
122
+ # list.update_members(members)
123
+ #
124
+ # @param [Hash, Array<Hash>] members details of member(s) to update details
125
+ # of. Members are matched based on the value of :email, to update the
126
+ # email address assign the new address to :new_email. All other field
127
+ # names are lowercase symbols representing the MERGEVAR name in
128
+ # Mailchimp (e.g., FNAME is :fname)
129
+ # @param [Hash] options additional options when updating members.
130
+ # @option options [String] :email_type Change the email type preference for the member ('html', 'text', or 'mobile').
131
+ # @option options [Boolean] :replace_interests (true) replace the interest groups provided (will append interest groups to existing values when false).
132
+ def update_members(members, options = {})
133
+ members = members.is_a?(Array) ? members : [members]
134
+ options.merge!(:id => self.id, :method => "listUpdateMember")
135
+ members.each do |member|
136
+ mailchimp_args = {:email_address => member[:email]}
137
+ member[:email] = member[:new_email]
138
+ member.delete(:new_email)
139
+ mailchimp_args.merge!({ :merge_vars => member }.to_mailchimp)
140
+ post(options.merge(mailchimp_args))
141
+ end
142
+ end
143
+
144
+ # Find a member in this list with the given email address
145
+ #
146
+ # @example
147
+ # list = MonkeyWrench::List.find("0a649eafc3")
148
+ # list.member("glenn@rubypond.com")
149
+ #
150
+ # @param [String] email members email address
151
+ # @return [MonkeyWrench::Member]
152
+ def member(email)
153
+ response = post(:id => self.id, :method => "listMemberInfo", :email_address => email)
154
+ if response['error']
155
+ raise response['error']
156
+ else
157
+ MonkeyWrench::Member.new(response)
158
+ end
159
+ end
160
+
161
+ # Subscribes a new member to the list
162
+ #
163
+ # @example Subscribe a new email address
164
+ # list.subscribe("foo@bar.com")
165
+ #
166
+ # @example Subscribe a new member with extended details
167
+ # list.subscribe({:email => "foo@bar.com", :type => :html})
168
+ #
169
+ # @example Subscribe multiple new members
170
+ # subscribers = [{:email => "foo@bar.com", :type => :html},
171
+ # {:email => "bar@foo.com", :type => :html}]
172
+ # list.subscribe(subscribe, :send_welcome => true, :double_optin => false)
173
+ #
174
+ # @param [String, Hash, Array<Hash>] contact_details the email address or hash of values for the new member
175
+ # @param [Hash] opts options when adding new member
176
+ # @option opts [Boolean] :send_welcome (false) if :double_optin if false and this is
177
+ # true, send the lists 'Welcome Email' to the member(s). Will not send email if
178
+ # updating an existing member.
179
+ # @option opts [Boolean] :double_optin (true) send an opt-in confirmation email
180
+ # @option opts [Boolean] :update_existing (false) update members that are already subscribed to the list or to return an error (false returns error)
181
+ # @option opts [Boolean] :replace_interests (true) replace interest groups or append to existing interest groups (false appends to groups)
182
+ def subscribe(contact_details, opts = {})
183
+ if contact_details.is_a?(Array)
184
+ return subscribe_many(contact_details, opts)
185
+ else
186
+ if contact_details.is_a?(Hash)
187
+ email_address = contact_details.delete(:email)
188
+ opts = opts.merge(contact_details)
189
+ else
190
+ email_address = contact_details
191
+ end
192
+ subscribe_one(email_address, opts)
193
+ return { :success => 1, :errors => []}
194
+ end
195
+ end
196
+
197
+ # Unsubscribes a person (or list of people) from the list
198
+ #
199
+ # @example Unsubscribe a single user
200
+ # list = MonkeyWrench::List.find("0a649eafc3")
201
+ # list.unsubscribe("glenn@rubypond.com", :send_goodbye => true) # Unsubscribe a single person
202
+ #
203
+ # @example Unsubscribe a list of users
204
+ # emails = ["glenn@rubypond.com", "me@glenngillen.com"]
205
+ # list.unsubscribe(emails, :send_goodbye => true) # Unsubscribe multiple people at once
206
+ #
207
+ # @param [String, Array<String>] email address(es) of people to unsubscribe.
208
+ # @param [Hash] opts additional option to include when unsubscribing.
209
+ # @option opts [Boolean] :delete_member (false) completely delete the member from your list instead of just unsubscribing.
210
+ # @option opts [Boolean] :send_goodbye (true) send the goodbye email to the email addresses.
211
+ # @option opts [Boolean] :send_notify (false) send the unsubscribe notification email to the address defined in the list email notification settings.
212
+ # @return [Hash] contains 2 keys. :success contains the number of successful actions, :error a list of all errors.
213
+ def unsubscribe(emails, opts = {})
214
+ emails = [*emails]
215
+ params = { :method => "listBatchUnsubscribe",
216
+ :id => self.id }
217
+ params[:delete_member] = opts[:delete_member] if opts.has_key?(:delete_member)
218
+ params[:send_goodbye] = opts[:send_goodbye] if opts.has_key?(:send_goodbye)
219
+ params[:send_notify] = opts[:send_notify] if opts.has_key?(:send_notify)
220
+ params.merge!({ :emails => emails }.to_mailchimp)
221
+ response = post(params)
222
+ return { :success => response["success_count"],
223
+ :errors => response["errors"] }
224
+ end
225
+
226
+ # Will flag the email(s) as opted-out for all future mailing for this list
227
+ #
228
+ # @example Opt-out a single user
229
+ # list = MonkeyWrench::List.find("0a649eafc3")
230
+ # list.opt_out("glenn@rubypond.com") # Opt-out a single person
231
+ #
232
+ # @example Opt-out a list of users
233
+ # emails = ["glenn@rubypond.com", "me@glenngillen.com"]
234
+ # list.opt_out(emails) # Opt-out multiple people at once
235
+ #
236
+ # @param [String, Array<String>] email address(es) of people to opt-out.
237
+ # @return [Hash] contains 2 keys. :success contains the number of successful actions, :error a list of all errors.
238
+ def opt_out(emails)
239
+ emails = [*emails]
240
+ subscribe(emails.map{|email| { :email => email }})
241
+ unsubscribe(emails, :send_goodbye => false, :send_notify => false)
242
+ end
243
+
244
+ private
245
+ def self.reserved_keys
246
+ [:email, :type, :double_optin, :update_existing, :replace_interests,
247
+ :send_welcome, :emails, :send_notify, :send_goodbye, :delete_member]
248
+ end
249
+
250
+ def subscribe_many(subscribers, opts = {})
251
+ if opts[:send_welcome]
252
+ subscribe_one_at_a_time(subscribers, opts)
253
+ else
254
+ subscribe_in_batches(subscribers, opts)
255
+ end
256
+ end
257
+
258
+ def subscribe_in_batches(subscribers, opts)
259
+ cumulative_response = { :success => 0, :errors => [] }
260
+ i = 0
261
+ while i < subscribers.size
262
+ response = subscribe_one_batch(subscribers[i..i+9], opts)
263
+ cumulative_response[:success] += response['success_count']
264
+ cumulative_response[:errors] += response['errors']
265
+ i += 10
266
+ end
267
+ cumulative_response
268
+ end
269
+
270
+ def subscribe_one_batch(subscribers, opts)
271
+ params = { :id => self.id, :method => "listBatchSubscribe" }
272
+ params[:double_optin] = opts[:double_optin] if opts.has_key?(:double_optin)
273
+ params[:update_existing] = opts[:update_existing] if opts.has_key?(:update_existing)
274
+ params[:replace_interests] = opts[:replace_interests] if opts.has_key?(:replace_interests)
275
+ params.merge!({ :batch => subscribers }.to_mailchimp)
276
+ post(params)
277
+ end
278
+
279
+ def subscribe_one_at_a_time(subscribers, opts)
280
+ cumulative_response = { :success => 0, :errors => [] }
281
+ subscribers.each do |subscriber|
282
+ params = opts.merge(subscriber)
283
+ params.delete(:email)
284
+ if subscribe_one(subscriber[:email], params) == true
285
+ cumulative_response[:success] += 1
286
+ else
287
+ cumulative_response[:errors] << response["error"]
288
+ end
289
+ end
290
+ cumulative_response
291
+ end
292
+
293
+ def subscribe_one(email_address, opts = {})
294
+ params = {
295
+ :type => opts.delete(:type),
296
+ :double_optin => opts.delete(:double_optin),
297
+ :update_existing => opts.delete(:update_existing),
298
+ :replace_interests => opts.delete(:replace_interests),
299
+ :send_welcome => opts.delete(:send_welcome),
300
+ :email => email_address
301
+ }
302
+ params.reject!{ |k,v| v.nil? }
303
+ merge_vars = { :merge_vars => opts }.to_mailchimp
304
+ params.merge!(merge_vars)
305
+ params.merge!(:method => "listSubscribe", :id => self.id)
306
+ post(params)
307
+ end
308
+ end
309
+ end