couchrest_extended_document 1.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +68 -0
  3. data/Rakefile +68 -0
  4. data/THANKS.md +19 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +159 -0
  7. data/lib/couchrest/casted_array.rb +25 -0
  8. data/lib/couchrest/casted_model.rb +55 -0
  9. data/lib/couchrest/extended_document.rb +323 -0
  10. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  11. data/lib/couchrest/mixins/callbacks.rb +532 -0
  12. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  13. data/lib/couchrest/mixins/collection.rb +260 -0
  14. data/lib/couchrest/mixins/design_doc.rb +127 -0
  15. data/lib/couchrest/mixins/document_queries.rb +82 -0
  16. data/lib/couchrest/mixins/extended_attachments.rb +73 -0
  17. data/lib/couchrest/mixins/properties.rb +162 -0
  18. data/lib/couchrest/mixins/validation.rb +245 -0
  19. data/lib/couchrest/mixins/views.rb +148 -0
  20. data/lib/couchrest/mixins.rb +11 -0
  21. data/lib/couchrest/property.rb +50 -0
  22. data/lib/couchrest/support/couchrest.rb +19 -0
  23. data/lib/couchrest/support/rails.rb +42 -0
  24. data/lib/couchrest/typecast.rb +175 -0
  25. data/lib/couchrest/validation/auto_validate.rb +156 -0
  26. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  27. data/lib/couchrest/validation/validation_errors.rb +125 -0
  28. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  29. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  30. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  31. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  32. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  33. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  34. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  35. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  36. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  37. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  38. data/lib/couchrest/validation.rb +245 -0
  39. data/lib/couchrest_extended_document.rb +21 -0
  40. data/spec/couchrest/attribute_protection_spec.rb +150 -0
  41. data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
  42. data/spec/couchrest/casted_model_spec.rb +406 -0
  43. data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
  44. data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
  45. data/spec/couchrest/extended_doc_spec.rb +868 -0
  46. data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
  47. data/spec/couchrest/extended_doc_view_spec.rb +529 -0
  48. data/spec/couchrest/property_spec.rb +648 -0
  49. data/spec/fixtures/attachments/README +3 -0
  50. data/spec/fixtures/attachments/couchdb.png +0 -0
  51. data/spec/fixtures/attachments/test.html +11 -0
  52. data/spec/fixtures/more/article.rb +35 -0
  53. data/spec/fixtures/more/card.rb +22 -0
  54. data/spec/fixtures/more/cat.rb +22 -0
  55. data/spec/fixtures/more/course.rb +25 -0
  56. data/spec/fixtures/more/event.rb +8 -0
  57. data/spec/fixtures/more/invoice.rb +17 -0
  58. data/spec/fixtures/more/person.rb +9 -0
  59. data/spec/fixtures/more/question.rb +6 -0
  60. data/spec/fixtures/more/service.rb +12 -0
  61. data/spec/fixtures/more/user.rb +22 -0
  62. data/spec/fixtures/views/lib.js +3 -0
  63. data/spec/fixtures/views/test_view/lib.js +3 -0
  64. data/spec/fixtures/views/test_view/only-map.js +4 -0
  65. data/spec/fixtures/views/test_view/test-map.js +3 -0
  66. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  67. data/spec/spec.opts +5 -0
  68. data/spec/spec_helper.rb +49 -0
  69. data/utils/remap.rb +27 -0
  70. data/utils/subset.rb +30 -0
  71. metadata +200 -0
