rufus-jig 0.1.23 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.rspec +1 -0
  2. data/CHANGELOG.txt +8 -0
  3. data/README.rdoc +41 -11
  4. data/Rakefile +37 -12
  5. data/TODO.txt +6 -3
  6. data/lib/rufus/jig/adapters/em.rb +21 -24
  7. data/lib/rufus/jig/adapters/net.rb +3 -4
  8. data/lib/rufus/jig/adapters/net_persistent.rb +26 -7
  9. data/lib/rufus/jig/adapters/patron.rb +25 -10
  10. data/lib/rufus/jig/couch.rb +199 -36
  11. data/lib/rufus/jig/http.rb +183 -90
  12. data/lib/rufus/jig/path.rb +4 -4
  13. data/lib/rufus/jig/version.rb +1 -1
  14. data/rufus-jig.gemspec +55 -34
  15. data/spec/couch/attachements_spec.rb +113 -0
  16. data/spec/couch/basic_auth_spec.rb +75 -0
  17. data/spec/couch/conditional_spec.rb +178 -0
  18. data/spec/couch/continuous.rb +97 -0
  19. data/spec/couch/couch_spec.rb +64 -0
  20. data/spec/couch/db_spec.rb +366 -0
  21. data/{test → spec/couch}/tweet.png +0 -0
  22. data/spec/couch/views_spec.rb +326 -0
  23. data/spec/couch_url.txt +2 -0
  24. data/spec/jig/basic_auth_spec.rb +51 -0
  25. data/spec/jig/conditional_spec.rb +76 -0
  26. data/spec/jig/delete_spec.rb +32 -0
  27. data/spec/jig/get_spec.rb +116 -0
  28. data/spec/jig/misc_spec.rb +120 -0
  29. data/spec/jig/new_spec.rb +95 -0
  30. data/spec/jig/parse_uri_spec.rb +139 -0
  31. data/spec/jig/post_spec.rb +79 -0
  32. data/spec/jig/prefix_spec.rb +51 -0
  33. data/spec/jig/put_spec.rb +68 -0
  34. data/spec/jig/timeout_spec.rb +94 -0
  35. data/{test → spec}/server.rb +14 -4
  36. data/spec/spec_helper.rb +61 -0
  37. data/spec/support/couch_helper.rb +14 -0
  38. data/spec/support/server_helper.rb +32 -0
  39. metadata +98 -43
  40. data/lib/rufus/jig/adapters/net_response.rb +0 -42
  41. data/test/base.rb +0 -53
  42. data/test/bm/bm0.rb +0 -49
  43. data/test/bm/bm1.rb +0 -43
  44. data/test/conc/put_vs_delete.rb +0 -28
  45. data/test/couch_base.rb +0 -52
  46. data/test/couch_url.txt +0 -1
  47. data/test/ct_0_couch.rb +0 -64
  48. data/test/ct_1_couchdb.rb +0 -204
  49. data/test/ct_2_couchdb_options.rb +0 -50
  50. data/test/ct_3_couchdb_views.rb +0 -106
  51. data/test/ct_4_attachments.rb +0 -126
  52. data/test/ct_5_couchdb_continuous.rb +0 -92
  53. data/test/cut_0_auth_couch.rb +0 -62
  54. data/test/test.rb +0 -28
  55. data/test/to.sh +0 -25
  56. data/test/tt_0_get_timeout.rb +0 -92
  57. data/test/ut_0_http_get.rb +0 -191
  58. data/test/ut_1_http_post.rb +0 -81
  59. data/test/ut_2_http_delete.rb +0 -42
  60. data/test/ut_3_http_put.rb +0 -105
  61. data/test/ut_4_http_prefix.rb +0 -50
  62. data/test/ut_5_http_misc.rb +0 -65
  63. data/test/ut_6_args.rb +0 -98
  64. data/test/ut_7_parse_uri.rb +0 -79
  65. data/test/ut_8_auth.rb +0 -37
