samflores-couchrest 0.2.1 → 0.12.3

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