wuparty 1.0.2

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 (4) hide show
  1. data/README.rdoc +92 -0
  2. data/lib/wuparty.rb +339 -0
  3. data/test/wuparty_test.rb +100 -0
  4. metadata +115 -0
data/README.rdoc ADDED
@@ -0,0 +1,92 @@
1
+ = WuParty
2
+
3
+ WuParty is a fairly lightweight wrapper for Wufoo's REST API[http://wufoo.com/docs/api/v3]
4
+ using HTTParty[http://httparty.rubyforge.org].
5
+
6
+ == API Support
7
+
8
+ Wufoo's REST API supports viewing and submitting entries, filtering entries,
9
+ viewing reports, and viewing users.
10
+
11
+ This library supports all methods of Wufoo's API version 3.0.
12
+
13
+ There is an older version of this library (version 0.1.x) that only supports version 2 of Wufoo's API.
14
+ Make sure you have latest version installed (1.0.0 or higher).
15
+
16
+ == Source & Download
17
+
18
+ * {Homepage & Demo}[http://wuparty.71m.us]
19
+ * {Source Code}[http://github.com/seven1m/wuparty]
20
+ * {RDoc Documentation}[http://seven1m.github.com/wuparty]
21
+
22
+ == Installation
23
+
24
+ gem install wuparty
25
+
26
+ == Usage
27
+
28
+ require 'wuparty'
29
+
30
+ ACCOUNT = 'accountname'
31
+ API_KEY = 'AAAA-BBBB-CCCC-DDDD'
32
+ FORM_ID = 'my-form-id'
33
+
34
+ wufoo = WuParty.new(ACCOUNT, API_KEY)
35
+
36
+ wufoo.forms # list all accessible forms and details
37
+ form = wufoo.form(FORM_ID) # or details for a specific form
38
+
39
+ # query all entries
40
+ form.entries
41
+
42
+ # ...or filter entries (see http://wufoo.com/docs/api/v3/entries/get/#filter)
43
+ form.entries([['Field1', 'Is_equal_to', 'Tim']])
44
+
45
+ # To submit, use field numbers shown on the 'API Information' page in Wufoo
46
+ result = wufoo.submit(
47
+ 'Field1' => 'Tim',
48
+ 'Field2' => 'Morgan'
49
+ })
50
+ if result['Success'] == 0
51
+ puts result['ErrorText']
52
+ end
53
+
54
+ == Feedback
55
+
56
+ I’d love to hear from you if you have suggestions for improvement, bug fixes, or whatever.
57
+ Email me at mailto:tim@timmorgan.org or fork the project[http://github.com/seven1m/wuparty] and send a pull request.
58
+
59
+ http://timmorgan.org
60
+
61
+ == Tests
62
+
63
+ To run the tests, you must create a form called "Test Form" and pass in its id via the
64
+ ENV variable WUFOO_FORM_ID. Give the form a standard name and address field.
65
+ Make the name field required. Then run:
66
+
67
+ WUFOO_ACCOUNT=accountname \
68
+ WUFOO_API_KEY=AAAA-BBBB-CCCC-DDDD \
69
+ WUFOO_FORM_ID=test-form \
70
+ ruby -rrubygems test/wuparty_test.rb
71
+
72
+ == License
73
+
74
+ Copyright (c) 2010 Tim Morgan
75
+
76
+ Permission is hereby granted, free of charge, to any person obtaining a copy
77
+ of this software and associated documentation files (the "Software"), to deal
78
+ in the Software without restriction, including without limitation the rights
79
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
80
+ copies of the Software, and to permit persons to whom the Software is
81
+ furnished to do so, subject to the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be included in
84
+ all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
88
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
92
+ THE SOFTWARE.
data/lib/wuparty.rb ADDED
@@ -0,0 +1,339 @@
1
+ require 'httparty'
2
+ gem 'multipart-post'
3
+ require 'net/http/post/multipart'
4
+ gem 'mime-types'
5
+ require 'mime/types'
6
+
7
+ # multipart POST support from David Balater's fork of HTTParty:
8
+ # http://github.com/dbalatero/httparty
9
+ # :stopdoc:
10
+ module HTTParty
11
+ module ClassMethods
12
+ def post(path, options={})
13
+ klass = options[:multipart] ? Net::HTTP::Post::Multipart : Net::HTTP::Post
14
+ perform_request klass, path, options
15
+ end
16
+ end
17
+ class Request
18
+ SupportedHTTPMethods << Net::HTTP::Post::Multipart
19
+ private
20
+ def setup_raw_request
21
+ if multipart?
22
+ @file_handles = []
23
+ io_objects = {}
24
+
25
+ options[:multipart].each do |field_name, info|
26
+ fp = File.open(info[:path])
27
+ @file_handles << fp
28
+
29
+ io_objects[field_name] = UploadIO.new(fp,
30
+ info[:type],
31
+ info[:path])
32
+ end
33
+
34
+ if options[:body]
35
+ options[:body].each do |field_name, value|
36
+ io_objects[field_name] = value
37
+ end
38
+ end
39
+
40
+ @raw_request = http_method.new(uri.request_uri,
41
+ io_objects)
42
+
43
+ # We have to duplicate and merge the headers set by the
44
+ # multipart object to make sure that Net::HTTP
45
+ # doesn't override them down the line when it calls
46
+ # initialize_http_header.
47
+ #
48
+ # Otherwise important headers like Content-Length,
49
+ # Accept, and Content-Type will be deleted.
50
+ original_headers = {}
51
+ @raw_request.each do |key, value|
52
+ original_headers[key] = value
53
+ end
54
+
55
+ options[:headers] ||= {}
56
+ original_headers.merge!(options[:headers])
57
+ options[:headers] = original_headers
58
+ else
59
+ @raw_request = http_method.new(uri.request_uri)
60
+ @raw_request.body = body if body
61
+ end
62
+
63
+ @raw_request.initialize_http_header options[:headers]
64
+ @raw_request.basic_auth(username, password) if options[:basic_auth]
65
+ end
66
+ def multipart?
67
+ Net::HTTP::Post::Multipart == http_method
68
+ end
69
+ end
70
+ end
71
+ # :startdoc:
72
+
73
+ class WuParty
74
+ include HTTParty
75
+ format :json
76
+
77
+ VERSION = '1.0.2'
78
+
79
+ # Represents a general error connecting to the Wufoo service
80
+ class ConnectionError < RuntimeError; end
81
+
82
+ # Represents a specific error returned from Wufoo.
83
+ class HTTPError < RuntimeError
84
+ def initialize(code, message) # :nodoc:
85
+ @code = code
86
+ super(message)
87
+ end
88
+
89
+ # Error code
90
+ attr_reader :code
91
+ end
92
+
93
+ ENDPOINT = 'https://%s.wufoo.com/api/v3'
94
+ API_VERSION = '3.0'
95
+
96
+ # Create a new WuParty object
97
+ def initialize(account, api_key)
98
+ @account = account
99
+ @api_key = api_key
100
+ @field_numbers = {}
101
+ end
102
+
103
+ # Returns list of forms and details accessible by the user account.
104
+ def forms
105
+ get(:forms)['Forms'].map do |details|
106
+ Form.new(details['Url'], :party => self, :details => details)
107
+ end
108
+ end
109
+
110
+ # Returns list of reports and details accessible by the user account.
111
+ def reports
112
+ get(:reports)['Reports'].map do |details|
113
+ Report.new(details['Url'], :party => self, :details => details)
114
+ end
115
+ end
116
+
117
+ # Returns list of users and details.
118
+ def users
119
+ get(:users)['Users'].map do |details|
120
+ User.new(details['Url'], :party => self, :details => details)
121
+ end
122
+ end
123
+
124
+ # Returns details about the specified form.
125
+ def form(form_id)
126
+ if f = get("forms/#{form_id}")['Forms']
127
+ Form.new(f.first['Url'], :party => self, :details => f.first)
128
+ end
129
+ end
130
+
131
+ # Returns details about the specified report.
132
+ def report(report_id)
133
+ if r = get("reports/#{report_id}")['Reports']
134
+ Form.new(r.first['Url'], :party => self, :details => r.first)
135
+ end
136
+ end
137
+
138
+ def get(method, options={}) # :nodoc:
139
+ options.merge!(:basic_auth => {:username => @api_key})
140
+ url = "#{base_url}/#{method}.json"
141
+ result = self.class.get(url, options)
142
+ if result.is_a?(String)
143
+ raise ConnectionError, result
144
+ elsif result['HTTPCode']
145
+ raise HTTPError.new(result['HTTPCode'], result['Text'])
146
+ else
147
+ result
148
+ end
149
+ end
150
+
151
+ def post(method, options={}) # :nodoc:
152
+ options.merge!(:basic_auth => {:username => @api_key})
153
+ url = "#{base_url}/#{method}.json"
154
+ result = self.class.post(url, options)
155
+ if result.is_a?(String)
156
+ raise ConnectionError, result
157
+ elsif result['HTTPCode']
158
+ raise HTTPError.new(result['HTTPCode'], result['Text'])
159
+ else
160
+ result
161
+ end
162
+ end
163
+
164
+ private
165
+
166
+ def base_url
167
+ ENDPOINT % @account
168
+ end
169
+
170
+ public
171
+
172
+ # ----------
173
+
174
+ class Entity # :nodoc:
175
+ include HTTParty
176
+ format :json
177
+
178
+ def initialize(id, options)
179
+ @id = id
180
+ if options[:party]
181
+ @party = options[:party]
182
+ elsif options[:account] and options[:api_key]
183
+ @party = WuParty.new(options[:account], options[:api_key])
184
+ else
185
+ raise WuParty::InitializationException, "You must either specify a :party object or pass the :account and :api_key options. Please see the README."
186
+ end
187
+ @details = options[:details]
188
+ end
189
+
190
+ attr_accessor :details
191
+ end
192
+
193
+ # Wraps an individual Wufoo Form.
194
+ # == Instantiation
195
+ # There are two ways to instantiate a Form object:
196
+ # 1. Via the parent WuParty object that represents the account.
197
+ # 2. Via the WuParty::Form class directly.
198
+ # wufoo = WuParty.new(ACCOUNT, API_KEY)
199
+ # form = wufoo.form(FORM_ID)
200
+ # # or...
201
+ # form = WuParty::Form.new(FORM_ID, :account => ACCOUNT, :api_key => API_KEY)
202
+ # The first technique makes a call to the Wufoo API to get the form details,
203
+ # while the second technique lazily loads the form details, once something is accessed via [].
204
+ # == \Form Details
205
+ # Access form details like it is a Hash, e.g.:
206
+ # form['Name']
207
+ # form['RedirectMessage']
208
+ class Form < Entity
209
+ # Returns field details for the form.
210
+ def fields
211
+ @party.get("forms/#{@id}/fields")['Fields']
212
+ end
213
+
214
+ # Access form details.
215
+ def [](id)
216
+ @details ||= @party.form(@id)
217
+ @details[id]
218
+ end
219
+
220
+ # Returns fields and subfields, as a flattened array, e.g.
221
+ # [{'ID' => 'Field1', 'Title' => 'Name - First', 'Type' => 'shortname', 'Required' => true }, # (subfield)
222
+ # {'ID' => 'Field2', 'Title' => 'Name - Last', 'Type' => 'shortname', 'Required' => true }, # (subfield)
223
+ # {'ID' => 'Field3', 'Title' => 'Birthday', 'Type' => 'date', 'Required' => flase}] # (field)
224
+ # By default, only fields that can be submitted are returned. Pass *true* as the first arg to return all fields.
225
+ def flattened_fields(all=false)
226
+ flattened = []
227
+ fields.each do |field|
228
+ next unless all or field['ID'] =~ /^Field/
229
+ if field['SubFields']
230
+ field['SubFields'].each do |sub_field|
231
+ flattened << {'ID' => sub_field['ID'], 'Title' => field['Title'] + ' - ' + sub_field['Label'], 'Type' => field['Type'], 'Required' => field['IsRequired'] == '1'}
232
+ end
233
+ else
234
+ flattened << {'ID' => field['ID'], 'Title' => field['Title'], 'Type' => field['Type'], 'Required' => field['IsRequired'] == '1'}
235
+ end
236
+ end
237
+ flattened
238
+ end
239
+
240
+ # Return entries already submitted to the form.
241
+ #
242
+ # If you need to filter entries, pass an array as the first argument:
243
+ # entries([[field_id, operator, value], ...])
244
+ # e.g.:
245
+ # entries([['EntryId', 'Is_after', 12], ['EntryId', 'Is_before', 17]])
246
+ # entries([['Field1', 'Is_equal', 'Tim']])
247
+ # The second arg is the match parameter (AND/OR) and defaults to 'AND', e.g.
248
+ # entries([['Field2', 'Is_equal', 'Morgan'], ['Field2', 'Is_equal', 'Smith']], 'OR')
249
+ # See http://wufoo.com/docs/api/v3/entries/get/#filter for details
250
+ def entries(filters=[], filter_match='AND')
251
+ if filters.any?
252
+ options = {'match' => filter_match}
253
+ filters.each_with_index do |filter, index|
254
+ options["Filter#{index+1}"] = filter.join(' ')
255
+ end
256
+ options = {:query => options}
257
+ else
258
+ options = {}
259
+ end
260
+ @party.get("forms/#{@id}/entries", options)['Entries']
261
+ end
262
+
263
+ # Submit form data to the form.
264
+ # Pass data as a hash, with field ids as the hash keys, e.g.
265
+ # submit('Field1' => 'Tim', 'Field2' => 'Morgan')
266
+ # Return value is a Hash that includes the following keys:
267
+ # * Status
268
+ # * ErrorText
269
+ # * FieldErrors
270
+ # You must submit values for required fields (including all sub fields),
271
+ # and dates must be formatted as <tt>YYYYMMDD</tt>.
272
+ def submit(data)
273
+ options = {}
274
+ data.each do |key, value|
275
+ if value.is_a?(Hash)
276
+ type = MIME::Types.of(value[:path]).first.content_type rescue 'application/octet-stream'
277
+ options[:multipart] ||= {}
278
+ options[:multipart][key] = {:type => type, :path => value[:path]}
279
+ else
280
+ options[:body] ||= {}
281
+ options[:body][key] = value
282
+ end
283
+ end
284
+ @party.post("forms/#{@id}/entries", options)
285
+ end
286
+
287
+ # Returns comment details for the form.
288
+ # See Wufoo API documentation for possible options,
289
+ # e.g. to filter comments for a specific form entry:
290
+ # form.comments('entryId' => 123)
291
+ def comments(options={})
292
+ options = {:query => options} if options.any?
293
+ @party.get("forms/#{@id}/comments", options)['Comments']
294
+ end
295
+ end
296
+
297
+ # Wraps an individual Wufoo Report.
298
+ # == Instantiation
299
+ # There are two ways to instantiate a Report object:
300
+ # 1. Via the parent WuParty object that represents the account.
301
+ # 2. Via the WuParty::Report class directly.
302
+ # wufoo = WuParty.new(ACCOUNT, API_KEY)
303
+ # report = wufoo.report(REPORT_ID)
304
+ # # or...
305
+ # report = WuParty::Report.new(REPORT_ID, :account => ACCOUNT, :api_key => API_KEY)
306
+ # The first technique makes a call to the Wufoo API to get the report details,
307
+ # while the second technique lazily loads the report details, once something is accessed via [].
308
+ # == \Report Details
309
+ # Access report details like it is a Hash, e.g.:
310
+ # report['Name']
311
+ class Report < Entity
312
+ # Access report details.
313
+ def [](id)
314
+ @details ||= @party.report(@id)
315
+ @details[id]
316
+ end
317
+
318
+ # Returns field details for the report
319
+ def fields
320
+ @party.get("reports/#{@id}/fields")['Fields']
321
+ end
322
+
323
+ # Returns widget details for the report
324
+ def widgets
325
+ @party.get("reports/#{@id}/widgets")['Widgets']
326
+ end
327
+ end
328
+
329
+ # Wraps an individual Wufoo User.
330
+ class User < Entity
331
+
332
+ # Access user details.
333
+ def [](id)
334
+ @details ||= @party.report(@id)
335
+ @details[id]
336
+ end
337
+
338
+ end
339
+ end
@@ -0,0 +1,100 @@
1
+ require File.dirname(__FILE__) + '/../lib/wuparty'
2
+ require 'test/unit'
3
+
4
+ class WuPartyTest < Test::Unit::TestCase
5
+
6
+ # Must create a form called "Test Form" and pass in its id
7
+ # via the ENV variable WUFOO_FORM_ID.
8
+ # Give the form standard name and address fields.
9
+ # Make the name field required.
10
+
11
+ def setup
12
+ if ENV['WUFOO_ACCOUNT'] and ENV['WUFOO_API_KEY'] and ENV['WUFOO_FORM_ID']
13
+ @wufoo = WuParty.new(ENV['WUFOO_ACCOUNT'], ENV['WUFOO_API_KEY'])
14
+ @form_id = ENV['WUFOO_FORM_ID']
15
+ else
16
+ puts 'Must set WUFOO_ACCOUNT, WUFOO_API_KEY and WUFOO_FORM_ID env variables before running.'
17
+ exit(1)
18
+ end
19
+ end
20
+
21
+ def test_forms
22
+ assert @wufoo.forms
23
+ end
24
+
25
+ def test_form
26
+ form = @wufoo.form(@form_id)
27
+ assert form
28
+ assert_equal 'Test Form', form['Name']
29
+ end
30
+
31
+ def test_form_by_hash
32
+ hash = @wufoo.form(@form_id)['Hash']
33
+ assert @wufoo.form(hash)
34
+ end
35
+
36
+ def test_form_directly
37
+ form = WuParty::Form.new(@form_id, :account => ENV['WUFOO_ACCOUNT'], :api_key => ENV['WUFOO_API_KEY'])
38
+ assert_equal 'Test Form', form['Name']
39
+ end
40
+
41
+ def test_non_existent_form
42
+ assert_raise WuParty::HTTPError do
43
+ @wufoo.form('does-not-exist')
44
+ end
45
+ end
46
+
47
+ def test_reports
48
+ assert @wufoo.reports
49
+ end
50
+
51
+ def test_users
52
+ assert @wufoo.users
53
+ end
54
+
55
+ def test_form_fields
56
+ form = @wufoo.form(@form_id)
57
+ field_names = form.fields.map { |f| f['Title'] }
58
+ assert field_names.include?('Name')
59
+ assert field_names.include?('Address')
60
+ end
61
+
62
+ def test_form_submit
63
+ form = @wufoo.form(@form_id)
64
+ result = form.submit('Field1' => 'Tim', 'Field2' => 'Morgan', 'Field3' => '4010 W. New Orleans', 'Field5' => 'Broken Arrow', 'Field6' => 'OK', 'Field7' => '74011')
65
+ assert_equal 1, result['Success']
66
+ assert result['EntryId']
67
+ assert result['EntryLink']
68
+ end
69
+
70
+ def test_form_submit_error
71
+ form = @wufoo.form(@form_id)
72
+ # test a form error -- non-existent field
73
+ result = form.submit('Field1' => 'Tim', 'Field2' => 'Morgan', 'Field100' => 'Foobar')
74
+ assert_equal 0, result['Success']
75
+ assert result['ErrorText']
76
+ assert_equal [], result['FieldErrors']
77
+ # test a field error -- nothing in a required field
78
+ result = form.submit('Field2' => 'Morgan')
79
+ assert_equal 0, result['Success']
80
+ assert_equal 1, result['FieldErrors'].length
81
+ error = result['FieldErrors'].first
82
+ assert_equal 'Field1', error['ID']
83
+ assert_match /required/i, error['ErrorText']
84
+ end
85
+
86
+ def test_entries
87
+ form = @wufoo.form(@form_id)
88
+ form.submit('Field1' => 'Tim', 'Field2' => 'Morgan')
89
+ assert form.entries.last['Field1'].any?
90
+ end
91
+
92
+ def test_filtering_entries
93
+ form = @wufoo.form(@form_id)
94
+ form.submit('Field1' => 'Tim', 'Field2' => 'Morgan')
95
+ id = form.submit('Field1' => 'Jane', 'Field2' => 'Smith')['EntryId']
96
+ assert form.entries([['Field2', 'Is_equal_to', 'Morgan']]).any?
97
+ assert_equal 1, form.entries([['EntryId', 'Is_equal_to', id]]).length
98
+ end
99
+
100
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wuparty
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 2
10
+ version: 1.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Tim Morgan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-09-17 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: httparty
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 0
32
+ - 6
33
+ - 1
34
+ version: 0.6.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: multipart-post
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 21
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 1
50
+ version: 1.0.1
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: mime-types
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 47
62
+ segments:
63
+ - 1
64
+ - 16
65
+ version: "1.16"
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ description:
69
+ email: tim@timmorgan.org
70
+ executables: []
71
+
72
+ extensions: []
73
+
74
+ extra_rdoc_files: []
75
+
76
+ files:
77
+ - README.rdoc
78
+ - lib/wuparty.rb
79
+ - test/wuparty_test.rb
80
+ has_rdoc: true
81
+ homepage: http://seven1m.github.com/wuparty
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options: []
86
+
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project:
110
+ rubygems_version: 1.3.7
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Ruby wrapper for Wufoo's REST API v3.
114
+ test_files: []
115
+