mattetti-couchrest 0.2.1.0 → 0.13

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 (37) hide show
  1. data/README.md +6 -31
  2. data/Rakefile +1 -1
  3. data/examples/model/example.rb +13 -19
  4. data/lib/couchrest/core/database.rb +4 -3
  5. data/lib/couchrest/core/model.rb +615 -0
  6. data/lib/couchrest/core/response.rb +4 -5
  7. data/lib/couchrest/core/server.rb +1 -1
  8. data/lib/couchrest/mixins/callbacks.rb +33 -74
  9. data/lib/couchrest/mixins/design_doc.rb +1 -2
  10. data/lib/couchrest/mixins/document_queries.rb +1 -1
  11. data/lib/couchrest/mixins/extended_document_mixins.rb +1 -2
  12. data/lib/couchrest/mixins/properties.rb +3 -8
  13. data/lib/couchrest/mixins/validation.rb +1 -2
  14. data/lib/couchrest/mixins/views.rb +5 -5
  15. data/lib/couchrest/monkeypatches.rb +48 -65
  16. data/lib/couchrest/more/extended_document.rb +8 -75
  17. data/lib/couchrest/more/property.rb +1 -12
  18. data/lib/couchrest/support/class.rb +132 -148
  19. data/lib/couchrest.rb +8 -33
  20. data/spec/couchrest/core/database_spec.rb +23 -28
  21. data/spec/couchrest/core/model_spec.rb +856 -0
  22. data/spec/couchrest/more/casted_model_spec.rb +0 -14
  23. data/spec/couchrest/more/extended_doc_spec.rb +4 -450
  24. data/spec/couchrest/more/property_spec.rb +5 -16
  25. data/spec/spec_helper.rb +0 -4
  26. metadata +3 -16
  27. data/lib/couchrest/mixins/extended_attachments.rb +0 -68
  28. data/lib/couchrest/validation/validators/confirmation_validator.rb +0 -99
  29. data/spec/couchrest/more/casted_extended_doc_spec.rb +0 -40
  30. data/spec/couchrest/more/extended_doc_attachment_spec.rb +0 -129
  31. data/spec/couchrest/more/extended_doc_view_spec.rb +0 -206
  32. data/spec/couchrest/support/class_spec.rb +0 -59
  33. data/spec/fixtures/more/article.rb +0 -34
  34. data/spec/fixtures/more/course.rb +0 -14
  35. data/spec/fixtures/more/event.rb +0 -6
  36. data/spec/fixtures/more/person.rb +0 -8
  37. data/spec/fixtures/more/question.rb +0 -6
