samlown-couchrest 0.35

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