jchris-couchrest 0.2 → 0.2.1
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.rb +1 -1
- data/lib/couchrest/core/database.rb +1 -1
- data/lib/couchrest/mixins/callbacks.rb +73 -32
- data/lib/couchrest/mixins/design_doc.rb +1 -0
- data/lib/couchrest/mixins/views.rb +2 -3
- data/lib/couchrest/monkeypatches.rb +12 -6
- data/lib/couchrest/support/class.rb +148 -132
- data/spec/couchrest/more/extended_doc_view_spec.rb +11 -12
- data/spec/couchrest/support/class_spec.rb +59 -0
- metadata +3 -1
data/lib/couchrest.rb
CHANGED
@@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
|
|
28
28
|
|
29
29
|
# = CouchDB, close to the metal
|
30
30
|
module CouchRest
|
31
|
-
VERSION = '0.2' unless self.const_defined?("VERSION")
|
31
|
+
VERSION = '0.2.1' unless self.const_defined?("VERSION")
|
32
32
|
|
33
33
|
autoload :Server, 'couchrest/core/server'
|
34
34
|
autoload :Database, 'couchrest/core/database'
|
@@ -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
|
@@ -129,12 +129,11 @@ 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
|
136
|
-
|
137
|
-
view = fetch_view name, (opts.has_key?(:include_docs) ? opts : opts.merge({:include_docs => true})), &block
|
136
|
+
view = fetch_view name, opts.merge({:include_docs => true}), &block
|
138
137
|
view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
139
138
|
rescue
|
140
139
|
# fallback for old versions of couchdb that don't
|
@@ -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
|
@@ -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)
|
@@ -167,24 +167,24 @@ describe "ExtendedDocument views" do
|
|
167
167
|
# TODO: moved to Design, delete
|
168
168
|
describe "adding a view" do
|
169
169
|
before(:each) do
|
170
|
+
reset_test_db!
|
170
171
|
Article.by_date
|
171
|
-
@design_docs = Article.database.documents :startkey => "_design/",
|
172
|
-
:endkey => "_design/\u9999"
|
172
|
+
@design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
173
173
|
end
|
174
174
|
it "should not create a design doc on view definition" do
|
175
175
|
Article.view_by :created_at
|
176
|
-
newdocs = Article.database.documents :startkey => "_design/",
|
177
|
-
:endkey => "_design/\u9999"
|
176
|
+
newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
178
177
|
newdocs["rows"].length.should == @design_docs["rows"].length
|
179
178
|
end
|
180
|
-
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"]
|
181
181
|
Article.view_by :updated_at
|
182
182
|
Article.by_updated_at
|
183
|
-
newdocs = Article.database.documents
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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")
|
188
188
|
end
|
189
189
|
end
|
190
190
|
|
@@ -195,10 +195,9 @@ describe "ExtendedDocument views" do
|
|
195
195
|
Article.by_field
|
196
196
|
end
|
197
197
|
it "should clean them up" do
|
198
|
+
ddocs = Article.all_design_doc_versions
|
198
199
|
Article.view_by :stream
|
199
200
|
Article.by_stream
|
200
|
-
ddocs = Article.all_design_doc_versions
|
201
|
-
ddocs["rows"].length.should > 1
|
202
201
|
Article.cleanup_design_docs!
|
203
202
|
ddocs = Article.all_design_doc_versions
|
204
203
|
ddocs["rows"].length.should == 1
|
@@ -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.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- J. Chris Anderson
|
@@ -142,6 +142,8 @@ files:
|
|
142
142
|
- spec/couchrest/more/extended_doc_spec.rb
|
143
143
|
- spec/couchrest/more/extended_doc_view_spec.rb
|
144
144
|
- spec/couchrest/more/property_spec.rb
|
145
|
+
- spec/couchrest/support
|
146
|
+
- spec/couchrest/support/class_spec.rb
|
145
147
|
- spec/fixtures
|
146
148
|
- spec/fixtures/attachments
|
147
149
|
- spec/fixtures/attachments/couchdb.png
|