jchris-couchrest 0.16 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 = 50
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
- url = CouchRest.paramify_url "#{@uri}/_view/#{name}", params
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(name, params, &block)
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
- hash = CouchRest.get("#{@uri}/#{slug}")
80
- doc = if /^_design/ =~ hash["_id"]
81
- Design.new(hash)
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(hash)
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(keys = {})
4
- keys.each do |k,v|
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 []= key, value
9
+ def []=(key, value)
9
10
  super(key.to_s, value)
10
11
  end
11
- def [] key
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 CouchRest::Callbacks
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 CouchRest::Callbacks
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, object = 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
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
181
- elsif @compiled_options[0]
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, object = 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 =~ /_run_(\w+)_(\w+)_(\w+)_callbacks/
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 compile(key = nil, object = nil)
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, object)
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, object)
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(map {|c| c.clone(klass)})
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
- self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
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("_run_\#{self.class}_#{symbol}_\#{key}_callbacks") { yield }
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 _run_#{klass}_#{kind}_#{key}_callbacks
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
- obj.send("_run_#{klass}_#{kind}_#{key}_callbacks", &blk)
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(type, *filters, &blk)
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
- filters.map! {|f| Callback.new(f, type, options.dup, self, :#{symbol})}
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}, self._#{symbol}_callbacks.compile, options)
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(type, *filters, &blk)
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}, self._#{symbol}_callbacks.compile, options)
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[key].casted_by = self if self[key].respond_to?(:casted_by)
65
+ self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
61
66
  end
62
67
  end
63
68
  end
@@ -129,7 +129,7 @@ module CouchRest
129
129
  private
130
130
 
131
131
  def fetch_view_with_docs(name, opts, raw=false, &block)
132
- if raw
132
+ if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
133
133
  fetch_view(name, opts, &block)
134
134
  else
135
135
  begin
@@ -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
- begin
35
- @rbuf << @io.read_nonblock(65536)
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
- else
40
- raise Timeout::TimeoutError
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(keys={})
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['_rev'] = nil
207
- self['_id'] = nil
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
- class Class
29
- # Defines class-level and instance-level attribute reader.
30
- #
31
- # @param *syms<Array> Array of attributes to define reader for.
32
- # @return <Array[#to_s]> List of attributes that were made into cattr_readers
33
- #
34
- # @api public
35
- #
36
- # @todo Is this inconsistent in that it does not allow you to prevent
37
- # an instance_reader via :instance_reader => false
38
- def cattr_reader(*syms)
39
- syms.flatten.each do |sym|
40
- next if sym.is_a?(Hash)
41
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
42
- unless defined? @@#{sym}
43
- @@#{sym} = nil
44
- end
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
- end
65
+ RUBY
66
+ end
67
+ end
56
68
 
57
- # Defines class-level (and optionally instance-level) attribute writer.
58
- #
59
- # @param <Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define writer for.
60
- # @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
61
- # @return <Array[#to_s]> List of attributes that were made into cattr_writers
62
- #
63
- # @api public
64
- def cattr_writer(*syms)
65
- options = syms.last.is_a?(Hash) ? syms.pop : {}
66
- syms.flatten.each do |sym|
67
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
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
- unless options[:instance_writer] == false
78
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
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
- end
94
+ RUBY
95
+ end
96
+ end
97
+ end
86
98
 
87
- # Defines class-level (and optionally instance-level) attribute accessor.
88
- #
89
- # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define accessor for.
90
- # @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
91
- # @return <Array[#to_s]> List of attributes that were made into accessors
92
- #
93
- # @api public
94
- def cattr_accessor(*syms)
95
- cattr_reader(*syms)
96
- cattr_writer(*syms)
97
- end
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
- # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
100
- # each subclass has a copy of parent's attribute.
101
- #
102
- # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
103
- # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
104
- #
105
- # @api public
106
- #
107
- # @todo Do we want to block instance_reader via :instance_reader => false
108
- # @todo It would be preferable that we do something with a Hash passed in
109
- # (error out or do the same as other methods above) instead of silently
110
- # moving on). In particular, this makes the return value of this function
111
- # less useful.
112
- def class_inheritable_reader(*ivars)
113
- instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
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
- ivars.each do |ivar|
116
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
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
- unless instance_reader == false
125
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
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
- end
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
- # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
135
- # each subclass has a copy of parent's attribute.
136
- #
137
- # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
138
- # define inheritable writer for.
139
- # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
140
- # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
141
- #
142
- # @api public
143
- #
144
- # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
145
- # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
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
- # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
163
- # each subclass has a copy of parent's attribute.
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 = '0.16' unless self.const_defined?("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 uri, doc = nil
129
+ def put(uri, doc = nil)
128
130
  payload = doc.to_json if doc
129
- JSON.parse(RestClient.put(uri, payload))
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 uri
133
- JSON.parse(RestClient.get(uri), :max_nesting => false)
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
- JSON.parse(RestClient.post(uri, payload))
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['new_revs'].each do |r|
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['new_revs'].each do |r|
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
- lambda{@db.move_doc @doc, @docid}.should raise_error(RestClient::RequestFailed)
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
- lambda{@doc.move @docid}.should raise_error(RestClient::RequestFailed)
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
@@ -442,7 +442,7 @@ describe "ExtendedDocument" do
442
442
  @dobj.destroy
443
443
  @dobj.rev.should be_nil
444
444
  @dobj.id.should be_nil
445
- @dobj.save.should == true
445
+ @dobj.save.should == true
446
446
  end
447
447
  it "should make it go away" do
448
448
  @dobj.destroy
@@ -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 :startkey => "_design/",
181
- :endkey => "_design/\u9999"
182
- # puts @design_docs.inspect
183
- # puts newdocs.inspect
184
- newdocs["rows"].length.should == @design_docs["rows"].length + 1
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: "0.16"
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