samlown-couchrest 0.35

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