@@ -0,0 +1,532 @@
1
+ # Copyright (c) 2006-2009 David Heinemeier Hansson
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+ # Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
23
+ # http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
24
+ # http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
25
+
26
+ module CouchRest
27
+ module Mixins
28
+ # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
29
+ # before or after an alteration of the object state.
30
+ #
31
+ # Mixing in this module allows you to define callbacks in your class.
32
+ #
33
+ # Example:
34
+ # class Storage
35
+ # include ActiveSupport::Callbacks
36
+ #
37
+ # define_callbacks :save
38
+ # end
39
+ #
40
+ # class ConfigStorage < Storage
41
+ # save_callback :before, :saving_message
42
+ # def saving_message
43
+ # puts "saving..."
44
+ # end
45
+ #
46
+ # save_callback :after do |object|
47
+ # puts "saved"
48
+ # end
49
+ #
50
+ # def save
51
+ # _run_save_callbacks do
52
+ # puts "- save"
53
+ # end
54
+ # end
55
+ # end
56
+ #
57
+ # config = ConfigStorage.new
58
+ # config.save
59
+ #
60
+ # Output:
61
+ # saving...
62
+ # - save
63
+ # saved
64
+ #
65
+ # Callbacks from parent classes are inherited.
66
+ #
67
+ # Example:
68
+ # class Storage
69
+ # include ActiveSupport::Callbacks
70
+ #
71
+ # define_callbacks :save
72
+ #
73
+ # save_callback :before, :prepare
74
+ # def prepare
75
+ # puts "preparing save"
76
+ # end
77
+ # end
78
+ #
79
+ # class ConfigStorage < Storage
80
+ # save_callback :before, :saving_message
81
+ # def saving_message
82
+ # puts "saving..."
83
+ # end
84
+ #
85
+ # save_callback :after do |object|
86
+ # puts "saved"
87
+ # end
88
+ #
89
+ # def save
90
+ # _run_save_callbacks do
91
+ # puts "- save"
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # config = ConfigStorage.new
97
+ # config.save
98
+ #
99
+ # Output:
100
+ # preparing save
101
+ # saving...
102
+ # - save
103
+ # saved
104
+ module Callbacks
105
+ def self.included(klass)
106
+ klass.extend ClassMethods
107
+ end
108
+
109
+ def run_callbacks(kind, options = {}, &blk)
110
+ send("_run_#{kind}_callbacks", &blk)
111
+ end
112
+
113
+ class Callback
114
+ @@_callback_sequence = 0
115
+
116
+ attr_accessor :filter, :kind, :name, :options, :per_key, :klass
117
+ def initialize(filter, kind, options, klass)
118
+ @kind, @klass = kind, klass
119
+
120
+ normalize_options!(options)
121
+
122
+ @per_key = options.delete(:per_key)
123
+ @raw_filter, @options = filter, options
124
+ @filter = _compile_filter(filter)
125
+ @compiled_options = _compile_options(options)
126
+ @callback_id = next_id
127
+
128
+ _compile_per_key_options
129
+ end
130
+
131
+ def clone(klass)
132
+ obj = super()
133
+ obj.klass = klass
134
+ obj.per_key = @per_key.dup
135
+ obj.options = @options.dup
136
+ obj.per_key[:if] = @per_key[:if].dup
137
+ obj.per_key[:unless] = @per_key[:unless].dup
138
+ obj.options[:if] = @options[:if].dup
139
+ obj.options[:unless] = @options[:unless].dup
140
+ obj
141
+ end
142
+
143
+ def normalize_options!(options)
144
+ options[:if] = Array.wrap(options[:if])
145
+ options[:unless] = Array.wrap(options[:unless])
146
+
147
+ options[:per_key] ||= {}
148
+ options[:per_key][:if] = Array.wrap(options[:per_key][:if])
149
+ options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
150
+ end
151
+
152
+ def next_id
153
+ @@_callback_sequence += 1
154
+ end
155
+
156
+ def matches?(_kind, _filter)
157
+ @kind == _kind &&
158
+ @filter == _filter
159
+ end
160
+
161
+ def _update_filter(filter_options, new_options)
162
+ filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
163
+ filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
164
+ end
165
+
166
+ def recompile!(_options, _per_key)
167
+ _update_filter(self.options, _options)
168
+ _update_filter(self.per_key, _per_key)
169
+
170
+ @callback_id = next_id
171
+ @filter = _compile_filter(@raw_filter)
172
+ @compiled_options = _compile_options(@options)
173
+ _compile_per_key_options
174
+ end
175
+
176
+ def _compile_per_key_options
177
+ key_options = _compile_options(@per_key)
178
+
179
+ @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
180
+ def _one_time_conditions_valid_#{@callback_id}?
181
+ true #{key_options[0]}
182
+ end
183
+ RUBY_EVAL
184
+ end
185
+
186
+ # This will supply contents for before and around filters, and no
187
+ # contents for after filters (for the forward pass).
188
+ def start(key = nil, options = {})
189
+ object, terminator = (options || {}).values_at(:object, :terminator)
190
+
191
+ return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
192
+
193
+ terminator ||= false
194
+
195
+ # options[0] is the compiled form of supplied conditions
196
+ # options[1] is the "end" for the conditional
197
+
198
+ if @kind == :before || @kind == :around
199
+ if @kind == :before
200
+ # if condition # before_save :filter_name, :if => :condition
201
+ # filter_name
202
+ # end
203
+ filter = <<-RUBY_EVAL
204
+ unless halted
205
+ result = #{@filter}
206
+ halted = (#{terminator})
207
+ end
208
+ RUBY_EVAL
209
+
210
+ [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
211
+ else
212
+ # Compile around filters with conditions into proxy methods
213
+ # that contain the conditions.
214
+ #
215
+ # For `around_save :filter_name, :if => :condition':
216
+ #
217
+ # def _conditional_callback_save_17
218
+ # if condition
219
+ # filter_name do
220
+ # yield self
221
+ # end
222
+ # else
223
+ # yield self
224
+ # end
225
+ # end
226
+
227
+ name = "_conditional_callback_#{@kind}_#{next_id}"
228
+ txt, line = <<-RUBY_EVAL, __LINE__ + 1
229
+ def #{name}(halted)
230
+ #{@compiled_options[0] || "if true"} && !halted
231
+ #{@filter} do
232
+ yield self
233
+ end
234
+ else
235
+ yield self
236
+ end
237
+ end
238
+ RUBY_EVAL
239
+ @klass.class_eval(txt, __FILE__, line)
240
+ "#{name}(halted) do"
241
+ end
242
+ end
243
+ end
244
+
245
+ # This will supply contents for around and after filters, but not
246
+ # before filters (for the backward pass).
247
+ def end(key = nil, options = {})
248
+ object = (options || {})[:object]
249
+
250
+ return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
251
+
252
+ if @kind == :around || @kind == :after
253
+ # if condition # after_save :filter_name, :if => :condition
254
+ # filter_name
255
+ # end
256
+ if @kind == :after
257
+ [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
258
+ else
259
+ "end"
260
+ end
261
+ end
262
+ end
263
+
264
+ private
265
+ # Options support the same options as filters themselves (and support
266
+ # symbols, string, procs, and objects), so compile a conditional
267
+ # expression based on the options
268
+ def _compile_options(options)
269
+ return [] if options[:if].empty? && options[:unless].empty?
270
+
271
+ conditions = []
272
+
273
+ unless options[:if].empty?
274
+ conditions << Array.wrap(_compile_filter(options[:if]))
275
+ end
276
+
277
+ unless options[:unless].empty?
278
+ conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
279
+ end
280
+
281
+ ["if #{conditions.flatten.join(" && ")}", "end"]
282
+ end
283
+
284
+ # Filters support:
285
+ # Arrays:: Used in conditions. This is used to specify
286
+ # multiple conditions. Used internally to
287
+ # merge conditions from skip_* filters
288
+ # Symbols:: A method to call
289
+ # Strings:: Some content to evaluate
290
+ # Procs:: A proc to call with the object
291
+ # Objects:: An object with a before_foo method on it to call
292
+ #
293
+ # All of these objects are compiled into methods and handled
294
+ # the same after this point:
295
+ # Arrays:: Merged together into a single filter
296
+ # Symbols:: Already methods
297
+ # Strings:: class_eval'ed into methods
298
+ # Procs:: define_method'ed into methods
299
+ # Objects::
300
+ # a method is created that calls the before_foo method
301
+ # on the object.
302
+ def _compile_filter(filter)
303
+ method_name = "_callback_#{@kind}_#{next_id}"
304
+ case filter
305
+ when Array
306
+ filter.map {|f| _compile_filter(f)}
307
+ when Symbol
308
+ filter
309
+ when String
310
+ "(#{filter})"
311
+ when Proc
312
+ @klass.send(:define_method, method_name, &filter)
313
+ return method_name if filter.arity == 0
314
+
315
+ method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
316
+ else
317
+ @klass.send(:define_method, "#{method_name}_object") { filter }
318
+
319
+ _normalize_legacy_filter(kind, filter)
320
+
321
+ @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
322
+ def #{method_name}(&blk)
323
+ #{method_name}_object.send(:#{kind}, self, &blk)
324
+ end
325
+ RUBY_EVAL
326
+
327
+ method_name
328
+ end
329
+ end
330
+
331
+ def _normalize_legacy_filter(kind, filter)
332
+ if !filter.respond_to?(kind) && filter.respond_to?(:filter)
333
+ filter.class_eval(
334
+ "def #{kind}(context, &block) filter(context, &block) end",
335
+ __FILE__, __LINE__ - 1)
336
+ elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
337
+ def filter.around(context)
338
+ should_continue = before(context)
339
+ yield if should_continue
340
+ after(context)
341
+ end
342
+ end
343
+ end
344
+
345
+ end
346
+
347
+ # An Array with a compile method
348
+ class CallbackChain < Array
349
+ def initialize(symbol)
350
+ @symbol = symbol
351
+ end
352
+
353
+ def compile(key = nil, options = {})
354
+ method = []
355
+ method << "halted = false"
356
+ each do |callback|
357
+ method << callback.start(key, options)
358
+ end
359
+ method << "yield self if block_given? && !halted"
360
+ reverse_each do |callback|
361
+ method << callback.end(key, options)
362
+ end
363
+ method.compact.join("\n")
364
+ end
365
+
366
+ def clone(klass)
367
+ chain = CallbackChain.new(@symbol)
368
+ chain.push(*map {|c| c.clone(klass)})
369
+ end
370
+ end
371
+
372
+ module ClassMethods
373
+ #CHAINS = {:before => :before, :around => :before, :after => :after}
374
+
375
+ # Make the _run_save_callbacks method. The generated method takes
376
+ # a block that it'll yield to. It'll call the before and around filters
377
+ # in order, yield the block, and then run the after filters.
378
+ #
379
+ # _run_save_callbacks do
380
+ # save
381
+ # end
382
+ #
383
+ # The _run_save_callbacks method can optionally take a key, which
384
+ # will be used to compile an optimized callback method for each
385
+ # key. See #define_callbacks for more information.
386
+ def _define_runner(symbol)
387
+ body = send("_#{symbol}_callback").
388
+ compile(nil, :terminator => send("_#{symbol}_terminator"))
389
+
390
+ body, line = <<-RUBY_EVAL, __LINE__ + 1
391
+ def _run_#{symbol}_callbacks(key = nil, &blk)
392
+ if key
393
+ name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
394
+
395
+ unless respond_to?(name)
396
+ self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
397
+ end
398
+
399
+ send(name, &blk)
400
+ else
401
+ #{body}
402
+ end
403
+ end
404
+ RUBY_EVAL
405
+
406
+ undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
407
+ class_eval body, __FILE__, line
408
+ end
409
+
410
+ # This is called the first time a callback is called with a particular
411
+ # key. It creates a new callback method for the key, calculating
412
+ # which callbacks can be omitted because of per_key conditions.
413
+ def _create_keyed_callback(name, kind, obj, &blk)
414
+ @_keyed_callbacks ||= {}
415
+ @_keyed_callbacks[name] ||= begin
416
+ str = send("_#{kind}_callback").
417
+ compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
418
+
419
+ class_eval "def #{name}() #{str} end", __FILE__, __LINE__
420
+
421
+ true
422
+ end
423
+ end
424
+
425
+ # Define callbacks.
426
+ #
427
+ # Creates a <name>_callback method that you can use to add callbacks.
428
+ #
429
+ # Syntax:
430
+ # save_callback :before, :before_meth
431
+ # save_callback :after, :after_meth, :if => :condition
432
+ # save_callback :around {|r| stuff; yield; stuff }
433
+ #
434
+ # The <name>_callback method also updates the _run_<name>_callbacks
435
+ # method, which is the public API to run the callbacks.
436
+ #
437
+ # Also creates a skip_<name>_callback method that you can use to skip
438
+ # callbacks.
439
+ #
440
+ # When creating or skipping callbacks, you can specify conditions that
441
+ # are always the same for a given key. For instance, in ActionPack,
442
+ # we convert :only and :except conditions into per-key conditions.
443
+ #
444
+ # before_filter :authenticate, :except => "index"
445
+ # becomes
446
+ # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
447
+ #
448
+ # Per-Key conditions are evaluated only once per use of a given key.
449
+ # In the case of the above example, you would do:
450
+ #
451
+ # run_dispatch_callbacks(action_name) { ... dispatch stuff ... }
452
+ #
453
+ # In that case, each action_name would get its own compiled callback
454
+ # method that took into consideration the per_key conditions. This
455
+ # is a speed improvement for ActionPack.
456
+ def _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
457
+ type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
458
+ options = filters.last.is_a?(Hash) ? filters.pop : {}
459
+ filters.unshift(block) if block
460
+
461
+ callbacks = send("_#{name}_callback")
462
+ yield callbacks, type, filters, options if block_given?
463
+
464
+ _define_runner(name)
465
+ end
466
+
467
+ alias_method :_reset_callbacks, :_update_callbacks
468
+
469
+ def set_callback(name, *filters, &block)
470
+ _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
471
+ filters.map! do |filter|
472
+ # overrides parent class
473
+ callbacks.delete_if {|c| c.matches?(type, filter) }
474
+ Callback.new(filter, type, options.dup, self)
475
+ end
476
+
477
+ options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
478
+ end
479
+ end
480
+
481
+ def skip_callback(name, *filters, &block)
482
+ _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
483
+ filters.each do |filter|
484
+ callbacks = send("_#{name}_callback=", callbacks.clone(self))
485
+
486
+ filter = callbacks.find {|c| c.matches?(type, filter) }
487
+
488
+ if filter && options.any?
489
+ filter.recompile!(options, options[:per_key] || {})
490
+ else
491
+ callbacks.delete(filter)
492
+ end
493
+ end
494
+ end
495
+ end
496
+
497
+ def define_callbacks(*symbols)
498
+ terminator = symbols.pop if symbols.last.is_a?(String)
499
+ symbols.each do |symbol|
500
+ extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
501
+
502
+ extlib_inheritable_accessor("_#{symbol}_callback") do
503
+ CallbackChain.new(symbol)
504
+ end
505
+
506
+ _define_runner(symbol)
507
+
508
+ # Define more convenient callback methods
509
+ # set_callback(:save, :before) becomes before_save
510
+ [:before, :after, :around].each do |filter|
511
+ self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
512
+ def self.#{filter}_#{symbol}(*symbols, &blk)
513
+ _alias_callbacks(symbols, blk) do |callback, options|
514
+ set_callback(:#{symbol}, :#{filter}, callback, options)
515
+ end
516
+ end
517
+ RUBY_EVAL
518
+ end
519
+ end
520
+ end
521
+
522
+ def _alias_callbacks(callbacks, block)
523
+ options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
524
+ callbacks.push(block) if block
525
+ callbacks.each do |callback|
526
+ yield callback, options
527
+ end
528
+ end
529
+ end
530
+ end
531
+ end
532
+ end
@@ -0,0 +1,120 @@
1
+ module CouchRest
2
+ module Mixins
3
+ module ClassProxy
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ # Return a proxy object which represents a model class on a
12
+ # chosen database instance. This allows you to DRY operations
13
+ # where a database is chosen dynamically.
14
+ #
15
+ # ==== Example:
16
+ #
17
+ # db = CouchRest::Database.new(...)
18
+ # articles = Article.on(db)
19
+ #
20
+ # articles.all { ... }
21
+ # articles.by_title { ... }
22
+ #
23
+ # u = articles.get("someid")
24
+ #
25
+ # u = articles.new(:title => "I like plankton")
26
+ # u.save # saved on the correct database
27
+
28
+ def on(database)
29
+ Proxy.new(self, database)
30
+ end
31
+ end
32
+
33
+ class Proxy #:nodoc:
34
+ def initialize(klass, database)
35
+ @klass = klass
36
+ @database = database
37
+ end
38
+
39
+ # ExtendedDocument
40
+
41
+ def new(*args)
42
+ doc = @klass.new(*args)
43
+ doc.database = @database
44
+ doc
45
+ end
46
+
47
+ def method_missing(m, *args, &block)
48
+ if has_view?(m)
49
+ query = args.shift || {}
50
+ view(m, query, *args, &block)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ # Mixins::DocumentQueries
57
+
58
+ def all(opts = {}, &block)
59
+ docs = @klass.all({:database => @database}.merge(opts), &block)
60
+ docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
61
+ docs
62
+ end
63
+
64
+ def count(opts = {}, &block)
65
+ @klass.all({:database => @database, :raw => true, :limit => 0}.merge(opts), &block)['total_rows']
66
+ end
67
+
68
+ def first(opts = {})
69
+ doc = @klass.first({:database => @database}.merge(opts))
70
+ doc.database = @database if doc && doc.respond_to?(:database)
71
+ doc
72
+ end
73
+
74
+ def get(id)
75
+ doc = @klass.get(id, @database)
76
+ doc.database = @database if doc && doc.respond_to?(:database)
77
+ doc
78
+ end
79
+
80
+ # Mixins::Views
81
+
82
+ def has_view?(view)
83
+ @klass.has_view?(view)
84
+ end
85
+
86
+ def view(name, query={}, &block)
87
+ docs = @klass.view(name, {:database => @database}.merge(query), &block)
88
+ docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
89
+ docs
90
+ end
91
+
92
+
93
+ # Mixins::DesignDoc
94
+
95
+ def design_doc
96
+ @klass.design_doc
97
+ end
98
+
99
+ def refresh_design_doc
100
+ @klass.refresh_design_doc(@database)
101
+ end
102
+
103
+ def save_design_doc
104
+ @klass.save_design_doc(@database)
105
+ end
106
+
107
+ # DEPRICATED
108
+ def all_design_doc_versions
109
+ @klass.all_design_doc_versions(@database)
110
+ end
111
+
112
+ def stored_design_doc
113
+ @klass.stored_design_doc(@database)
114
+ end
115
+ alias :model_design_doc :stored_design_doc
116
+
117
+ end
118
+ end
119
+ end
120
+ end