@@ -22,6 +22,8 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
+ require 'cgi'
26
+
25
27
  require 'base64'
26
28
  require 'socket'
27
29
  # for #on_change
@@ -38,7 +40,7 @@ module Rufus::Jig
38
40
  attr_reader :path
39
41
  attr_reader :http
40
42
 
41
- def initialize (*args)
43
+ def initialize(*args)
42
44
 
43
45
  @http = Rufus::Jig::Http.new(*args)
44
46
 
@@ -55,7 +57,7 @@ module Rufus::Jig
55
57
  @http.close
56
58
  end
57
59
 
58
- def put (doc_or_path, opts={})
60
+ def put(doc_or_path, opts={})
59
61
 
60
62
  path, payload = if doc_or_path.is_a?(String)
61
63
  [ doc_or_path, '' ]
@@ -84,19 +86,71 @@ module Rufus::Jig
84
86
  nil
85
87
  end
86
88
 
87
- def get (doc_or_path, opts={})
89
+ def get(doc_or_path, opts={})
88
90
 
89
91
  path = doc_or_path.is_a?(Hash) ? doc_or_path['_id'] : doc_or_path
90
92
  path = adjust(path)
91
93
 
92
- if et = etag(path)
93
- opts[:etag] = et
94
+ @http.get(path, opts)
95
+ end
96
+
97
+ # Returns all the docs in the current database.
98
+ #
99
+ # c = Rufus::Jig::Couch.new('http://127.0.0.1:5984, 'my_db')
100
+ #
101
+ # docs = c.all
102
+ # docs = c.all(:include_docs => false)
103
+ # docs = c.all(:include_design_docs => false)
104
+ #
105
+ # docs = c.all(:skip => 10, :limit => 10)
106
+ #
107
+ # It understands (passes) all the options for CouchDB view API :
108
+ #
109
+ # http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options
110
+ #
111
+ def all(opts={})
112
+
113
+ opts = opts.dup
114
+ # don't touch the original
115
+
116
+ path = adjust('_all_docs')
117
+
118
+ opts[:include_docs] = true if opts[:include_docs].nil?
119
+
120
+ adjust_params(opts)
121
+
122
+ keys = opts.delete(:keys)
123
+
124
+ return [] if keys && keys.empty?
125
+
126
+ res = if keys
127
+ opts[:cache] = :with_body if opts[:cache].nil?
128
+ @http.post(path, { 'keys' => keys }, opts)
129
+ else
130
+ @http.get(path, opts)
94
131
  end
95
132
 
96
- @http.get(path, opts)
133
+ rows = res['rows']
134
+
135
+ docs = if opts[:params][:include_docs]
136
+ rows.map { |row| row['doc'] }
137
+ else
138
+ rows.map { |row| { '_id' => row['id'], '_rev' => row['value']['rev'] } }
139
+ end
140
+
141
+ if opts[:include_design_docs] == false
142
+ docs = docs.reject { |doc| DESIGN_PATH_REGEX.match(doc['_id']) }
143
+ end
144
+
145
+ docs
146
+ end
147
+
148
+ def ids(opts={})
149
+
150
+ all(opts).collect { |row| row['_id'] }
97
151
  end
98
152
 
99
- def delete (doc_or_path, rev=nil)
153
+ def delete(doc_or_path, rev=nil)
100
154
 
101
155
  doc, path = if rev
102
156
  [ { '_id' => doc_or_path, '_rev' => rev }, doc_or_path ]
@@ -135,17 +189,9 @@ module Rufus::Jig
135
189
  end
136
190
  end
137
191
 
138
- def post (path, doc)
139
-
140
- path = adjust(path)
141
-
142
- opts = { :content_type => :json }
143
-
144
- if et = etag(path)
145
- opts[:etag] = et
146
- end
192
+ def post(path, doc, opts={})
147
193
 
