wuparty 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+