@@ -0,0 +1,615 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'extlib'
4
+ rescue
5
+ puts "CouchRest::Model requires extlib. This is left out of the gemspec on purpose."
6
+ raise
7
+ end
8
+ require 'digest/md5'
9
+ require File.dirname(__FILE__) + '/document'
10
+ require 'mime/types'
11
+
12
+ # = CouchRest::Model - Document modeling, the CouchDB way
13
+ module CouchRest
14
+ # = CouchRest::Model - Document modeling, the CouchDB way
15
+ #
16
+ # CouchRest::Model provides an ORM-like interface for CouchDB documents. It
17
+ # avoids all usage of <tt>method_missing</tt>, and tries to strike a balance
18
+ # between usability and magic. See CouchRest::Model#view_by for
19
+ # documentation about the view-generation system.
20
+ #
21
+ # ==== Example
22
+ #
23
+ # This is an example class using CouchRest::Model. It is taken from the
24
+ # spec/couchrest/core/model_spec.rb file, which may be even more up to date
25
+ # than this example.
26
+ #
27
+ # class Article < CouchRest::Model
28
+ # use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
29
+ # unique_id :slug
30
+ #
31
+ # view_by :date, :descending => true
32
+ # view_by :user_id, :date
33
+ #
34
+ # view_by :tags,
35
+ # :map =>
36
+ # "function(doc) {
37
+ # if (doc['couchrest-type'] == 'Article' && doc.tags) {
38
+ # doc.tags.forEach(function(tag){
39
+ # emit(tag, 1);
40
+ # });
41
+ # }
42
+ # }",
43
+ # :reduce =>
44
+ # "function(keys, values, rereduce) {
45
+ # return sum(values);
46
+ # }"
47
+ #
48
+ # key_writer :date
49
+ # key_reader :slug, :created_at, :updated_at
50
+ # key_accessor :title, :tags
51
+ #
52
+ # timestamps!
53
+ #
54
+ # before(:create, :generate_slug_from_title)
55
+ # def generate_slug_from_title
56
+ # self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
57
+ # end
58
+ # end
59
+ #
60
+ # ==== Examples of finding articles with these views:
61
+ #
62
+ # * All the articles by Barney published in the last 24 hours. Note that we
63
+ # use <tt>{}</tt> as a special value that sorts after all strings,
64
+ # numbers, and arrays.
65
+ #
66
+ # Article.by_user_id_and_date :startkey => ["barney", Time.now - 24 * 3600], :endkey => ["barney", {}]
67
+ #
68
+ # * The most recent 20 articles. Remember that the <tt>view_by :date</tt>
69
+ # has the default option <tt>:descending => true</tt>.
70
+ #
71
+ # Article.by_date :limit => 20
72
+ #
73
+ # * The raw CouchDB view reduce result for the custom <tt>:tags</tt> view.
74
+ # In this case we'll get a count of the number of articles tagged "ruby".
75
+ #
76
+ # Article.by_tags :key => "ruby", :reduce => true
77
+ #
78
+ class Model < Document
79
+
80
+ # instantiates the hash by converting all the keys to strings.
81
+ def initialize keys = {}
82
+ super(keys)
83
+ apply_defaults
84
+ cast_keys
85
+ unless self['_id'] && self['_rev']
86
+ self['couchrest-type'] = self.class.to_s
87
+ end
88
+ end
89
+
90
+ # this is the CouchRest::Database that model classes will use unless
91
+ # they override it with <tt>use_database</tt>
92
+ cattr_accessor :default_database
93
+
94
+ class_inheritable_accessor :casts
95
+ class_inheritable_accessor :default_obj
96
+ class_inheritable_accessor :class_database
97
+ class_inheritable_accessor :design_doc
98
+ class_inheritable_accessor :design_doc_slug_cache
99
+ class_inheritable_accessor :design_doc_fresh
100
+
101
+ class << self
102
+ # override the CouchRest::Model-wide default_database
103
+ def use_database db
104
+ self.class_database = db
105
+ end
106
+
107
+ # returns the CouchRest::Database instance that this class uses
108
+ def database
109
+ self.class_database || CouchRest::Model.default_database
110
+ end
111
+
112
+ # Load a document from the database by id
113
+ def get id
114
+ doc = database.get id
115
+ new(doc)
116
+ end
117
+
118
+ # Load all documents that have the "couchrest-type" field equal to the
119
+ # name of the current class. Take the standard set of
120
+ # CouchRest::Database#view options.
121
+ def all opts = {}, &block
122
+ self.design_doc ||= Design.new(default_design_doc)
123
+ unless design_doc_fresh
124
+ refresh_design_doc
125
+ end
126
+ view :all, opts, &block
127
+ end
128
+
129
+ # Load the first document that have the "couchrest-type" field equal to
130
+ # the name of the current class.
131
+ #
132
+ # ==== Returns
133
+ # Object:: The first object instance available
134
+ # or
135
+ # Nil:: if no instances available
136
+ #
137
+ # ==== Parameters
138
+ # opts<Hash>::
139
+ # View options, see <tt>CouchRest::Database#view</tt> options for more info.
140
+ def first opts = {}
141
+ first_instance = self.all(opts.merge!(:limit => 1))
142
+ first_instance.empty? ? nil : first_instance.first
143
+ end
144
+
145
+ # Cast a field as another class. The class must be happy to have the
146
+ # field's primitive type as the argument to it's constuctur. Classes
147
+ # which inherit from CouchRest::Model are happy to act as sub-objects
148
+ # for any fields that are stored in JSON as object (and therefore are
149
+ # parsed from the JSON as Ruby Hashes).
150
+ #
151
+ # Example:
152
+ #
153
+ # class Post < CouchRest::Model
154
+ #
155
+ # key_accessor :title, :body, :author
156
+ #
157
+ # cast :author, :as => 'Author'
158
+ #
159
+ # end
160
+ #
161
+ # post.author.class #=> Author
162
+ #
163
+ # Using the same example, if a Post should have many Comments, we
164
+ # would declare it like this:
165
+ #
166
+ # class Post < CouchRest::Model
167
+ #
168
+ # key_accessor :title, :body, :author, comments
169
+ #
170
+ # cast :author, :as => 'Author'
171
+ # cast :comments, :as => ['Comment']
172
+ #
173
+ # end
174
+ #
175
+ # post.author.class #=> Author
176
+ # post.comments.class #=> Array
177
+ # post.comments.first #=> Comment
178
+ #
179
+ def cast field, opts = {}
180
+ self.casts ||= {}
181
+ self.casts[field.to_s] = opts
182
+ end
183
+
184
+ # Defines methods for reading and writing from fields in the document.
185
+ # Uses key_writer and key_reader internally.
186
+ def key_accessor *keys
187
+ key_writer *keys
188
+ key_reader *keys
189
+ end
190
+
191
+ # For each argument key, define a method <tt>key=</tt> that sets the
192
+ # corresponding field on the CouchDB document.
193
+ def key_writer *keys
194
+ keys.each do |method|
195
+ key = method.to_s
196
+ define_method "#{method}=" do |value|
197
+ self[key] = value
198
+ end
199
+ end
200
+ end
201
+
202
+ # For each argument key, define a method <tt>key</tt> that reads the
203
+ # corresponding field on the CouchDB document.
204
+ def key_reader *keys
205
+ keys.each do |method|
206
+ key = method.to_s
207
+ define_method method do
208
+ self[key]
209
+ end
210
+ end
211
+ end
212
+
213
+ def default
214
+ self.default_obj
215
+ end
216
+
217
+ def set_default hash
218
+ self.default_obj = hash
219
+ end
220
+
221
+ # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
222
+ # on the document whenever saving occurs. CouchRest uses a pretty
223
+ # decent time format by default. See Time#to_json
224
+ def timestamps!
225
+ before(:save) do
226
+ self['updated_at'] = Time.now
227
+ self['created_at'] = self['updated_at'] if new_document?
228
+ end
229
+ end
230
+
231
+ # Name a method that will be called before the document is first saved,
232
+ # which returns a string to be used for the document's <tt>_id</tt>.
233
+ # Because CouchDB enforces a constraint that each id must be unique,
234
+ # this can be used to enforce eg: uniq usernames. Note that this id
235
+ # must be globally unique across all document types which share a
236
+ # database, so if you'd like to scope uniqueness to this class, you
237
+ # should use the class name as part of the unique id.
238
+ def unique_id method = nil, &block
239
+ if method
240
+ define_method :set_unique_id do
241
+ self['_id'] ||= self.send(method)
242
+ end
243
+ elsif block
244
+ define_method :set_unique_id do
245
+ uniqid = block.call(self)
246
+ raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
247
+ self['_id'] ||= uniqid
248
+ end
249
+ end
250
+ end
251
+
252
+ # Define a CouchDB view. The name of the view will be the concatenation
253
+ # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
254
+ #
255
+ # ==== Example views:
256
+ #
257
+ # class Post
258
+ # # view with default options
259
+ # # query with Post.by_date
260
+ # view_by :date, :descending => true
261
+ #
262
+ # # view with compound sort-keys
263
+ # # query with Post.by_user_id_and_date
264
+ # view_by :user_id, :date
265
+ #
266
+ # # view with custom map/reduce functions
267
+ # # query with Post.by_tags :reduce => true
268
+ # view_by :tags,
269
+ # :map =>
270
+ # "function(doc) {
271
+ # if (doc['couchrest-type'] == 'Post' && doc.tags) {
272
+ # doc.tags.forEach(function(tag){
273
+ # emit(doc.tag, 1);
274
+ # });
275
+ # }
276
+ # }",
277
+ # :reduce =>
278
+ # "function(keys, values, rereduce) {
279
+ # return sum(values);
280
+ # }"
281
+ # end
282
+ #
283
+ # <tt>view_by :date</tt> will create a view defined by this Javascript
284
+ # function:
285
+ #
286
+ # function(doc) {
287
+ # if (doc['couchrest-type'] == 'Post' && doc.date) {
288
+ # emit(doc.date, null);
289
+ # }
290
+ # }
291
+ #
292
+ # It can be queried by calling <tt>Post.by_date</tt> which accepts all
293
+ # valid options for CouchRest::Database#view. In addition, calling with
294
+ # the <tt>:raw => true</tt> option will return the view rows
295
+ # themselves. By default <tt>Post.by_date</tt> will return the
296
+ # documents included in the generated view.
297
+ #
298
+ # CouchRest::Database#view options can be applied at view definition
299
+ # time as defaults, and they will be curried and used at view query
300
+ # time. Or they can be overridden at query time.
301
+ #
302
+ # Custom views can be queried with <tt>:reduce => true</tt> to return
303
+ # reduce results. The default for custom views is to query with
304
+ # <tt>:reduce => false</tt>.
305
+ #
306
+ # Views are generated (on a per-model basis) lazily on first-access.
307
+ # This means that if you are deploying changes to a view, the views for
308
+ # that model won't be available until generation is complete. This can
309
+ # take some time with large databases. Strategies are in the works.
310
+ #
311
+ # To understand the capabilities of this view system more compeletly,
312
+ # it is recommended that you read the RSpec file at
313
+ # <tt>spec/core/model_spec.rb</tt>.
314
+
315
+ def view_by *keys
316
+ self.design_doc ||= Design.new(default_design_doc)
317
+ opts = keys.pop if keys.last.is_a?(Hash)
318
+ opts ||= {}
319
+ ducktype = opts.delete(:ducktype)
320
+ unless ducktype || opts[:map]
321
+ opts[:guards] ||= []
322
+ opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
323
+ end
324
+ keys.push opts
325
+ self.design_doc.view_by(*keys)
326
+ self.design_doc_fresh = false
327
+ end
328
+
329
+ def method_missing m, *args
330
+ if has_view?(m)
331
+ query = args.shift || {}
332
+ view(m, query, *args)
333
+ else
334
+ super
335
+ end
336
+ end
337
+
338
+ # returns stored defaults if the there is a view named this in the design doc
339
+ def has_view?(view)
340
+ view = view.to_s
341
+ design_doc && design_doc['views'] && design_doc['views'][view]
342
+ end
343
+
344
+ # Dispatches to any named view.
345
+ def view name, query={}, &block
346
+ unless design_doc_fresh
347
+ refresh_design_doc
348
+ end
349
+ query[:raw] = true if query[:reduce]
350
+ raw = query.delete(:raw)
351
+ fetch_view_with_docs(name, query, raw, &block)
352
+ end
353
+
354
+ def all_design_doc_versions
355
+ database.documents :startkey => "_design/#{self.to_s}-",
356
+ :endkey => "_design/#{self.to_s}-\u9999"
357
+ end
358
+
359
+ # Deletes any non-current design docs that were created by this class.
360
+ # Running this when you're deployed version of your application is steadily
361
+ # and consistently using the latest code, is the way to clear out old design
362
+ # docs. Running it to early could mean that live code has to regenerate
363
+ # potentially large indexes.
364
+ def cleanup_design_docs!
365
+ ddocs = all_design_doc_versions
366
+ ddocs["rows"].each do |row|
367
+ if (row['id'] != design_doc_id)
368
+ database.delete_doc({
369
+ "_id" => row['id'],
370
+ "_rev" => row['value']['rev']
371
+ })
372
+ end
373
+ end
374
+ end
375
+
376
+ private
377
+
378
+ def fetch_view_with_docs name, opts, raw=false, &block
379
+ if raw
380
+ fetch_view name, opts, &block
381
+ else
382
+ begin
383
+ view = fetch_view name, opts.merge({:include_docs => true}), &block
384
+ view['rows'].collect{|r|new(r['doc'])} if view['rows']
385
+ rescue
386
+ # fallback for old versions of couchdb that don't
387
+ # have include_docs support
388
+ view = fetch_view name, opts, &block
389
+ view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
390
+ end
391
+ end
392
+ end
393
+
394
+ def fetch_view view_name, opts, &block
395
+ retryable = true
396
+ begin
397
+ design_doc.view(view_name, opts, &block)
398
+ # the design doc could have been deleted by a rouge process
399
+ rescue RestClient::ResourceNotFound => e
400
+ if retryable
401
+ refresh_design_doc
402
+ retryable = false
403
+ retry
404
+ else
405
+ raise e
406
+ end
407
+ end
408
+ end
409
+
410
+ def design_doc_id
411
+ "_design/#{design_doc_slug}"
412
+ end
413
+
414
+ def design_doc_slug
415
+ return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
416
+ funcs = []
417
+ design_doc['views'].each do |name, view|
418
+ funcs << "#{name}/#{view['map']}#{view['reduce']}"
419
+ end
420
+ md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
421
+ self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
422
+ end
423
+
424
+ def default_design_doc
425
+ {
426
+ "language" => "javascript",
427
+ "views" => {
428
+ 'all' => {
429
+ 'map' => "function(doc) {
430
+ if (doc['couchrest-type'] == '#{self.to_s}') {
431
+ emit(null,null);
432
+ }
433
+ }"
434
+ }
435
+ }
436
+ }
437
+ end
438
+
439
+ def refresh_design_doc
440
+ did = design_doc_id
441
+ saved = database.get(did) rescue nil
442
+ if saved
443
+ design_doc['views'].each do |name, view|
444
+ saved['views'][name] = view
445
+ end
446
+ database.save(saved)
447
+ self.design_doc = saved
448
+ else
449
+ design_doc['_id'] = did
450
+ design_doc.delete('_rev')
451
+ design_doc.database = database
452
+ design_doc.save
453
+ end
454
+ self.design_doc_fresh = true
455
+ end
456
+
457
+ end # class << self
458
+
459
+ # returns the database used by this model's class
460
+ def database
461
+ self.class.database
462
+ end
463
+
464
+ # Takes a hash as argument, and applies the values by using writer methods
465
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
466
+ # missing. In case of error, no attributes are changed.
467
+ def update_attributes_without_saving hash
468
+ hash.each do |k, v|
469
+ raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{k}" unless self.respond_to?("#{k}=")
470
+ end
471
+ hash.each do |k, v|
472
+ self.send("#{k}=",v)
473
+ end
474
+ end
475
+
476
+ # Takes a hash as argument, and applies the values by using writer methods
477
+ # for each key. Raises a NoMethodError if the corresponding methods are
478
+ # missing. In case of error, no attributes are changed.
479
+ def update_attributes hash
480
+ update_attributes_without_saving hash
481
+ save
482
+ end
483
+
484
+ # for compatibility with old-school frameworks
485
+ alias :new_record? :new_document?
486
+
487
+ # Overridden to set the unique ID.
488
+ # Returns a boolean value
489
+ def save bulk = false
490
+ set_unique_id if new_document? && self.respond_to?(:set_unique_id)
491
+ result = database.save_doc(self, bulk)
492
+ result["ok"] == true
493
+ end
494
+
495
+ # Saves the document to the db using create or update. Raises an exception
496
+ # if the document is not saved properly.
497
+ def save!
498
+ raise "#{self.inspect} failed to save" unless self.save
499
+ end
500
+
501
+ # Deletes the document from the database. Runs the :destroy callbacks.
502
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
503
+ # document to be saved to a new <tt>_id</tt>.
504
+ def destroy
505
+ result = database.delete_doc self
506
+ if result['ok']
507
+ self['_rev'] = nil
508
+ self['_id'] = nil
509
+ end
510
+ result['ok']
511
+ end
512
+
513
+ # creates a file attachment to the current doc
514
+ def create_attachment(args={})
515
+ raise ArgumentError unless args[:file] && args[:name]
516
+ return if has_attachment?(args[:name])
517
+ self['_attachments'] ||= {}
518
+ set_attachment_attr(args)
519
+ rescue ArgumentError => e
520
+ raise ArgumentError, 'You must specify :file and :name'
521
+ end
522
+
523
+ # reads the data from an attachment
524
+ def read_attachment(attachment_name)
525
+ Base64.decode64(database.fetch_attachment(self.id, attachment_name))
526
+ end
527
+
528
+ # modifies a file attachment on the current doc
529
+ def update_attachment(args={})
530
+ raise ArgumentError unless args[:file] && args[:name]
531
+ return unless has_attachment?(args[:name])
532
+ delete_attachment(args[:name])
533
+ set_attachment_attr(args)
534
+ rescue ArgumentError => e
535
+ raise ArgumentError, 'You must specify :file and :name'
536
+ end
537
+
538
+ # deletes a file attachment from the current doc
539
+ def delete_attachment(attachment_name)
540
+ return unless self['_attachments']
541
+ self['_attachments'].delete attachment_name
542
+ end
543
+
544
+ # returns true if attachment_name exists
545
+ def has_attachment?(attachment_name)
546
+ !!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
547
+ end
548
+
549
+ # returns URL to fetch the attachment from
550
+ def attachment_url(attachment_name)
551
+ return unless has_attachment?(attachment_name)
552
+ "#{database.root}/#{self.id}/#{attachment_name}"
553
+ end
554
+
555
+ private
556
+
557
+ def apply_defaults
558
+ return unless new_document?
559
+ if self.class.default
560
+ self.class.default.each do |k,v|
561
+ unless self.key?(k.to_s)
562
+ if v.class == Proc
563
+ self[k.to_s] = v.call
564
+ else
565
+ self[k.to_s] = Marshal.load(Marshal.dump(v))
566
+ end
567
+ end
568
+ end
569
+ end
570
+ end
571
+
572
+ def cast_keys
573
+ return unless self.class.casts
574
+ # TODO move the argument checking to the cast method for early crashes
575
+ self.class.casts.each do |k,v|
576
+ next unless self[k]
577
+ target = v[:as]
578
+ v[:send] || 'new'
579
+ if target.is_a?(Array)
580
+ klass = ::Extlib::Inflection.constantize(target[0])
581
+ self[k] = self[k].collect do |value|
582
+ (!v[:send] && klass == Time) ? Time.parse(value) : klass.send((v[:send] || 'new'), value)
583
+ end
584
+ else
585
+ self[k] = if (!v[:send] && target == 'Time')
586
+ Time.parse(self[k])
587
+ else
588
+ ::Extlib::Inflection.constantize(target).send((v[:send] || 'new'), self[k])
589
+ end
590
+ end
591
+ end
592
+ end
593
+
594
+ def encode_attachment(data)
595
+ Base64.encode64(data).gsub(/\r|\n/,'')
596
+ end
597
+
598
+ def get_mime_type(file)
599
+ MIME::Types.type_for(file.path).empty? ?
600
+ 'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
601
+ end
602
+
603
+ def set_attachment_attr(args)
604
+ content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
605
+ self['_attachments'][args[:name]] = {
606
+ 'content-type' => content_type,
607
+ 'data' => encode_attachment(args[:file].read)
608
+ }
609
+ end
610
+
611
+ include ::Extlib::Hook
612
+ register_instance_hooks :save, :destroy
613
+
614
+ end # class Model
615
+ end # module CouchRest
@@ -1,15 +1,14 @@
1
1
  module CouchRest
2
2
  class Response < Hash
3
- def initialize(pkeys = {})
4
- pkeys ||= {}
5
- pkeys.each do |k,v|
3
+ def initialize(keys = {})
4
+ keys.each do |k,v|
6
5
  self[k.to_s] = v
7
6
  end
8
7
  end
9
- def []=(key, value)
8
+ def []= key, value
10
9
  super(key.to_s, value)
11
10
  end
12
- def [](key)
11
+ def [] key
13
12
  super(key.to_s)
14
13
  end
15
14
  end
@@ -79,7 +79,7 @@ module CouchRest
79
79
  def next_uuid(count = @uuid_batch_count)
80
80
  @uuids ||= []
81
81
  if @uuids.empty?
82
- @uuids = CouchRest.get("#{@uri}/_uuids?count=#{count}")["uuids"]
82
+ @uuids = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
83
83
  end
84
84
  @uuids.pop
85
85
  end