148
- @http.post(path, doc, opts)
194
+ @http.post(adjust(path), doc, opts.merge(:content_type => :json))
149
195
  end
150
196
 
151
197
  # Attaches a file to a couch document.
@@ -160,7 +206,7 @@ module Rufus::Jig
160
206
  # doc, 'my_picture', data,
161
207
  # :content_type => 'image/jpeg')
162
208
  #
163
- def attach (doc_id, doc_rev, attachment_name, data, opts=nil)
209
+ def attach(doc_id, doc_rev, attachment_name, data, opts=nil)
164
210
 
165
211
  if opts.nil?
166
212
  opts = data
@@ -183,23 +229,27 @@ module Rufus::Jig
183
229
  path = adjust("#{doc_id}/#{attachment_name}?rev=#{doc_rev}")
184
230
 
185
231
  if @http.variant == :patron
186
- #
187
- # patron, as of 0.4.5 has difficulties when PUTting attachements
232
+
233
+ # patron, as of 0.4.5 (~> 0.4.10), has difficulties when PUTting
234
+ # attachements
188
235
  # this is a fallback to net/http
189
- #
236
+
190
237
  require 'net/http'
238
+
191
239
  http = Net::HTTP.new(@http.host, @http.port)
240
+
192
241
  req = Net::HTTP::Put.new(path)
193
242
  req['User-Agent'] =
194
- "rufus-jig #{Rufus::Jig::VERSION} (patron 0.4.5 fallback to net/http)"
243
+ "rufus-jig #{Rufus::Jig::VERSION} (patron 0.4.x fallback to net/http)"
195
244
  req['Content-Type'] =
196
245
  opts[:content_type]
246
+ req['Accept'] =
247
+ 'application/json'
197
248
  req.body = data
198
- res = http.start { |h| h.request(req) }
199
- status = res.code.to_i
200
- raise Rufus::Jig::HttpError.new(status, res.body) \
201
- unless [ 200, 201 ].include?(status)
202
- return nil
249
+
250
+ res = Rufus::Jig::HttpResponse.new(http.start { |h| h.request(req) })
251
+
252
+ return @http.send(:respond, :put, path, nil, opts, nil, res)
203
253
  end
204
254
 
205
255
  @http.put(path, data, opts)
@@ -213,7 +263,7 @@ module Rufus::Jig
213
263
  #
214
264
  # couch.detach(doc, 'my_picture')
215
265
  #
216
- def detach (doc_id, doc_rev, attachment_name=nil)
266
+ def detach(doc_id, doc_rev, attachment_name=nil)
217
267
 
218
268
  if attachment_name.nil?
219
269
  attachment_name = doc_rev
@@ -244,7 +294,7 @@ module Rufus::Jig
244
294
  # Note : doc inclusion (third parameter to the block) only works with
245
295
  # CouchDB >= 0.11.
246
296
  #
247
- def on_change (opts={}, &block)
297
+ def on_change(opts={}, &block)
248
298
 
