jchris-couchrest 0.16 → 0.17.0
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.
- data/lib/couchrest/core/database.rb +13 -8
- data/lib/couchrest/core/response.rb +5 -4
- data/lib/couchrest/helper/upgrade.rb +51 -0
- data/lib/couchrest/mixins/callbacks.rb +73 -32
- data/lib/couchrest/mixins/design_doc.rb +1 -0
- data/lib/couchrest/mixins/properties.rb +6 -1
- data/lib/couchrest/mixins/views.rb +1 -1
- data/lib/couchrest/monkeypatches.rb +12 -6
- data/lib/couchrest/more/extended_document.rb +6 -4
- data/lib/couchrest/support/class.rb +148 -132
- data/lib/couchrest.rb +32 -6
- data/spec/couchrest/core/database_spec.rb +6 -21
- data/spec/couchrest/core/document_spec.rb +2 -1
- data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
- data/spec/couchrest/more/casted_model_spec.rb +2 -1
- data/spec/couchrest/more/extended_doc_attachment_spec.rb +2 -1
- data/spec/couchrest/more/extended_doc_spec.rb +1 -1
- data/spec/couchrest/more/extended_doc_view_spec.rb +16 -13
- data/spec/couchrest/more/property_spec.rb +2 -1
- data/spec/couchrest/support/class_spec.rb +59 -0
- metadata +5 -21
@@ -20,7 +20,7 @@ module CouchRest
|
|
20
20
|
@uri = @root = "#{host}/#{name}"
|
21
21
|
@streamer = Streamer.new(self)
|
22
22
|
@bulk_save_cache = []
|
23
|
-
@bulk_save_cache_limit =
|
23
|
+
@bulk_save_cache_limit = 500 # must be smaller than the uuid count
|
24
24
|
end
|
25
25
|
|
26
26
|
# returns the database's uri
|
@@ -61,12 +61,15 @@ module CouchRest
|
|
61
61
|
# paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
|
62
62
|
def view(name, params = {}, &block)
|
63
63
|
keys = params.delete(:keys)
|
64
|
-
|
64
|
+
name = name.split('/') # I think this will always be length == 2, but maybe not...
|
65
|
+
dname = name.shift
|
66
|
+
vname = name.join('/')
|
67
|
+
url = CouchRest.paramify_url "#{@uri}/_design/#{dname}/_view/#{vname}", params
|
65
68
|
if keys
|
66
69
|
CouchRest.post(url, {:keys => keys})
|
67
70
|
else
|
68
71
|
if block_given?
|
69
|
-
@streamer.view(
|
72
|
+
@streamer.view("_design/#{dname}/_view/#{vname}", params, &block)
|
70
73
|
else
|
71
74
|
CouchRest.get url
|
72
75
|
end
|
@@ -74,13 +77,15 @@ module CouchRest
|
|
74
77
|
end
|
75
78
|
|
76
79
|
# GET a document from CouchDB, by id. Returns a Ruby Hash.
|
77
|
-
def get(id)
|
80
|
+
def get(id, params = {})
|
78
81
|
slug = escape_docid(id)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
url = CouchRest.paramify_url("#{@uri}/#{slug}", params)
|
83
|
+
result = CouchRest.get(url)
|
84
|
+
return result unless result.is_a?(Hash)
|
85
|
+
doc = if /^_design/ =~ result["_id"]
|
86
|
+
Design.new(result)
|
82
87
|
else
|
83
|
-
Document.new(
|
88
|
+
Document.new(result)
|
84
89
|
end
|
85
90
|
doc.database = self
|
86
91
|
doc
|
@@ -1,14 +1,15 @@
|
|
1
1
|
module CouchRest
|
2
2
|
class Response < Hash
|
3
|
-
def initialize(
|
4
|
-
|
3
|
+
def initialize(pkeys = {})
|
4
|
+
pkeys ||= {}
|
5
|
+
pkeys.each do |k,v|
|
5
6
|
self[k.to_s] = v
|
6
7
|
end
|
7
8
|
end
|
8
|
-
def []=
|
9
|
+
def []=(key, value)
|
9
10
|
super(key.to_s, value)
|
10
11
|
end
|
11
|
-
def []
|
12
|
+
def [](key)
|
12
13
|
super(key.to_s)
|
13
14
|
end
|
14
15
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Upgrade
|
3
|
+
attr_accessor :olddb, :newdb, :dbname
|
4
|
+
def initialize dbname, old_couch, new_couch
|
5
|
+
@dbname = dbname
|
6
|
+
@olddb = old_couch.database dbname
|
7
|
+
@newdb = new_couch.database!(dbname)
|
8
|
+
@bulk_docs = []
|
9
|
+
end
|
10
|
+
def clone!
|
11
|
+
puts "#{dbname} - #{olddb.info['doc_count']} docs"
|
12
|
+
streamer = CouchRest::Streamer.new(olddb)
|
13
|
+
streamer.view("_all_docs_by_seq") do |row|
|
14
|
+
load_row_docs(row) if row
|
15
|
+
maybe_flush_bulks
|
16
|
+
end
|
17
|
+
flush_bulks!
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def maybe_flush_bulks
|
23
|
+
flush_bulks! if (@bulk_docs.length > 99)
|
24
|
+
end
|
25
|
+
|
26
|
+
def flush_bulks!
|
27
|
+
url = CouchRest.paramify_url "#{@newdb.uri}/_bulk_docs", {:all_or_nothing => true}
|
28
|
+
puts "posting #{@bulk_docs.length} bulk docs to #{url}"
|
29
|
+
begin
|
30
|
+
CouchRest.post url, {:docs => @bulk_docs}
|
31
|
+
@bulk_docs = []
|
32
|
+
rescue Exception => e
|
33
|
+
puts e.response
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_row_docs(row)
|
39
|
+
results = @olddb.get(row["id"], {:open_revs => "all", :attachments => true})
|
40
|
+
results.select{|r|r["ok"]}.each do |r|
|
41
|
+
doc = r["ok"]
|
42
|
+
if /^_/.match(doc["_id"]) && !/^_design/.match(doc["_id"])
|
43
|
+
puts "invalid docid #{doc["_id"]} -- trimming"
|
44
|
+
doc["_id"] = doc["_id"].sub('_','')
|
45
|
+
end
|
46
|
+
doc.delete('_rev')
|
47
|
+
@bulk_docs << doc
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
2
2
|
|
3
3
|
# Extracted from ActiveSupport::Callbacks written by Yehuda Katz
|
4
|
+
# http://github.com/wycats/rails/raw/abstract_controller/activesupport/lib/active_support/new_callbacks.rb
|
4
5
|
# http://github.com/wycats/rails/raw/18b405f154868204a8f332888871041a7bad95e1/activesupport/lib/active_support/callbacks.rb
|
5
6
|
|
6
7
|
module CouchRest
|
@@ -11,7 +12,7 @@ module CouchRest
|
|
11
12
|
#
|
12
13
|
# Example:
|
13
14
|
# class Storage
|
14
|
-
# include
|
15
|
+
# include ActiveSupport::Callbacks
|
15
16
|
#
|
16
17
|
# define_callbacks :save
|
17
18
|
# end
|
@@ -45,7 +46,7 @@ module CouchRest
|
|
45
46
|
#
|
46
47
|
# Example:
|
47
48
|
# class Storage
|
48
|
-
# include
|
49
|
+
# include ActiveSupport::Callbacks
|
49
50
|
#
|
50
51
|
# define_callbacks :save
|
51
52
|
#
|
@@ -85,8 +86,8 @@ module CouchRest
|
|
85
86
|
klass.extend ClassMethods
|
86
87
|
end
|
87
88
|
|
88
|
-
def run_callbacks(kind, options = {})
|
89
|
-
send("_run_#{kind}_callbacks")
|
89
|
+
def run_callbacks(kind, options = {}, &blk)
|
90
|
+
send("_run_#{kind}_callbacks", &blk)
|
90
91
|
end
|
91
92
|
|
92
93
|
class Callback
|
@@ -166,9 +167,13 @@ module CouchRest
|
|
166
167
|
|
167
168
|
# This will supply contents for before and around filters, and no
|
168
169
|
# contents for after filters (for the forward pass).
|
169
|
-
def start(key = nil,
|
170
|
+
def start(key = nil, options = {})
|
171
|
+
object, terminator = (options || {}).values_at(:object, :terminator)
|
172
|
+
|
170
173
|
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
171
174
|
|
175
|
+
terminator ||= false
|
176
|
+
|
172
177
|
# options[0] is the compiled form of supplied conditions
|
173
178
|
# options[1] is the "end" for the conditional
|
174
179
|
|
@@ -177,8 +182,14 @@ module CouchRest
|
|
177
182
|
# if condition # before_save :filter_name, :if => :condition
|
178
183
|
# filter_name
|
179
184
|
# end
|
180
|
-
|
181
|
-
|
185
|
+
filter = <<-RUBY_EVAL
|
186
|
+
unless halted
|
187
|
+
result = #{@filter}
|
188
|
+
halted ||= (#{terminator})
|
189
|
+
end
|
190
|
+
RUBY_EVAL
|
191
|
+
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
|
192
|
+
else
|
182
193
|
# Compile around filters with conditions into proxy methods
|
183
194
|
# that contain the conditions.
|
184
195
|
#
|
@@ -196,8 +207,8 @@ module CouchRest
|
|
196
207
|
|
197
208
|
name = "_conditional_callback_#{@kind}_#{next_id}"
|
198
209
|
txt = <<-RUBY_EVAL
|
199
|
-
def #{name}
|
200
|
-
#{@compiled_options[0]}
|
210
|
+
def #{name}(halted)
|
211
|
+
#{@compiled_options[0] || "if true"} && !halted
|
201
212
|
#{@filter} do
|
202
213
|
yield self
|
203
214
|
end
|
@@ -207,16 +218,16 @@ module CouchRest
|
|
207
218
|
end
|
208
219
|
RUBY_EVAL
|
209
220
|
@klass.class_eval(txt)
|
210
|
-
"#{name} do"
|
211
|
-
else
|
212
|
-
"#{@filter} do"
|
221
|
+
"#{name}(halted) do"
|
213
222
|
end
|
214
223
|
end
|
215
224
|
end
|
216
225
|
|
217
226
|
# This will supply contents for around and after filters, but not
|
218
227
|
# before filters (for the backward pass).
|
219
|
-
def end(key = nil,
|
228
|
+
def end(key = nil, options = {})
|
229
|
+
object = (options || {})[:object]
|
230
|
+
|
220
231
|
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
221
232
|
|
222
233
|
if @kind == :around || @kind == :after
|
@@ -299,7 +310,7 @@ module CouchRest
|
|
299
310
|
# This method_missing is supplied to catch callbacks with keys and create
|
300
311
|
# the appropriate callback for future use.
|
301
312
|
def method_missing(meth, *args, &blk)
|
302
|
-
if meth.to_s =~ /
|
313
|
+
if meth.to_s =~ /_run__([\w:]+)__(\w+)__(\w+)__callbacks/
|
303
314
|
return self.class._create_and_run_keyed_callback($1, $2.to_sym, $3.to_sym, self, &blk)
|
304
315
|
end
|
305
316
|
super
|
@@ -307,20 +318,26 @@ module CouchRest
|
|
307
318
|
|
308
319
|
# An Array with a compile method
|
309
320
|
class CallbackChain < Array
|
310
|
-
def
|
321
|
+
def initialize(symbol)
|
322
|
+
@symbol = symbol
|
323
|
+
end
|
324
|
+
|
325
|
+
def compile(key = nil, options = {})
|
311
326
|
method = []
|
327
|
+
method << "halted = false"
|
312
328
|
each do |callback|
|
313
|
-
method << callback.start(key,
|
329
|
+
method << callback.start(key, options)
|
314
330
|
end
|
315
|
-
method << "yield self"
|
331
|
+
method << "yield self if block_given?"
|
316
332
|
reverse_each do |callback|
|
317
|
-
method << callback.end(key,
|
333
|
+
method << callback.end(key, options)
|
318
334
|
end
|
319
335
|
method.compact.join("\n")
|
320
336
|
end
|
321
337
|
|
322
338
|
def clone(klass)
|
323
|
-
CallbackChain.new(
|
339
|
+
chain = CallbackChain.new(@symbol)
|
340
|
+
chain.push(*map {|c| c.clone(klass)})
|
324
341
|
end
|
325
342
|
end
|
326
343
|
|
@@ -338,16 +355,18 @@ module CouchRest
|
|
338
355
|
# The _run_save_callbacks method can optionally take a key, which
|
339
356
|
# will be used to compile an optimized callback method for each
|
340
357
|
# key. See #define_callbacks for more information.
|
341
|
-
def _define_runner(symbol, str, options)
|
342
|
-
|
358
|
+
def _define_runner(symbol, str, options)
|
359
|
+
str = <<-RUBY_EVAL
|
343
360
|
def _run_#{symbol}_callbacks(key = nil)
|
344
361
|
if key
|
345
|
-
send("
|
362
|
+
send("_run__\#{self.class.name.split("::").last}__#{symbol}__\#{key}__callbacks") { yield if block_given? }
|
346
363
|
else
|
347
364
|
#{str}
|
348
365
|
end
|
349
366
|
end
|
350
367
|
RUBY_EVAL
|
368
|
+
|
369
|
+
class_eval str, __FILE__, __LINE__ + 1
|
351
370
|
|
352
371
|
before_name, around_name, after_name =
|
353
372
|
options.values_at(:before, :after, :around)
|
@@ -359,15 +378,18 @@ module CouchRest
|
|
359
378
|
def _create_and_run_keyed_callback(klass, kind, key, obj, &blk)
|
360
379
|
@_keyed_callbacks ||= {}
|
361
380
|
@_keyed_callbacks[[kind, key]] ||= begin
|
362
|
-
str = self.send("_#{kind}_callbacks").compile(key, obj)
|
381
|
+
str = self.send("_#{kind}_callbacks").compile(key, :object => obj, :terminator => self.send("_#{kind}_terminator"))
|
382
|
+
|
363
383
|
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
364
|
-
def
|
384
|
+
def _run__#{klass.split("::").last}__#{kind}__#{key}__callbacks
|
365
385
|
#{str}
|
366
386
|
end
|
367
387
|
RUBY_EVAL
|
388
|
+
|
368
389
|
true
|
369
390
|
end
|
370
|
-
|
391
|
+
|
392
|
+
obj.send("_run__#{klass.split("::").last}__#{kind}__#{key}__callbacks", &blk)
|
371
393
|
end
|
372
394
|
|
373
395
|
# Define callbacks.
|
@@ -402,20 +424,32 @@ module CouchRest
|
|
402
424
|
# method that took into consideration the per_key conditions. This
|
403
425
|
# is a speed improvement for ActionPack.
|
404
426
|
def define_callbacks(*symbols)
|
427
|
+
terminator = symbols.pop if symbols.last.is_a?(String)
|
405
428
|
symbols.each do |symbol|
|
429
|
+
self.class_inheritable_accessor("_#{symbol}_terminator")
|
430
|
+
self.send("_#{symbol}_terminator=", terminator)
|
406
431
|
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
407
432
|
class_inheritable_accessor :_#{symbol}_callbacks
|
408
|
-
self._#{symbol}_callbacks = CallbackChain.new
|
433
|
+
self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
|
409
434
|
|
410
|
-
def self.#{symbol}_callback(
|
435
|
+
def self.#{symbol}_callback(*filters, &blk)
|
436
|
+
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
|
411
437
|
options = filters.last.is_a?(Hash) ? filters.pop : {}
|
412
438
|
filters.unshift(blk) if block_given?
|
413
|
-
|
439
|
+
|
440
|
+
filters.map! do |filter|
|
441
|
+
# overrides parent class
|
442
|
+
self._#{symbol}_callbacks.delete_if {|c| c.matches?(type, :#{symbol}, filter)}
|
443
|
+
Callback.new(filter, type, options.dup, self, :#{symbol})
|
444
|
+
end
|
414
445
|
self._#{symbol}_callbacks.push(*filters)
|
415
|
-
_define_runner(:#{symbol},
|
446
|
+
_define_runner(:#{symbol},
|
447
|
+
self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator),
|
448
|
+
options)
|
416
449
|
end
|
417
450
|
|
418
|
-
def self.skip_#{symbol}_callback(
|
451
|
+
def self.skip_#{symbol}_callback(*filters, &blk)
|
452
|
+
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
|
419
453
|
options = filters.last.is_a?(Hash) ? filters.pop : {}
|
420
454
|
filters.unshift(blk) if block_given?
|
421
455
|
filters.each do |filter|
|
@@ -428,15 +462,22 @@ module CouchRest
|
|
428
462
|
else
|
429
463
|
self._#{symbol}_callbacks.delete(filter)
|
430
464
|
end
|
431
|
-
_define_runner(:#{symbol},
|
465
|
+
_define_runner(:#{symbol},
|
466
|
+
self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator),
|
467
|
+
options)
|
432
468
|
end
|
433
469
|
|
434
470
|
end
|
435
471
|
|
472
|
+
def self.reset_#{symbol}_callbacks
|
473
|
+
self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
|
474
|
+
_define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, {})
|
475
|
+
end
|
476
|
+
|
436
477
|
self.#{symbol}_callback(:before)
|
437
478
|
RUBY_EVAL
|
438
479
|
end
|
439
480
|
end
|
440
481
|
end
|
441
482
|
end
|
442
|
-
end
|
483
|
+
end
|
@@ -16,6 +16,7 @@ module CouchRest
|
|
16
16
|
def design_doc_slug
|
17
17
|
return design_doc_slug_cache if (design_doc_slug_cache && design_doc_fresh)
|
18
18
|
funcs = []
|
19
|
+
design_doc ||= Design.new(default_design_doc)
|
19
20
|
design_doc['views'].each do |name, view|
|
20
21
|
funcs << "#{name}/#{view['map']}#{view['reduce']}"
|
21
22
|
end
|
@@ -55,9 +55,14 @@ module CouchRest
|
|
55
55
|
else
|
56
56
|
# Let people use :send as a Time parse arg
|
57
57
|
klass = ::CouchRest.constantize(target)
|
58
|
+
# I'm not convince we should or should not create a new instance if we are casting a doc/extended doc without default value and nothing was passed
|
59
|
+
# unless (property.casted &&
|
60
|
+
# (klass.superclass == CouchRest::ExtendedDocument || klass.superclass == CouchRest::Document) &&
|
61
|
+
# (self[key].nil? || property.default.nil?))
|
58
62
|
klass.send(property.init_method, self[key])
|
63
|
+
#end
|
59
64
|
end
|
60
|
-
self[
|
65
|
+
self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
|
61
66
|
end
|
62
67
|
end
|
63
68
|
end
|
@@ -31,13 +31,19 @@ if RUBY_VERSION.to_f < 1.9
|
|
31
31
|
class Net::BufferedIO #:nodoc:
|
32
32
|
alias :old_rbuf_fill :rbuf_fill
|
33
33
|
def rbuf_fill
|
34
|
-
|
35
|
-
|
36
|
-
rescue Errno::EWOULDBLOCK
|
37
|
-
if IO.select([@io], nil, nil, @read_timeout)
|
34
|
+
if @io.respond_to?(:read_nonblock)
|
35
|
+
begin
|
38
36
|
@rbuf << @io.read_nonblock(65536)
|
39
|
-
|
40
|
-
|
37
|
+
rescue Errno::EWOULDBLOCK
|
38
|
+
if IO.select([@io], nil, nil, @read_timeout)
|
39
|
+
retry
|
40
|
+
else
|
41
|
+
raise Timeout::TimeoutError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
timeout(@read_timeout) do
|
46
|
+
@rbuf << @io.sysread(65536)
|
41
47
|
end
|
42
48
|
end
|
43
49
|
end
|
@@ -24,15 +24,17 @@ module CouchRest
|
|
24
24
|
subklass.send(:include, CouchRest::Mixins::Properties)
|
25
25
|
end
|
26
26
|
|
27
|
+
# Accessors
|
28
|
+
attr_accessor :casted_by
|
29
|
+
|
27
30
|
# Callbacks
|
28
31
|
define_callbacks :create
|
29
32
|
define_callbacks :save
|
30
33
|
define_callbacks :update
|
31
34
|
define_callbacks :destroy
|
32
35
|
|
33
|
-
def initialize(
|
36
|
+
def initialize(passed_keys={})
|
34
37
|
apply_defaults # defined in CouchRest::Mixins::Properties
|
35
|
-
keys ||= {}
|
36
38
|
super
|
37
39
|
cast_keys # defined in CouchRest::Mixins::Properties
|
38
40
|
unless self['_id'] && self['_rev']
|
@@ -203,8 +205,8 @@ module CouchRest
|
|
203
205
|
_run_destroy_callbacks do
|
204
206
|
result = database.delete_doc(self, bulk)
|
205
207
|
if result['ok']
|
206
|
-
self
|
207
|
-
self
|
208
|
+
self.delete('_rev')
|
209
|
+
self.delete('_id')
|
208
210
|
end
|
209
211
|
result['ok']
|
210
212
|
end
|
@@ -25,151 +25,167 @@
|
|
25
25
|
# example, an array without those additions being shared with either their
|
26
26
|
# parent, siblings, or children, which is unlike the regular class-level
|
27
27
|
# attributes that are shared across the entire hierarchy.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
28
|
+
module CouchRest
|
29
|
+
module ClassExtension
|
30
|
+
def self.included(base)
|
31
|
+
if CouchRest::ClassExtension::InstanceMethods.instance_methods.all? {|m| base.respond_to?(m)}
|
32
|
+
# do nothing
|
33
|
+
elsif CouchRest::ClassExtension::InstanceMethods.instance_methods.any? {|m| base.respond_to?(m)}
|
34
|
+
raise RuntimeError, "Conflicting extentions to Class, work it out"
|
35
|
+
else
|
36
|
+
base.send(:include, CouchRest::ClassExtension::InstanceMethods)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module InstanceMethods
|
41
|
+
# Defines class-level and instance-level attribute reader.
|
42
|
+
#
|
43
|
+
# @param *syms<Array> Array of attributes to define reader for.
|
44
|
+
# @return <Array[#to_s]> List of attributes that were made into cattr_readers
|
45
|
+
#
|
46
|
+
# @api public
|
47
|
+
#
|
48
|
+
# @todo Is this inconsistent in that it does not allow you to prevent
|
49
|
+
# an instance_reader via :instance_reader => false
|
50
|
+
def cattr_reader(*syms)
|
51
|
+
syms.flatten.each do |sym|
|
52
|
+
next if sym.is_a?(Hash)
|
53
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
54
|
+
unless defined? @@#{sym}
|
55
|
+
@@#{sym} = nil
|
56
|
+
end
|
45
57
|
|
46
|
-
def self.#{sym}
|
47
|
-
@@#{sym}
|
48
|
-
end
|
58
|
+
def self.#{sym}
|
59
|
+
@@#{sym}
|
60
|
+
end
|
49
61
|
|
50
|
-
def #{sym}
|
51
|
-
@@#{sym}
|
52
|
-
end
|
53
|
-
RUBY
|
62
|
+
def #{sym}
|
63
|
+
@@#{sym}
|
54
64
|
end
|
55
|
-
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
end
|
56
68
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
unless defined? @@#{sym}
|
69
|
-
@@#{sym} = nil
|
70
|
-
end
|
69
|
+
# Defines class-level (and optionally instance-level) attribute writer.
|
70
|
+
#
|
71
|
+
# @param <Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define writer for.
|
72
|
+
# @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
|
73
|
+
# @return <Array[#to_s]> List of attributes that were made into cattr_writers
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def cattr_writer(*syms)
|
77
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
78
|
+
syms.flatten.each do |sym|
|
79
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
80
|
+
unless defined? @@#{sym}
|
81
|
+
@@#{sym} = nil
|
82
|
+
end
|
71
83
|
|
72
|
-
def self.#{sym}=(obj)
|
73
|
-
@@#{sym} = obj
|
74
|
-
end
|
75
|
-
RUBY
|
84
|
+
def self.#{sym}=(obj)
|
85
|
+
@@#{sym} = obj
|
86
|
+
end
|
87
|
+
RUBY
|
76
88
|
|
77
|
-
|
78
|
-
|
79
|
-
def #{sym}=(obj)
|
80
|
-
@@#{sym} = obj
|
81
|
-
end
|
82
|
-
RUBY
|
83
|
-
end
|
89
|
+
unless options[:instance_writer] == false
|
90
|
+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
91
|
+
def #{sym}=(obj)
|
92
|
+
@@#{sym} = obj
|
84
93
|
end
|
85
|
-
|
94
|
+
RUBY
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
86
98
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
99
|
+
# Defines class-level (and optionally instance-level) attribute accessor.
|
100
|
+
#
|
101
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define accessor for.
|
102
|
+
# @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
|
103
|
+
# @return <Array[#to_s]> List of attributes that were made into accessors
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def cattr_accessor(*syms)
|
107
|
+
cattr_reader(*syms)
|
108
|
+
cattr_writer(*syms)
|
109
|
+
end
|
98
110
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
# Defines class-level inheritable attribute reader. Attributes are available to subclasses,
|
112
|
+
# each subclass has a copy of parent's attribute.
|
113
|
+
#
|
114
|
+
# @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
|
115
|
+
# @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
|
116
|
+
#
|
117
|
+
# @api public
|
118
|
+
#
|
119
|
+
# @todo Do we want to block instance_reader via :instance_reader => false
|
120
|
+
# @todo It would be preferable that we do something with a Hash passed in
|
121
|
+
# (error out or do the same as other methods above) instead of silently
|
122
|
+
# moving on). In particular, this makes the return value of this function
|
123
|
+
# less useful.
|
124
|
+
def class_inheritable_reader(*ivars)
|
125
|
+
instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
|
114
126
|
|
115
|
-
|
116
|
-
|
117
|
-
def self.#{ivar}
|
118
|
-
return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
|
119
|
-
ivar = superclass.#{ivar}
|
120
|
-
return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
|
121
|
-
@#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) && !ivar.is_a?(Symbol) ? ivar.dup : ivar
|
122
|
-
end
|
123
|
-
RUBY
|
124
|
-
|
125
|
-
|
126
|
-
def #{ivar}
|
127
|
-
self.class.#{ivar}
|
128
|
-
end
|
129
|
-
RUBY
|
127
|
+
ivars.each do |ivar|
|
128
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
129
|
+
def self.#{ivar}
|
130
|
+
return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
|
131
|
+
ivar = superclass.#{ivar}
|
132
|
+
return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
|
133
|
+
@#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) && !ivar.is_a?(Symbol) ? ivar.dup : ivar
|
134
|
+
end
|
135
|
+
RUBY
|
136
|
+
unless instance_reader == false
|
137
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
138
|
+
def #{ivar}
|
139
|
+
self.class.#{ivar}
|
140
|
+
end
|
141
|
+
RUBY
|
142
|
+
end
|
143
|
+
end
|
130
144
|
end
|
145
|
+
|
146
|
+
# Defines class-level inheritable attribute writer. Attributes are available to subclasses,
|
147
|
+
# each subclass has a copy of parent's attribute.
|
148
|
+
#
|
149
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
150
|
+
# define inheritable writer for.
|
151
|
+
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
152
|
+
# @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
#
|
156
|
+
# @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
|
157
|
+
# class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
|
158
|
+
def class_inheritable_writer(*ivars)
|
159
|
+
instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash)
|
160
|
+
ivars.each do |ivar|
|
161
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
162
|
+
def self.#{ivar}=(obj)
|
163
|
+
@#{ivar} = obj
|
131
164
|
end
|
132
|
-
|
165
|
+
RUBY
|
166
|
+
unless instance_writer == false
|
167
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
168
|
+
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
169
|
+
RUBY
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
133
173
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
def class_inheritable_writer(*ivars)
|
147
|
-
instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash)
|
148
|
-
ivars.each do |ivar|
|
149
|
-
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
150
|
-
def self.#{ivar}=(obj)
|
151
|
-
@#{ivar} = obj
|
152
|
-
end
|
153
|
-
RUBY
|
154
|
-
unless instance_writer == false
|
155
|
-
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
156
|
-
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
157
|
-
RUBY
|
174
|
+
# Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
|
175
|
+
# each subclass has a copy of parent's attribute.
|
176
|
+
#
|
177
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
178
|
+
# define inheritable accessor for.
|
179
|
+
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
180
|
+
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
181
|
+
#
|
182
|
+
# @api public
|
183
|
+
def class_inheritable_accessor(*syms)
|
184
|
+
class_inheritable_reader(*syms)
|
185
|
+
class_inheritable_writer(*syms)
|
158
186
|
end
|
159
187
|
end
|
160
188
|
end
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
#
|
165
|
-
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
166
|
-
# define inheritable accessor for.
|
167
|
-
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
168
|
-
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
169
|
-
#
|
170
|
-
# @api public
|
171
|
-
def class_inheritable_accessor(*syms)
|
172
|
-
class_inheritable_reader(*syms)
|
173
|
-
class_inheritable_writer(*syms)
|
174
|
-
end
|
175
|
-
end
|
189
|
+
end
|
190
|
+
|
191
|
+
Class.send(:include, CouchRest::ClassExtension)
|
data/lib/couchrest.rb
CHANGED
@@ -22,12 +22,13 @@ $:.unshift File.dirname(__FILE__) unless
|
|
22
22
|
$:.include?(File.dirname(__FILE__)) ||
|
23
23
|
$:.include?(File.expand_path(File.dirname(__FILE__)))
|
24
24
|
|
25
|
+
$COUCHREST_DEBUG ||= false
|
25
26
|
|
26
27
|
require 'couchrest/monkeypatches'
|
27
28
|
|
28
29
|
# = CouchDB, close to the metal
|
29
30
|
module CouchRest
|
30
|
-
VERSION
|
31
|
+
VERSION = '0.17.0' unless self.const_defined?("VERSION")
|
31
32
|
|
32
33
|
autoload :Server, 'couchrest/core/server'
|
33
34
|
autoload :Database, 'couchrest/core/database'
|
@@ -39,6 +40,7 @@ module CouchRest
|
|
39
40
|
autoload :Pager, 'couchrest/helper/pager'
|
40
41
|
autoload :FileManager, 'couchrest/helper/file_manager'
|
41
42
|
autoload :Streamer, 'couchrest/helper/streamer'
|
43
|
+
autoload :Upgrade, 'couchrest/helper/upgrade'
|
42
44
|
|
43
45
|
autoload :ExtendedDocument, 'couchrest/more/extended_document'
|
44
46
|
autoload :CastedModel, 'couchrest/more/casted_model'
|
@@ -124,18 +126,42 @@ module CouchRest
|
|
124
126
|
cr.database(parsed[:database])
|
125
127
|
end
|
126
128
|
|
127
|
-
def put
|
129
|
+
def put(uri, doc = nil)
|
128
130
|
payload = doc.to_json if doc
|
129
|
-
|
131
|
+
begin
|
132
|
+
JSON.parse(RestClient.put(uri, payload))
|
133
|
+
rescue Exception => e
|
134
|
+
if $COUCHREST_DEBUG == true
|
135
|
+
raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
136
|
+
else
|
137
|
+
raise e
|
138
|
+
end
|
139
|
+
end
|
130
140
|
end
|
131
141
|
|
132
|
-
def get
|
133
|
-
|
142
|
+
def get(uri)
|
143
|
+
begin
|
144
|
+
JSON.parse(RestClient.get(uri), :max_nesting => false)
|
145
|
+
rescue => e
|
146
|
+
if $COUCHREST_DEBUG == true
|
147
|
+
raise "Error while sending a GET request #{uri}\n: #{e}"
|
148
|
+
else
|
149
|
+
raise e
|
150
|
+
end
|
151
|
+
end
|
134
152
|
end
|
135
153
|
|
136
154
|
def post uri, doc = nil
|
137
155
|
payload = doc.to_json if doc
|
138
|
-
|
156
|
+
begin
|
157
|
+
JSON.parse(RestClient.post(uri, payload))
|
158
|
+
rescue Exception => e
|
159
|
+
if $COUCHREST_DEBUG == true
|
160
|
+
raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
161
|
+
else
|
162
|
+
raise e
|
163
|
+
end
|
164
|
+
end
|
139
165
|
end
|
140
166
|
|
141
167
|
def delete uri
|
@@ -149,8 +149,8 @@ describe CouchRest::Database do
|
|
149
149
|
{"mild" => "yet local"},
|
150
150
|
{"another" => ["set","of","keys"]}
|
151
151
|
])
|
152
|
-
rs
|
153
|
-
@db.get(r['id'])
|
152
|
+
rs.each do |r|
|
153
|
+
@db.get(r['id']).rev.should == r["rev"]
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
@@ -170,26 +170,10 @@ describe CouchRest::Database do
|
|
170
170
|
{"_id" => "twoB", "mild" => "yet local"},
|
171
171
|
{"another" => ["set","of","keys"]}
|
172
172
|
])
|
173
|
-
rs
|
174
|
-
@db.get(r['id'])
|
173
|
+
rs.each do |r|
|
174
|
+
@db.get(r['id']).rev.should == r["rev"]
|
175
175
|
end
|
176
176
|
end
|
177
|
-
|
178
|
-
it "in the case of an id conflict should not insert anything" do
|
179
|
-
@r = @db.save_doc({'lemons' => 'from texas', 'and' => 'how', "_id" => "oneB"})
|
180
|
-
|
181
|
-
lambda do
|
182
|
-
rs = @db.bulk_save([
|
183
|
-
{"_id" => "oneB", "wild" => "and random"},
|
184
|
-
{"_id" => "twoB", "mild" => "yet local"},
|
185
|
-
{"another" => ["set","of","keys"]}
|
186
|
-
])
|
187
|
-
end.should raise_error(RestClient::RequestFailed)
|
188
|
-
|
189
|
-
lambda do
|
190
|
-
@db.get('twoB')
|
191
|
-
end.should raise_error(RestClient::ResourceNotFound)
|
192
|
-
end
|
193
177
|
|
194
178
|
it "should empty the bulk save cache if no documents are given" do
|
195
179
|
@db.save_doc({"_id" => "bulk_cache_1", "val" => "test"}, true)
|
@@ -593,7 +577,8 @@ describe CouchRest::Database do
|
|
593
577
|
@db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
|
594
578
|
end
|
595
579
|
it "should fail without a rev" do
|
596
|
-
|
580
|
+
@doc.delete("_rev")
|
581
|
+
lambda{@db.move_doc @doc, @docid}.should raise_error(ArgumentError)
|
597
582
|
lambda{@db.get(@r['id'])}.should_not raise_error
|
598
583
|
end
|
599
584
|
it "should succeed with a rev" do
|
@@ -224,7 +224,8 @@ describe CouchRest::Document do
|
|
224
224
|
@db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
|
225
225
|
end
|
226
226
|
it "should fail without a rev" do
|
227
|
-
|
227
|
+
@doc.delete("_rev")
|
228
|
+
lambda{@doc.move @docid}.should raise_error(ArgumentError)
|
228
229
|
lambda{@db.get(@resp['id'])}.should_not raise_error
|
229
230
|
end
|
230
231
|
it "should succeed with a rev" do
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
require File.join(FIXTURE_PATH, 'more', 'card')
|
3
|
+
|
4
|
+
class Car < CouchRest::ExtendedDocument
|
5
|
+
use_database TEST_SERVER.default_database
|
6
|
+
|
7
|
+
property :name
|
8
|
+
property :driver, :cast_as => 'Driver'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Driver < CouchRest::ExtendedDocument
|
12
|
+
use_database TEST_SERVER.default_database
|
13
|
+
# You have to add a casted_by accessor if you want to reach a casted extended doc parent
|
14
|
+
attr_accessor :casted_by
|
15
|
+
|
16
|
+
property :name
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "casting an extended document" do
|
20
|
+
|
21
|
+
before(:each) do
|
22
|
+
@car = Car.new(:name => 'Renault 306')
|
23
|
+
@driver = Driver.new(:name => 'Matt')
|
24
|
+
end
|
25
|
+
|
26
|
+
# it "should not create an empty casted object" do
|
27
|
+
# @car.driver.should be_nil
|
28
|
+
# end
|
29
|
+
|
30
|
+
it "should let you assign the casted attribute after instantializing an object" do
|
31
|
+
@car.driver = @driver
|
32
|
+
@car.driver.name.should == 'Matt'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should let the casted document who casted it" do
|
36
|
+
Car.new(:name => 'Renault 306', :driver => @driver)
|
37
|
+
@car.driver.casted_by.should == @car
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -70,6 +70,7 @@ describe CouchRest::CastedModel do
|
|
70
70
|
|
71
71
|
describe "saved document with casted models" do
|
72
72
|
before(:each) do
|
73
|
+
reset_test_db!
|
73
74
|
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
|
74
75
|
@obj.save.should be_true
|
75
76
|
@obj = DummyModel.get(@obj.id)
|
@@ -94,4 +95,4 @@ describe CouchRest::CastedModel do
|
|
94
95
|
|
95
96
|
end
|
96
97
|
|
97
|
-
end
|
98
|
+
end
|
@@ -4,6 +4,7 @@ describe "ExtendedDocument attachments" do
|
|
4
4
|
|
5
5
|
describe "#has_attachment?" do
|
6
6
|
before(:each) do
|
7
|
+
reset_test_db!
|
7
8
|
@obj = Basic.new
|
8
9
|
@obj.save.should == true
|
9
10
|
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
@@ -126,4 +127,4 @@ describe "ExtendedDocument attachments" do
|
|
126
127
|
@obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
|
127
128
|
end
|
128
129
|
end
|
129
|
-
end
|
130
|
+
end
|
@@ -105,6 +105,9 @@ describe "ExtendedDocument views" do
|
|
105
105
|
|
106
106
|
describe "a model with a compound key view" do
|
107
107
|
before(:all) do
|
108
|
+
Article.design_doc_fresh = false
|
109
|
+
Article.by_user_id_and_date.each{|a| a.destroy(true)}
|
110
|
+
Article.database.bulk_delete
|
108
111
|
written_at = Time.now - 24 * 3600 * 7
|
109
112
|
@titles = ["uniq one", "even more interesting", "less fun", "not junk"]
|
110
113
|
@user_ids = ["quentin", "aaron"]
|
@@ -164,41 +167,41 @@ describe "ExtendedDocument views" do
|
|
164
167
|
# TODO: moved to Design, delete
|
165
168
|
describe "adding a view" do
|
166
169
|
before(:each) do
|
170
|
+
reset_test_db!
|
167
171
|
Article.by_date
|
168
|
-
@design_docs = Article.database.documents :startkey => "_design/",
|
169
|
-
:endkey => "_design/\u9999"
|
172
|
+
@design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
170
173
|
end
|
171
174
|
it "should not create a design doc on view definition" do
|
172
175
|
Article.view_by :created_at
|
173
|
-
newdocs = Article.database.documents :startkey => "_design/",
|
174
|
-
:endkey => "_design/\u9999"
|
176
|
+
newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
175
177
|
newdocs["rows"].length.should == @design_docs["rows"].length
|
176
178
|
end
|
177
|
-
it "should create a new design document on view access" do
|
179
|
+
it "should create a new version of the design document on view access" do
|
180
|
+
old_design_doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
|
178
181
|
Article.view_by :updated_at
|
179
182
|
Article.by_updated_at
|
180
|
-
newdocs = Article.database.documents
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
183
|
+
newdocs = Article.database.documents({:startkey => "_design/", :endkey => "_design/\u9999"})
|
184
|
+
|
185
|
+
doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
|
186
|
+
doc["_rev"].should_not == old_design_doc["_rev"]
|
187
|
+
doc["views"].keys.should include("by_updated_at")
|
185
188
|
end
|
186
189
|
end
|
187
190
|
|
188
191
|
describe "with a lot of designs left around" do
|
189
192
|
before(:each) do
|
193
|
+
reset_test_db!
|
190
194
|
Article.by_date
|
191
195
|
Article.view_by :field
|
192
196
|
Article.by_field
|
193
197
|
end
|
194
198
|
it "should clean them up" do
|
199
|
+
ddocs = Article.all_design_doc_versions
|
195
200
|
Article.view_by :stream
|
196
201
|
Article.by_stream
|
197
|
-
ddocs = Article.all_design_doc_versions
|
198
|
-
ddocs["rows"].length.should > 1
|
199
202
|
Article.cleanup_design_docs!
|
200
203
|
ddocs = Article.all_design_doc_versions
|
201
204
|
ddocs["rows"].length.should == 1
|
202
205
|
end
|
203
206
|
end
|
204
|
-
end
|
207
|
+
end
|
@@ -8,6 +8,7 @@ require File.join(FIXTURE_PATH, 'more', 'event')
|
|
8
8
|
describe "ExtendedDocument properties" do
|
9
9
|
|
10
10
|
before(:each) do
|
11
|
+
reset_test_db!
|
11
12
|
@card = Card.new(:first_name => "matt")
|
12
13
|
end
|
13
14
|
|
@@ -126,4 +127,4 @@ describe "ExtendedDocument properties" do
|
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
129
|
-
end
|
130
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
require File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'couchrest', 'support', 'class')
|
3
|
+
|
4
|
+
describe CouchRest::ClassExtension do
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
class FullyDefinedClassExtensions
|
8
|
+
def self.respond_to?(method)
|
9
|
+
if CouchRest::ClassExtension::InstanceMethods.instance_methods.include?(method)
|
10
|
+
true
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class PartDefinedClassExtensions
|
18
|
+
def self.respond_to?(method)
|
19
|
+
methods = CouchRest::ClassExtension::InstanceMethods.instance_methods
|
20
|
+
methods.delete('cattr_reader')
|
21
|
+
|
22
|
+
if methods.include?(method)
|
23
|
+
false
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class NoClassExtensions
|
31
|
+
def self.respond_to?(method)
|
32
|
+
if CouchRest::ClassExtension::InstanceMethods.instance_methods.include?(method)
|
33
|
+
false
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should not include InstanceMethods if the class extensions are already defined" do
|
44
|
+
FullyDefinedClassExtensions.send(:include, CouchRest::ClassExtension)
|
45
|
+
FullyDefinedClassExtensions.ancestors.should_not include(CouchRest::ClassExtension::InstanceMethods)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise RuntimeError if the class extensions are only partially defined" do
|
49
|
+
lambda {
|
50
|
+
PartDefinedClassExtensions.send(:include, CouchRest::ClassExtension)
|
51
|
+
}.should raise_error(RuntimeError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should include class extensions if they are not already defined" do
|
55
|
+
NoClassExtensions.send(:include, CouchRest::ClassExtension)
|
56
|
+
NoClassExtensions.ancestors.should include(CouchRest::ClassExtension::InstanceMethods)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jchris-couchrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- J. Chris Anderson
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- lib/couchrest/helper
|
90
90
|
- lib/couchrest/helper/pager.rb
|
91
91
|
- lib/couchrest/helper/streamer.rb
|
92
|
+
- lib/couchrest/helper/upgrade.rb
|
92
93
|
- lib/couchrest/mixins
|
93
94
|
- lib/couchrest/mixins/attachments.rb
|
94
95
|
- lib/couchrest/mixins/callbacks.rb
|
@@ -136,36 +137,19 @@ files:
|
|
136
137
|
- spec/couchrest/helpers/pager_spec.rb
|
137
138
|
- spec/couchrest/helpers/streamer_spec.rb
|
138
139
|
- spec/couchrest/more
|
140
|
+
- spec/couchrest/more/casted_extended_doc_spec.rb
|
139
141
|
- spec/couchrest/more/casted_model_spec.rb
|
140
142
|
- spec/couchrest/more/extended_doc_attachment_spec.rb
|
141
143
|
- spec/couchrest/more/extended_doc_spec.rb
|
142
144
|
- spec/couchrest/more/extended_doc_view_spec.rb
|
143
145
|
- spec/couchrest/more/property_spec.rb
|
146
|
+
- spec/couchrest/support
|
147
|
+
- spec/couchrest/support/class_spec.rb
|
144
148
|
- spec/fixtures
|
145
149
|
- spec/fixtures/attachments
|
146
150
|
- spec/fixtures/attachments/couchdb.png
|
147
151
|
- spec/fixtures/attachments/README
|
148
152
|
- spec/fixtures/attachments/test.html
|
149
|
-
- spec/fixtures/couchapp
|
150
|
-
- spec/fixtures/couchapp/_attachments
|
151
|
-
- spec/fixtures/couchapp/_attachments/index.html
|
152
|
-
- spec/fixtures/couchapp/doc.json
|
153
|
-
- spec/fixtures/couchapp/foo
|
154
|
-
- spec/fixtures/couchapp/foo/bar.txt
|
155
|
-
- spec/fixtures/couchapp/foo/test.json
|
156
|
-
- spec/fixtures/couchapp/test.json
|
157
|
-
- spec/fixtures/couchapp/views
|
158
|
-
- spec/fixtures/couchapp/views/example-map.js
|
159
|
-
- spec/fixtures/couchapp/views/example-reduce.js
|
160
|
-
- spec/fixtures/couchapp-test
|
161
|
-
- spec/fixtures/couchapp-test/my-app
|
162
|
-
- spec/fixtures/couchapp-test/my-app/_attachments
|
163
|
-
- spec/fixtures/couchapp-test/my-app/_attachments/index.html
|
164
|
-
- spec/fixtures/couchapp-test/my-app/foo
|
165
|
-
- spec/fixtures/couchapp-test/my-app/foo/bar.txt
|
166
|
-
- spec/fixtures/couchapp-test/my-app/views
|
167
|
-
- spec/fixtures/couchapp-test/my-app/views/example-map.js
|
168
|
-
- spec/fixtures/couchapp-test/my-app/views/example-reduce.js
|
169
153
|
- spec/fixtures/more
|
170
154
|
- spec/fixtures/more/article.rb
|
171
155
|
- spec/fixtures/more/card.rb
|