couchrest_model 1.1.2 → 1.2.0.beta
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/README.md +8 -2
- data/VERSION +1 -1
- data/couchrest_model.gemspec +2 -1
- data/history.md +8 -0
- data/lib/couchrest/model/base.rb +0 -20
- data/lib/couchrest/model/configuration.rb +2 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +35 -9
- data/lib/couchrest/model/designs/design.rb +182 -0
- data/lib/couchrest/model/designs/view.rb +91 -48
- data/lib/couchrest/model/designs.rb +72 -19
- data/lib/couchrest/model/document_queries.rb +15 -45
- data/lib/couchrest/model/properties.rb +43 -2
- data/lib/couchrest/model/proxyable.rb +20 -54
- data/lib/couchrest/model/typecast.rb +1 -1
- data/lib/couchrest/model/validations/uniqueness.rb +7 -6
- data/lib/couchrest_model.rb +1 -5
- data/spec/fixtures/models/article.rb +22 -20
- data/spec/fixtures/models/base.rb +15 -7
- data/spec/fixtures/models/course.rb +7 -4
- data/spec/fixtures/models/project.rb +4 -1
- data/spec/fixtures/models/sale_entry.rb +5 -3
- data/spec/unit/base_spec.rb +51 -5
- data/spec/unit/core_extensions/time_parsing.rb +41 -0
- data/spec/unit/designs/design_spec.rb +291 -0
- data/spec/unit/designs/view_spec.rb +135 -40
- data/spec/unit/designs_spec.rb +341 -30
- data/spec/unit/dirty_spec.rb +67 -0
- data/spec/unit/inherited_spec.rb +2 -2
- data/spec/unit/property_protection_spec.rb +3 -1
- data/spec/unit/property_spec.rb +43 -3
- data/spec/unit/proxyable_spec.rb +57 -98
- data/spec/unit/subclass_spec.rb +14 -5
- data/spec/unit/validations_spec.rb +14 -12
- metadata +172 -129
- data/lib/couchrest/model/class_proxy.rb +0 -135
- data/lib/couchrest/model/collection.rb +0 -273
- data/lib/couchrest/model/design_doc.rb +0 -115
- data/lib/couchrest/model/support/couchrest_design.rb +0 -33
- data/lib/couchrest/model/views.rb +0 -148
- data/spec/unit/class_proxy_spec.rb +0 -167
- data/spec/unit/collection_spec.rb +0 -86
- data/spec/unit/design_doc_spec.rb +0 -212
- data/spec/unit/view_spec.rb +0 -352
@@ -1,273 +0,0 @@
|
|
1
|
-
module CouchRest
|
2
|
-
module Model
|
3
|
-
# Warning! The Collection module is seriously depricated.
|
4
|
-
# Use the new Design Views instead, as this code copies many other parts
|
5
|
-
# of CouchRest Model.
|
6
|
-
#
|
7
|
-
# Expect this to be removed soon.
|
8
|
-
#
|
9
|
-
module Collection
|
10
|
-
|
11
|
-
def self.included(base)
|
12
|
-
base.extend(ClassMethods)
|
13
|
-
end
|
14
|
-
|
15
|
-
module ClassMethods
|
16
|
-
|
17
|
-
# Creates a new class method, find_all_<collection_name>, that will
|
18
|
-
# execute the view specified with the design_doc and view_name
|
19
|
-
# parameters, along with the specified view_options. This method will
|
20
|
-
# return the results of the view as an Array of objects which are
|
21
|
-
# instances of the class.
|
22
|
-
#
|
23
|
-
# This method is handy for objects that do not use the view_by method
|
24
|
-
# to declare their views.
|
25
|
-
def provides_collection(collection_name, design_doc, view_name, view_options)
|
26
|
-
class_eval <<-END, __FILE__, __LINE__ + 1
|
27
|
-
def self.find_all_#{collection_name}(options = {})
|
28
|
-
view_options = #{view_options.inspect} || {}
|
29
|
-
CollectionProxy.new(options[:database] || database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
|
30
|
-
end
|
31
|
-
END
|
32
|
-
end
|
33
|
-
|
34
|
-
# Fetch a group of objects from CouchDB. Options can include:
|
35
|
-
# :page - Specifies the page to load (starting at 1)
|
36
|
-
# :per_page - Specifies the number of objects to load per page
|
37
|
-
#
|
38
|
-
# Defaults are used if these options are not specified.
|
39
|
-
def paginate(options)
|
40
|
-
proxy = create_collection_proxy(options)
|
41
|
-
proxy.paginate(options)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Iterate over the objects in a collection, fetching them from CouchDB
|
45
|
-
# in groups. Options can include:
|
46
|
-
# :page - Specifies the page to load
|
47
|
-
# :per_page - Specifies the number of objects to load per page
|
48
|
-
#
|
49
|
-
# Defaults are used if these options are not specified.
|
50
|
-
def paginated_each(options, &block)
|
51
|
-
search = options.delete(:search)
|
52
|
-
unless search == true
|
53
|
-
proxy = create_collection_proxy(options)
|
54
|
-
else
|
55
|
-
proxy = create_search_collection_proxy(options)
|
56
|
-
end
|
57
|
-
proxy.paginated_each(options, &block)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Create a CollectionProxy for the specified view and options.
|
61
|
-
# CollectionProxy behaves just like an Array, but offers support for
|
62
|
-
# pagination.
|
63
|
-
def collection_proxy_for(design_doc, view_name, view_options = {})
|
64
|
-
options = view_options.merge(:design_doc => design_doc, :view_name => view_name)
|
65
|
-
create_collection_proxy(options)
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def create_collection_proxy(options)
|
71
|
-
design_doc, view_name, view_options = parse_view_options(options)
|
72
|
-
CollectionProxy.new(options[:database] || database, design_doc, view_name, view_options, self)
|
73
|
-
end
|
74
|
-
|
75
|
-
def create_search_collection_proxy(options)
|
76
|
-
design_doc, search_name, search_options = parse_search_options(options)
|
77
|
-
CollectionProxy.new(options[:database] || database, design_doc, search_name, search_options, self, :search)
|
78
|
-
end
|
79
|
-
|
80
|
-
def parse_view_options(options)
|
81
|
-
design_doc = options.delete(:design_doc)
|
82
|
-
raise ArgumentError, 'design_doc is required' if design_doc.nil?
|
83
|
-
|
84
|
-
view_name = options.delete(:view_name)
|
85
|
-
raise ArgumentError, 'view_name is required' if view_name.nil?
|
86
|
-
|
87
|
-
default_view_options = (design_doc.class == Design &&
|
88
|
-
design_doc['views'][view_name.to_s] &&
|
89
|
-
design_doc['views'][view_name.to_s]["couchrest-defaults"]) || {}
|
90
|
-
view_options = default_view_options.merge(options)
|
91
|
-
view_options.delete(:database)
|
92
|
-
|
93
|
-
[design_doc, view_name, view_options]
|
94
|
-
end
|
95
|
-
|
96
|
-
def parse_search_options(options)
|
97
|
-
design_doc = options.delete(:design_doc)
|
98
|
-
raise ArgumentError, 'design_doc is required' if design_doc.nil?
|
99
|
-
|
100
|
-
search_name = options.delete(:view_name)
|
101
|
-
raise ArgumentError, 'search_name is required' if search_name.nil?
|
102
|
-
|
103
|
-
search_options = options.clone
|
104
|
-
search_options.delete(:database)
|
105
|
-
|
106
|
-
[design_doc, search_name, search_options]
|
107
|
-
end
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
class CollectionProxy
|
112
|
-
alias_method :proxy_respond_to?, :respond_to?
|
113
|
-
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
114
|
-
|
115
|
-
DEFAULT_PAGE = 1
|
116
|
-
DEFAULT_PER_PAGE = 30
|
117
|
-
|
118
|
-
# Create a new CollectionProxy to represent the specified view. If a
|
119
|
-
# container class is specified, the proxy will create an object of the
|
120
|
-
# given type for each row that comes back from the view. If no
|
121
|
-
# container class is specified, the raw results are returned.
|
122
|
-
#
|
123
|
-
# The CollectionProxy provides support for paginating over a collection
|
124
|
-
# via the paginate, and paginated_each methods.
|
125
|
-
def initialize(database, design_doc, view_name, view_options = {}, container_class = nil, query_type = :view)
|
126
|
-
raise ArgumentError, "database is a required parameter" if database.nil?
|
127
|
-
|
128
|
-
@database = database
|
129
|
-
@container_class = container_class
|
130
|
-
@query_type = query_type
|
131
|
-
|
132
|
-
strip_pagination_options(view_options)
|
133
|
-
@view_options = view_options
|
134
|
-
|
135
|
-
if design_doc.class == Design
|
136
|
-
@view_name = "#{design_doc.name}/#{view_name}"
|
137
|
-
else
|
138
|
-
@view_name = "#{design_doc}/#{view_name}"
|
139
|
-
end
|
140
|
-
|
141
|
-
# Save the design doc, ready for use
|
142
|
-
@container_class.save_design_doc(@database)
|
143
|
-
end
|
144
|
-
|
145
|
-
# See Collection.paginate
|
146
|
-
def paginate(options = {})
|
147
|
-
page, per_page = parse_options(options)
|
148
|
-
results = @database.send(@query_type, @view_name, pagination_options(page, per_page))
|
149
|
-
remember_where_we_left_off(results, page)
|
150
|
-
instances = convert_to_container_array(results)
|
151
|
-
|
152
|
-
begin
|
153
|
-
if Kernel.const_get('WillPaginate')
|
154
|
-
total_rows = results['total_rows'].to_i
|
155
|
-
paginated = WillPaginate::Collection.create(page, per_page, total_rows) do |pager|
|
156
|
-
pager.replace(instances)
|
157
|
-
end
|
158
|
-
return paginated
|
159
|
-
end
|
160
|
-
rescue NameError
|
161
|
-
# When not using will_paginate, not much we could do about this. :x
|
162
|
-
end
|
163
|
-
return instances
|
164
|
-
end
|
165
|
-
|
166
|
-
# See Collection.paginated_each
|
167
|
-
def paginated_each(options = {}, &block)
|
168
|
-
page, per_page = parse_options(options)
|
169
|
-
|
170
|
-
begin
|
171
|
-
collection = paginate({:page => page, :per_page => per_page})
|
172
|
-
collection.each(&block)
|
173
|
-
page += 1
|
174
|
-
end until collection.size < per_page
|
175
|
-
end
|
176
|
-
|
177
|
-
def respond_to?(*args)
|
178
|
-
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
179
|
-
end
|
180
|
-
|
181
|
-
# Explicitly proxy === because the instance method removal above
|
182
|
-
# doesn't catch it.
|
183
|
-
def ===(other)
|
184
|
-
load_target
|
185
|
-
other === @target
|
186
|
-
end
|
187
|
-
|
188
|
-
private
|
189
|
-
|
190
|
-
def method_missing(method, *args)
|
191
|
-
if load_target
|
192
|
-
if block_given?
|
193
|
-
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
194
|
-
else
|
195
|
-
@target.send(method, *args)
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def load_target
|
201
|
-
unless loaded?
|
202
|
-
@view_options.merge!({:include_docs => true}) if @query_type == :search
|
203
|
-
results = @database.send(@query_type, @view_name, @view_options)
|
204
|
-
@target = convert_to_container_array(results)
|
205
|
-
end
|
206
|
-
@loaded = true
|
207
|
-
@target
|
208
|
-
end
|
209
|
-
|
210
|
-
def loaded?
|
211
|
-
@loaded
|
212
|
-
end
|
213
|
-
|
214
|
-
def reload
|
215
|
-
reset
|
216
|
-
load_target
|
217
|
-
self unless @target.nil?
|
218
|
-
end
|
219
|
-
|
220
|
-
def reset
|
221
|
-
@loaded = false
|
222
|
-
@target = nil
|
223
|
-
end
|
224
|
-
|
225
|
-
def inspect
|
226
|
-
load_target
|
227
|
-
@target.inspect
|
228
|
-
end
|
229
|
-
|
230
|
-
def convert_to_container_array(results)
|
231
|
-
if @container_class.nil?
|
232
|
-
results
|
233
|
-
else
|
234
|
-
results['rows'].collect { |row| @container_class.build_from_database(row['doc']) } unless results['rows'].nil?
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def pagination_options(page, per_page)
|
239
|
-
view_options = @view_options.clone
|
240
|
-
if @query_type == :view && @last_key && @last_docid && @last_page == page - 1
|
241
|
-
key = view_options.delete(:key)
|
242
|
-
end_key = view_options[:endkey] || key
|
243
|
-
options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
|
244
|
-
else
|
245
|
-
options = { :limit => per_page, :skip => per_page * (page - 1) }
|
246
|
-
end
|
247
|
-
options[:include_docs] = true
|
248
|
-
view_options.merge(options)
|
249
|
-
end
|
250
|
-
|
251
|
-
def parse_options(options)
|
252
|
-
page = options.delete(:page) || DEFAULT_PAGE
|
253
|
-
per_page = options.delete(:per_page) || DEFAULT_PER_PAGE
|
254
|
-
[page.to_i, per_page.to_i]
|
255
|
-
end
|
256
|
-
|
257
|
-
def strip_pagination_options(options)
|
258
|
-
parse_options(options)
|
259
|
-
end
|
260
|
-
|
261
|
-
def remember_where_we_left_off(results, page)
|
262
|
-
last_row = results['rows'].last
|
263
|
-
if last_row
|
264
|
-
@last_key = last_row['key']
|
265
|
-
@last_docid = last_row['id']
|
266
|
-
end
|
267
|
-
@last_page = page
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module CouchRest
|
3
|
-
module Model
|
4
|
-
module DesignDoc
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
|
9
|
-
def design_doc
|
10
|
-
@design_doc ||= ::CouchRest::Design.new(default_design_doc)
|
11
|
-
end
|
12
|
-
|
13
|
-
def design_doc_id
|
14
|
-
"_design/#{design_doc_slug}"
|
15
|
-
end
|
16
|
-
|
17
|
-
def design_doc_slug
|
18
|
-
self.to_s
|
19
|
-
end
|
20
|
-
|
21
|
-
def design_doc_uri(db = database)
|
22
|
-
"#{db.root}/#{design_doc_id}"
|
23
|
-
end
|
24
|
-
|
25
|
-
# Retreive the latest version of the design document directly
|
26
|
-
# from the database. This is never cached and will return nil if
|
27
|
-
# the design is not present.
|
28
|
-
#
|
29
|
-
# Use this method if you'd like to compare revisions [_rev] which
|
30
|
-
# is not stored in the normal design doc.
|
31
|
-
def stored_design_doc(db = database)
|
32
|
-
db.get(design_doc_id)
|
33
|
-
rescue RestClient::ResourceNotFound
|
34
|
-
nil
|
35
|
-
end
|
36
|
-
|
37
|
-
# Save the design doc onto a target database in a thread-safe way,
|
38
|
-
# not modifying the model's design_doc
|
39
|
-
#
|
40
|
-
# See also save_design_doc! to always save the design doc even if there
|
41
|
-
# are no changes.
|
42
|
-
def save_design_doc(db = database, force = false)
|
43
|
-
update_design_doc(db, force)
|
44
|
-
end
|
45
|
-
|
46
|
-
# Force the update of the model's design_doc even if it hasn't changed.
|
47
|
-
def save_design_doc!(db = database)
|
48
|
-
save_design_doc(db, true)
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def design_doc_cache
|
54
|
-
Thread.current[:couchrest_design_cache] ||= {}
|
55
|
-
end
|
56
|
-
def design_doc_cache_checksum(db)
|
57
|
-
design_doc_cache[design_doc_uri(db)]
|
58
|
-
end
|
59
|
-
def set_design_doc_cache_checksum(db, checksum)
|
60
|
-
design_doc_cache[design_doc_uri(db)] = checksum
|
61
|
-
end
|
62
|
-
|
63
|
-
# Writes out a design_doc to a given database if forced
|
64
|
-
# or the stored checksum is not the same as the current
|
65
|
-
# generated checksum.
|
66
|
-
#
|
67
|
-
# Returns the original design_doc provided, but does
|
68
|
-
# not update it with the revision.
|
69
|
-
def update_design_doc(db, force = false)
|
70
|
-
return design_doc unless force || auto_update_design_doc
|
71
|
-
|
72
|
-
# Grab the design doc's checksum
|
73
|
-
checksum = design_doc.checksum!
|
74
|
-
|
75
|
-
# If auto updates enabled, check checksum cache
|
76
|
-
return design_doc if auto_update_design_doc && design_doc_cache_checksum(db) == checksum
|
77
|
-
|
78
|
-
# Load up the stored doc (if present), update, and save
|
79
|
-
saved = stored_design_doc(db)
|
80
|
-
if saved
|
81
|
-
if force || saved['couchrest-hash'] != checksum
|
82
|
-
saved.merge!(design_doc)
|
83
|
-
db.save_doc(saved)
|
84
|
-
end
|
85
|
-
else
|
86
|
-
db.save_doc(design_doc)
|
87
|
-
design_doc.delete('_rev') # Prevent conflicts, never store rev as DB specific
|
88
|
-
end
|
89
|
-
|
90
|
-
# Ensure checksum cached for next attempt if using auto updates
|
91
|
-
set_design_doc_cache_checksum(db, checksum) if auto_update_design_doc
|
92
|
-
design_doc
|
93
|
-
end
|
94
|
-
|
95
|
-
def default_design_doc
|
96
|
-
{
|
97
|
-
"_id" => design_doc_id,
|
98
|
-
"language" => "javascript",
|
99
|
-
"views" => {
|
100
|
-
'all' => {
|
101
|
-
'map' => "function(doc) {
|
102
|
-
if (doc['#{self.model_type_key}'] == '#{self.to_s}') {
|
103
|
-
emit(doc['_id'],1);
|
104
|
-
}
|
105
|
-
}"
|
106
|
-
}
|
107
|
-
}
|
108
|
-
}
|
109
|
-
end
|
110
|
-
|
111
|
-
end # module ClassMethods
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
|
2
|
-
CouchRest::Design.class_eval do
|
3
|
-
|
4
|
-
# Calculate and update the checksum of the Design document.
|
5
|
-
# Used for ensuring the latest version has been sent to the database.
|
6
|
-
#
|
7
|
-
# This will generate an flatterned, ordered array of all the elements of the
|
8
|
-
# design document, convert to string then generate an MD5 Hash. This should
|
9
|
-
# result in a consisitent Hash accross all platforms.
|
10
|
-
#
|
11
|
-
def checksum!
|
12
|
-
# create a copy of basic elements
|
13
|
-
base = self.dup
|
14
|
-
base.delete('_id')
|
15
|
-
base.delete('_rev')
|
16
|
-
base.delete('couchrest-hash')
|
17
|
-
result = nil
|
18
|
-
flatten =
|
19
|
-
lambda {|r|
|
20
|
-
(recurse = lambda {|v|
|
21
|
-
if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
|
22
|
-
v.to_a.map{|v| recurse.call(v)}.flatten
|
23
|
-
elsif v.is_a?(Array)
|
24
|
-
v.flatten.map{|v| recurse.call(v)}
|
25
|
-
else
|
26
|
-
v.to_s
|
27
|
-
end
|
28
|
-
}).call(r)
|
29
|
-
}
|
30
|
-
self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
@@ -1,148 +0,0 @@
|
|
1
|
-
module CouchRest
|
2
|
-
module Model
|
3
|
-
module Views
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
# Define a CouchDB view. The name of the view will be the concatenation
|
8
|
-
# of <tt>by</tt> and the keys joined by <tt>_and_</tt>
|
9
|
-
#
|
10
|
-
# ==== Example views:
|
11
|
-
#
|
12
|
-
# class Post
|
13
|
-
# # view with default options
|
14
|
-
# # query with Post.by_date
|
15
|
-
# view_by :date, :descending => true
|
16
|
-
#
|
17
|
-
# # view with compound sort-keys
|
18
|
-
# # query with Post.by_user_id_and_date
|
19
|
-
# view_by :user_id, :date
|
20
|
-
#
|
21
|
-
# # view with custom map/reduce functions
|
22
|
-
# # query with Post.by_tags :reduce => true
|
23
|
-
# view_by :tags,
|
24
|
-
# :map =>
|
25
|
-
# "function(doc) {
|
26
|
-
# if (doc['model'] == 'Post' && doc.tags) {
|
27
|
-
# doc.tags.forEach(function(tag){
|
28
|
-
# emit(doc.tag, 1);
|
29
|
-
# });
|
30
|
-
# }
|
31
|
-
# }",
|
32
|
-
# :reduce =>
|
33
|
-
# "function(keys, values, rereduce) {
|
34
|
-
# return sum(values);
|
35
|
-
# }"
|
36
|
-
# end
|
37
|
-
#
|
38
|
-
# <tt>view_by :date</tt> will create a view defined by this Javascript
|
39
|
-
# function:
|
40
|
-
#
|
41
|
-
# function(doc) {
|
42
|
-
# if (doc['model'] == 'Post' && doc.date) {
|
43
|
-
# emit(doc.date, null);
|
44
|
-
# }
|
45
|
-
# }
|
46
|
-
#
|
47
|
-
# It can be queried by calling <tt>Post.by_date</tt> which accepts all
|
48
|
-
# valid options for CouchRest::Database#view. In addition, calling with
|
49
|
-
# the <tt>:raw => true</tt> option will return the view rows
|
50
|
-
# themselves. By default <tt>Post.by_date</tt> will return the
|
51
|
-
# documents included in the generated view.
|
52
|
-
#
|
53
|
-
# Calling with :database => [instance of CouchRest::Database] will
|
54
|
-
# send the query to a specific database, otherwise it will go to
|
55
|
-
# the model's default database (use_database)
|
56
|
-
#
|
57
|
-
# CouchRest::Database#view options can be applied at view definition
|
58
|
-
# time as defaults, and they will be curried and used at view query
|
59
|
-
# time. Or they can be overridden at query time.
|
60
|
-
#
|
61
|
-
# Custom views can be queried with <tt>:reduce => true</tt> to return
|
62
|
-
# reduce results. The default for custom views is to query with
|
63
|
-
# <tt>:reduce => false</tt>.
|
64
|
-
#
|
65
|
-
# Views are generated (on a per-model basis) lazily on first-access.
|
66
|
-
# This means that if you are deploying changes to a view, the views for
|
67
|
-
# that model won't be available until generation is complete. This can
|
68
|
-
# take some time with large databases. Strategies are in the works.
|
69
|
-
#
|
70
|
-
# To understand the capabilities of this view system more completely,
|
71
|
-
# it is recommended that you read the RSpec file at
|
72
|
-
# <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
|
73
|
-
|
74
|
-
def view_by(*keys)
|
75
|
-
opts = keys.pop if keys.last.is_a?(Hash)
|
76
|
-
opts ||= {}
|
77
|
-
ducktype = opts.delete(:ducktype)
|
78
|
-
unless ducktype || opts[:map]
|
79
|
-
opts[:guards] ||= []
|
80
|
-
opts[:guards].push "(doc['#{model_type_key}'] == '#{self.to_s}')"
|
81
|
-
end
|
82
|
-
keys.push opts
|
83
|
-
design_doc.view_by(*keys)
|
84
|
-
end
|
85
|
-
|
86
|
-
# returns stored defaults if there is a view named this in the design doc
|
87
|
-
def has_view?(name)
|
88
|
-
design_doc && design_doc.has_view?(name)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Check if the view can be reduced by checking to see if it has a
|
92
|
-
# reduce function.
|
93
|
-
def can_reduce_view?(name)
|
94
|
-
design_doc && design_doc.can_reduce_view?(name)
|
95
|
-
end
|
96
|
-
|
97
|
-
# Dispatches to any named view.
|
98
|
-
def view(name, query={}, &block)
|
99
|
-
query = query.dup # Modifications made on copy!
|
100
|
-
db = query.delete(:database) || database
|
101
|
-
query[:raw] = true if query[:reduce]
|
102
|
-
raw = query.delete(:raw)
|
103
|
-
save_design_doc(db)
|
104
|
-
fetch_view_with_docs(db, name, query, raw, &block)
|
105
|
-
end
|
106
|
-
|
107
|
-
# Find the first entry in the view. If the second parameter is a string
|
108
|
-
# it will be used as the key for the request, for example:
|
109
|
-
#
|
110
|
-
# Course.first_from_view('by_teacher', 'Fred')
|
111
|
-
#
|
112
|
-
# More advanced requests can be performed by providing a hash:
|
113
|
-
#
|
114
|
-
# Course.first_from_view('by_teacher', :startkey => 'bbb', :endkey => 'eee')
|
115
|
-
#
|
116
|
-
def first_from_view(name, *args)
|
117
|
-
query = {:limit => 1}
|
118
|
-
case args.first
|
119
|
-
when String, Array
|
120
|
-
query.update(args[1]) unless args[1].nil?
|
121
|
-
query[:key] = args.first
|
122
|
-
when Hash
|
123
|
-
query.update(args.first)
|
124
|
-
end
|
125
|
-
view(name, query).first
|
126
|
-
end
|
127
|
-
|
128
|
-
private
|
129
|
-
|
130
|
-
def fetch_view_with_docs(db, name, opts, raw=false, &block)
|
131
|
-
if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
|
132
|
-
fetch_view(db, name, opts, &block)
|
133
|
-
else
|
134
|
-
opts = opts.merge(:include_docs => true)
|
135
|
-
view = fetch_view db, name, opts, &block
|
136
|
-
view['rows'].collect{|r| build_from_database(r['doc'])} if view['rows']
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def fetch_view(db, view_name, opts, &block)
|
141
|
-
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
|
142
|
-
design_doc.view_on(db, view_name, opts, &block)
|
143
|
-
end
|
144
|
-
end # module ClassMethods
|
145
|
-
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|