monkey_wrench 0.1.0

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.
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