249
299
  query = {
250
300
  'feed' => 'continuous',
@@ -299,6 +349,8 @@ module Rufus::Jig
299
349
  on_change(opts, &block) if opts[:reconnect]
300
350
  end
301
351
 
352
+ DESIGN_PATH_REGEX = /^\_design\//
353
+
302
354
  # A development method. Removes all the design documents in this couch
303
355
  # database.
304
356
  #
@@ -309,14 +361,111 @@ module Rufus::Jig
309
361
 
310
362
  docs = get('_all_docs')['rows']
311
363
 
312
- views = docs.select { |d| d['id'] && d['id'].match(/^\_design\//) }
364
+ views = docs.select { |d| d['id'] && DESIGN_PATH_REGEX.match(d['id']) }
313
365
 
314
366
  views.each { |v| delete(v['id'], v['value']['rev']) }
315
367
  end
316
368
 
369
+ # Queries a view.
370
+ #
371
+ # res = couch.query('_design/my_test/_view/my_view')
372
+ # #
373
+ # # [ {"id"=>"c3", "key"=>"capuccino", "value"=>nil},
374
+ # # {"id"=>"c0", "key"=>"espresso", "value"=>nil},
375
+ # # {"id"=>"c2", "key"=>"macchiato", "value"=>nil},
376
+ # # {"id"=>"c4", "key"=>"macchiato", "value"=>nil},
377
+ # # {"id"=>"c1", "key"=>"ristretto", "value"=>nil} ]
378
+ #
379
+ # # or simply :
380
+ #
381
+ # res = couch.query('my_test:my_view')
382
+ #
383
+ # Accepts the usual couch parameters : limit, skip, descending, keys,
384
+ # startkey, endkey, ...
385
+ #
386
+ def query(path, opts={})
387
+
388
+ opts = opts.dup
389
+ # don't touch the original
390
+
391
+ raw = opts.delete(:raw)
392
+
393
+ path = if DESIGN_PATH_REGEX.match(path)
394
+ path
395
+ else
396
+ doc_id, view = path.split(':')
397
+ path = "_design/#{doc_id}/_view/#{view}"
398
+ end
399
+
400
+ path = adjust(path)
401
+
402
+ adjust_params(opts)
403
+
404
+ keys = opts.delete(:keys)
405
+
406
+ res = if keys
407
+ opts[:cache] = :with_body if opts[:cache].nil?
408
+ @http.post(path, { 'keys' => keys }, opts)
409
+ else
410
+ @http.get(path, opts)
411
+ end
412
+
413
+ return nil if res == true
414
+ # POST and the view doesn't exist
415
+
416
+ return res if raw
417
+
418
+ res.nil? ? res : res['rows']
419
+ end
420
+
421
+ # A shortcut for
422
+ #
423
+ # query(path, :include_docs => true).collect { |row| row['doc'] }
424
+ #
425
+ def query_for_docs(path, opts={})
426
+
427
+ res = query(path, opts.merge(:include_docs => true))
428
+
429
+ if res.nil?
430
+ nil
431
+ elsif opts[:raw]
432
+ res
433
+ else
434
+ res.collect { |row| row['doc'] }.uniq
435
+ end
436
+ end
437
+
438
+ # Creates or updates docs in bulk (could even delete).
439
+ #
440
+ # http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API#Modify_Multiple_Documents_With_a_Single_Request
441
+ #
442
+ def bulk_put(docs, opts={})
443
+
444
+ res = @http.post(adjust('_bulk_docs'), { 'docs' => docs })
445
+
446
+ opts[:raw] ?
447
+ res :
448
+ res.collect { |row| { '_id' => row['id'], '_rev' => row['rev'] } }
449
+ end
450
+
451
+ # Given an array of documents (at least { '_id' => x, '_rev' => y },
452
+ # deletes them.
453
+ #
454
+ def bulk_delete(docs, opts={})
455
+
456
+ docs = docs.inject([]) { |a, doc|
457
+ a << {
458
+ '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true
459
+ } if doc
460
+ a
461
+ }
462
+
463
+ bulk_put(docs, opts)
464
+ end
465
+
317
466
  protected
318
467
 
319
- def adjust (path)
468
+ def adjust(path)
320
469
 
321
470
  case path
322
471
  when '.' then @path
@@ -325,13 +474,27 @@ module Rufus::Jig
325
474
  end
326
475
  end
327
476
 
328
- # Fetches etag from http cache
329
- #
330
- def etag (path)
477
+ COUCH_PARAMS = %w[
478
+ key startkey endkey descending group group_level limit skip include_docs
479
+ ].collect { |k| k.to_sym }
480
+
481
+ COUCH_KEYS = [ :key, :startkey, :endkey ]
482
+
483
+ def adjust_params(opts)
484
+
485
+ opts[:params] = opts.keys.inject({}) { |h, k|
331
486
 
332
- r = @http.cache[path]
487
+ if COUCH_PARAMS.include?(k)
488
+ v = opts.delete(k)
489
+ if COUCH_KEYS.include?(k)
490
+ h[k] = CGI.escape(Rufus::Json.encode(v))
491
+ elsif v != nil
492
+ h[k] = v
493
+ end
494
+ end
333
495
 
334
- r ? r.first : nil
496
+ h
497
+ }
335
498
  end
336
499
  end
337
500
  end
@@ -22,7 +22,6 @@
22
22
  # Made in Japan.
23
23
  #++
24
24
 
25
- require 'ostruct'
26
25
 
27
26
  require 'rufus/lru' # gem install rufus-lru
28
27
 
@@ -34,7 +33,7 @@ module Rufus::Jig
34
33
  # The classical helper method, does a full copy of the given object.
35
34
  # Thanks Marshal.
36
35
  #
37
- def self.marshal_copy (o)
36
+ def self.marshal_copy(o)
38
37
 
39
38
  Marshal.load(Marshal.dump(o))
40
39
  end
@@ -45,7 +44,7 @@ module Rufus::Jig
45
44
 
46
45
  attr_reader :status
47
46
 
48
- def initialize (status, message)
47
+ def initialize(status, message)
49
48
 
50
49
  @status = status
51
50
  super(message)
@@ -56,56 +55,129 @@ module Rufus::Jig
56
55
  #
57
56
  class TimeoutError < RuntimeError
58
57
 
59
- def initialize (message=nil)
58
+ def initialize(message=nil)
60
59
 
61
60
  super(message || 'timed out')
62
61
  end
63
62
  end
64
63
 
64
+ #
65
+ # a Rufus::Jig wrapper for the server response.
66
+ #
65
67
  class HttpResponse
66
68
 
67
69
  attr_reader :status, :headers, :body
68
70
  attr_reader :original
71
+
72
+ def initialize(res)
73
+
74
+ net_http_init(res)
75
+ end
76
+
77
+ def etag
78
+
79
+ headers['ETag'] || headers['Etag'] || headers['etag']
80
+ end
81
+
82
+ protected
83
+
84
+ # (leveraged by the patron adapter as well)
85
+ #
86
+ def net_http_init(net_http_response)
87
+
88
+ @original = net_http_response
89
+ @status = net_http_response.code.to_i
90
+ @body = net_http_response.body
91
+ @headers = {}
92
+ net_http_response.each { |k, v|
93
+ @headers[k.split('-').collect { |s| s.capitalize }.join('-')] = v
94
+ }
95
+ end
69
96
  end
70
97
 
71
- URI_REGEX = /(https?):\/\/([^@]+:[^@]+@)?([^\/]+)([^\?]*)(\?.+)?$/
98
+ #
99
+ # Rufus::Jig.parse_uri returns instances of this class.
100
+ #
101
+ class Uri
102
+
103
+ attr_accessor :scheme
104
+ attr_accessor :username, :password
105
+ attr_accessor :host, :port
106
+ attr_accessor :path, :query, :fragment
107
+
108
+ def initialize(sc, us, ps, ho, po, pa, qu, fr)
109
+
110
+ @scheme = sc
111
+ @username = us
112
+ @password = ps
113
+ @host = ho
114
+ @port = po
115
+ @path = pa
116
+ @query = qu
117
+ @fragment = fr
118
+ end
119
+
120
+ def to_s
121
+
122
+ tail = tail_to_s
123
+
124
+ return tail unless @host
125
+
126
+ up = ''
127
+ up = "#{@username}:#{password}@" if @username
128
+
129
+ "#{@scheme}://#{up}#{@host}:#{@port}#{tail}"
130
+ end
131
+
132
+ def tail_to_s
133
+
134
+ tail = @path
135
+ tail = "#{tail}?#{@query}" if @query
136
+ tail = "#{tail}##{@fragment}" if @fragment
137
+
138
+ tail
139
+ end
140
+ end
141
+
142
+ URI_REGEX = /(https?):\/\/([^@]+:[^@]+@)?([^\/]+)(.*)?$/
143
+ PATH_REGEX = /([^\?#]*)(\?[^#]+)?(#[^#]+)?$/
72
144
 
73
145
  # The current URI lib is not UTF-8 friendly, so this is a workaround.
74
146
  # Temporary hopefully.
75
147
  #
76
- def self.parse_uri (s)
148
+ def self.parse_uri(s)
77
149
 
78
150
  m = URI_REGEX.match(s)
79
151
 
80
- scheme, uname, pass, host, port, path, query = if m
152
+ scheme, uname, pass, host, port, tail = if m
81
153
 
82
154
  ho, po = m[3].split(':')
83
155
  po = (po || 80).to_i
84
156
 
85
- query = m[5] ? m[5][1..-1] : nil
86
-
87
157
  un, pa = m[2] ? m[2][0..-2].split(':') : [ nil, nil ]
88
158
 
89
- [ m[1], un, pa, ho, po, m[4], query ]
159
+ [ m[1], un, pa, ho, po, m[4] ]
90
160
 
91
161
  else
92
162
 
93
- pa, qu = s.split('?')
94
-
95
- [ nil, nil, nil, nil, nil, pa, qu ]
163
+ [ nil, nil, nil, nil, nil, s ]
96
164
  end
97
165
 
98
- OpenStruct.new(
99
- :scheme => scheme,
100
- :host => host, :port => port,
101
- :path => path, :query => query,
102
- :username => uname, :password => pass)
166
+ m = PATH_REGEX.match(tail)
167
+
168
+ path, query, fragment = [ m[1], m[2], m[3] ]
169
+
170
+ port = 443 if scheme == 'https' && port == 80
171
+ query = query[1..-1] if query
172
+ fragment = fragment[1..-1] if fragment
173
+
174
+ Uri.new(scheme, uname, pass, host, port, path, query, fragment)
103
175
  end
104
176
 
105
177
  # The current URI lib is not UTF-8 friendly, so this is a workaround.
106
178
  # Temporary hopefully.
107
179
  #
108
- def self.parse_host (s)
180
+ def self.parse_host(s)
109
181
 
110
182
  u = parse_uri(s)
111
183
 
@@ -140,9 +212,9 @@ module Rufus::Jig
140
212
  # path, it is stored in @_path (and not used).
141
213
  # Rufus::Jig::Couch uses it though.
142
214
  #
143
- attr_accessor :_path, :_query
215
+ attr_accessor :_path, :_query, :_fragment
144
216
 
145
- def initialize (*args)
217
+ def initialize(*args)
146
218
 
147
219
  @options = args.last.is_a?(Hash) ? args.pop.dup : {}
148
220
 
@@ -162,13 +234,19 @@ module Rufus::Jig
162
234
  @options[:basic_auth] ||= [ u.username, u.password ] if u.username
163
235
 
164
236
  if args[1]
165
- @_path, @_query = args[1].split('?')
237
+ uu = Rufus::Jig.parse_uri(args[1])
238
+ @_path = uu.path
239
+ @_query = uu.query
240
+ @_fragment = uu.fragment
166
241
  else
167
242
  @_path = u.path
168
243
  @_query = u.query
244
+ @_fragment = u.fragment
169
245
  end
170
246
  end
171
247
 
248
+ @_path ||= ''
249
+
172
250
  @cache = LruHash.new((@options[:cache_size] || 35).to_i)
173
251
 
174
252
  if pf = @options[:prefix]
@@ -180,99 +258,79 @@ module Rufus::Jig
180
258
 
181
259
  def uri
182
260
 
183
- OpenStruct.new(:scheme => @scheme, :host => @host, :port => @port)
261
+ Uri.new(@scheme, nil, nil, @host, @port, nil, nil, nil)
184
262
  end
185
263
 
186
- def get (path, opts={})
264
+ def get(path, opts={})
187
265
 
188
266
  request(:get, path, nil, opts)
189
267
  end
190
268
 
191
- def post (path, data, opts={})
269
+ def post(path, data, opts={})
192
270
 
193
271
  request(:post, path, data, opts)
194
272
  end
195
273
 
196
- def put (path, data, opts={})
274
+ def put(path, data, opts={})
197
275
 
198
276
  request(:put, path, data, opts)
199
277
  end
200
278
 
201
- def delete (path, opts={})
279
+ def delete(path, opts={})
202
280
 
203
281
  request(:delete, path, nil, opts)
204
282
  end
205
283
 
206
284
  protected
207
285
 
208
- def from_cache (path, opts)
209
-
210
- if et = opts[:etag]
211
-
212
- cached = @cache[path]
213
-
214
- if cached && cached.first != et
215
- #
216
- # cached version is perhaps stale
217
- #
218
- cached = nil
219
- opts.delete(:etag)
220
- end
221
-
222
- cached
223
-
224
- else
225
-
226
- nil
227
- end
228
- end
229
-
230
- def request (method, path, data, opts={})
286
+ def request(method, path, data, opts={})
231
287
 
232
- raw = raw_expected?(opts)
288
+ data_hash = data.hash
233
289
 
234
290
  path = add_prefix(path, opts)
235
291
  path = add_params(path, opts)
236
292
 
237
293
  path = '/' if path == ''
238
294
 
239
- cached = from_cache(path, opts)
240
- opts.delete(:etag) if (not cached) || method != :get
295
+ cached = from_cache(method, path, data_hash, opts)
241
296
 
242
297
  opts = rehash_options(opts)
243
298
  data = repack_data(data, opts)
244
299
 
245
- r = do_request(method, path, data, opts)
300
+ res = do_request(method, path, data, opts)
246
301
 
247
- @last_response = r
302
+ respond(method, path, data_hash, opts, cached, res)
303
+ end
248
304
 
249
- unless raw
305
+ def respond(method, path, data_hash, opts, cached, res)
250
306
 
251
- return Rufus::Jig.marshal_copy(cached.last) if r.status == 304
252
- return nil if method == :get && r.status == 404
253
- return true if [ 404, 409 ].include?(r.status)
307
+ @last_response = res
254
308
 
255
- raise @error_class.new(r.status, r.body) \
256
- if r.status >= 400 && r.status < 600
257
- end
309
+ raw = opts[:raw]
310
+ raw == false ? false : raw || @options[:raw]
258
311
 
259
- b = decode_body(r, opts)
312
+ unless raw
260
313
 
261
- do_cache(method, path, r, b, opts)
314
+ return Rufus::Jig.marshal_copy(cached.last) if res.status == 304
315
+ return nil if method == :get && res.status == 404
316
+ return true if [ 404, 409 ].include?(res.status)
262
317
 
263
- raw ? r : b
264
- end
318
+ if res.status >= 400 && res.status < 600
319
+ #File.open('error.html', 'wb') { |f| f.puts(res.body) }
320
+ raise @error_class.new(res.status, res.body)
321
+ end
322
+ end
265
323
 
266
- def raw_expected? (opts)
324
+ b = decode_body(res, opts)
267
325
 
268
- raw = opts[:raw]
326
+ do_cache(method, path, data_hash, opts, res, b)
269
327
 
270
- raw == false ? false : raw || @options[:raw]
328
+ raw ? res : b
271
329
  end
272
330
 
273
331
  # Should work with GET and POST/PUT options
274
332
  #
275
- def rehash_options (opts)
333
+ def rehash_options(opts)
276
334
 
277
335
  opts['Accept'] ||= (opts.delete(:accept) || 'application/json')
278
336
  opts['Accept'] = 'application/json' if opts['Accept'] == :json
@@ -291,7 +349,7 @@ module Rufus::Jig
291
349
  opts
292
350
  end
293
351
 
294
- def add_prefix (path, opts)
352
+ def add_prefix(path, opts)
295
353
 
296
354
  host = Rufus::Jig.parse_host(path)
297
355
 
@@ -306,7 +364,7 @@ module Rufus::Jig
306
364
  Path.join(*elts)
307
365
  end
308
366
 
309
- def add_params (path, opts)
367
+ def add_params(path, opts)
310
368
 
311
369
  if params = opts[:params]
312
370
 
@@ -322,42 +380,77 @@ module Rufus::Jig
322
380
  path
323
381
  end
324
382
 
325
- def repack_data (data, opts)
383
+ APP_JSON_REGEX = /^application\/json/
326
384
 
327
- return nil unless data
385
+ def repack_data(data, opts)
328
386
 
387
+ return nil if data == nil
388
+ return data if data.is_a?(String)
329
389
 
330
- data = if data.is_a?(String)
331
- data
332
- elsif (opts['Content-Type'] || '').match(/^application\/json/)
333
- Rufus::Json.encode(data)
390
+ ct = opts['Content-Type'] || ''
391
+
392
+ if APP_JSON_REGEX.match(ct)
393
+ return Rufus::Json.encode(data)
394
+ end
395
+ if ct == '' && (data.is_a?(Array) || data.is_a?(Hash))
396
+ opts['Content-Type'] = 'application/json'
397
+ return Rufus::Json.encode(data)
398
+ end
399
+
400
+ data.to_s
401
+ end
402
+
403
+ def from_cache(method, path, data_hash, opts)
404
+
405
+ path = if method == :post && opts[:cache] == :with_body
406
+ "#{path}//#{data_hash}"
334
407
  else
335
- data.to_s
408
+ path
336
409
  end
337
410
 
338
- #opts['Content-Length'] =
339
- # (data.respond_to?(:bytesize) ? data.bytesize : data.size).to_s
340
- #
341
- # Patron doesn't play well with custom lengths : "56, 56" issue
411
+ etag = opts[:etag]
412
+ cached = @cache[path]
413
+
414
+ if etag && cached && cached.first != etag
415
+ # cached version is probably stale
416
+ cached = nil
417
+ opts.delete(:etag)
418
+ end
419
+ if ( ! etag) && cached
420
+ opts[:etag] = cached.first
421
+ end
342
422
 
343
- data
423
+ cached
344
424
  end
345
425
 
346
- def do_cache (method, path, response, body, opts)
426
+ def do_cache(method, path, data_hash, opts, response, body)
427
+
428
+ path = if method == :post && opts[:cache] == :with_body
429
+ "#{path}//#{data_hash}"
430
+ else
431
+ path
432
+ end
347
433
 
348
- if (method != :get) || (opts[:cache] == false)
434
+ etag = response.etag
435
+
436
+ if etag.nil? || opts[:cache] == false || method == :delete
437
+ @cache.delete(path)
438
+ return
439
+ end
440
+ if method != :get && ( ! opts[:cache])
349
441
  @cache.delete(path)
350
- elsif et = response.headers['Etag']
351
- @cache[path] = [ et, Rufus::Jig.marshal_copy(body) ]
442
+ return
352
443
  end
444
+
445
+ @cache[path] = [ etag, Rufus::Jig.marshal_copy(body) ]
353
446
  end
354
447
 
355
- def decode_body (response, opts)
448
+ def decode_body(response, opts)
356
449
 
357
450
  b = response.body
358
451
  ct = response.headers['Content-Type']
359
452
 
360
- if opts[:force_json] || (ct && ct.match(/^application\/json/))
453
+ if opts[:force_json] || (ct && APP_JSON_REGEX.match(ct))
361
454
  Rufus::Json.decode(b)
362
455
  else
363
456
  b