samflores-couchrest 0.2.1 → 0.12.3

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