c280b3a29b
Refactored basic directory structure. Moved to ActiveSupport for Validations and Callbacks. Cleaned up older code, and removed support for text property types.
167 lines
6.7 KiB
Ruby
167 lines
6.7 KiB
Ruby
module CouchRest
|
|
module Model
|
|
module Views
|
|
extend ActiveSupport::Concern
|
|
|
|
module ClassMethods
|
|
# Define a CouchDB view. The name of the view will be the concatenation
|
|
# of <tt>by</tt> and the keys joined by <tt>_and_</tt>
|
|
#
|
|
# ==== Example views:
|
|
#
|
|
# class Post
|
|
# # view with default options
|
|
# # query with Post.by_date
|
|
# view_by :date, :descending => true
|
|
#
|
|
# # view with compound sort-keys
|
|
# # query with Post.by_user_id_and_date
|
|
# view_by :user_id, :date
|
|
#
|
|
# # view with custom map/reduce functions
|
|
# # query with Post.by_tags :reduce => true
|
|
# view_by :tags,
|
|
# :map =>
|
|
# "function(doc) {
|
|
# if (doc['couchrest-type'] == 'Post' && doc.tags) {
|
|
# doc.tags.forEach(function(tag){
|
|
# emit(doc.tag, 1);
|
|
# });
|
|
# }
|
|
# }",
|
|
# :reduce =>
|
|
# "function(keys, values, rereduce) {
|
|
# return sum(values);
|
|
# }"
|
|
# end
|
|
#
|
|
# <tt>view_by :date</tt> will create a view defined by this Javascript
|
|
# function:
|
|
#
|
|
# function(doc) {
|
|
# if (doc['couchrest-type'] == 'Post' && doc.date) {
|
|
# emit(doc.date, null);
|
|
# }
|
|
# }
|
|
#
|
|
# It can be queried by calling <tt>Post.by_date</tt> which accepts all
|
|
# valid options for CouchRest::Database#view. In addition, calling with
|
|
# the <tt>:raw => true</tt> option will return the view rows
|
|
# themselves. By default <tt>Post.by_date</tt> will return the
|
|
# documents included in the generated view.
|
|
#
|
|
# Calling with :database => [instance of CouchRest::Database] will
|
|
# send the query to a specific database, otherwise it will go to
|
|
# the model's default database (use_database)
|
|
#
|
|
# CouchRest::Database#view options can be applied at view definition
|
|
# time as defaults, and they will be curried and used at view query
|
|
# time. Or they can be overridden at query time.
|
|
#
|
|
# Custom views can be queried with <tt>:reduce => true</tt> to return
|
|
# reduce results. The default for custom views is to query with
|
|
# <tt>:reduce => false</tt>.
|
|
#
|
|
# Views are generated (on a per-model basis) lazily on first-access.
|
|
# This means that if you are deploying changes to a view, the views for
|
|
# that model won't be available until generation is complete. This can
|
|
# take some time with large databases. Strategies are in the works.
|
|
#
|
|
# To understand the capabilities of this view system more completely,
|
|
# it is recommended that you read the RSpec file at
|
|
# <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
|
|
|
|
def view_by(*keys)
|
|
opts = keys.pop if keys.last.is_a?(Hash)
|
|
opts ||= {}
|
|
ducktype = opts.delete(:ducktype)
|
|
unless ducktype || opts[:map]
|
|
opts[:guards] ||= []
|
|
opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
|
|
end
|
|
keys.push opts
|
|
design_doc.view_by(*keys)
|
|
req_design_doc_refresh
|
|
end
|
|
|
|
# returns stored defaults if there is a view named this in the design doc
|
|
def has_view?(view)
|
|
view = view.to_s
|
|
design_doc && design_doc['views'] && design_doc['views'][view]
|
|
end
|
|
|
|
# Dispatches to any named view.
|
|
def view(name, query={}, &block)
|
|
db = query.delete(:database) || database
|
|
refresh_design_doc(db)
|
|
query[:raw] = true if query[:reduce]
|
|
raw = query.delete(:raw)
|
|
fetch_view_with_docs(db, name, query, raw, &block)
|
|
end
|
|
|
|
# Find the first entry in the view. If the second parameter is a string
|
|
# it will be used as the key for the request, for example:
|
|
#
|
|
# Course.first_from_view('by_teacher', 'Fred')
|
|
#
|
|
# More advanced requests can be performed by providing a hash:
|
|
#
|
|
# Course.first_from_view('by_teacher', :startkey => 'bbb', :endkey => 'eee')
|
|
#
|
|
def first_from_view(name, *args)
|
|
query = {:limit => 1}
|
|
case args.first
|
|
when String, Array
|
|
query.update(args[1]) unless args[1].nil?
|
|
query[:key] = args.first
|
|
when Hash
|
|
query.update(args.first)
|
|
end
|
|
view(name, query).first
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_view_with_docs(db, name, opts, raw=false, &block)
|
|
if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
|
|
fetch_view(db, name, opts, &block)
|
|
else
|
|
begin
|
|
if block.nil?
|
|
collection_proxy_for(design_doc, name, opts.merge({:include_docs => true}))
|
|
else
|
|
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
|
|
view['rows'].collect{|r|create_from_database(r['doc'])} if view['rows']
|
|
end
|
|
rescue
|
|
# fallback for old versions of couchdb that don't
|
|
# have include_docs support
|
|
view = fetch_view(db, name, opts, &block)
|
|
view['rows'].collect{|r|create_from_database(db.get(r['id']))} if view['rows']
|
|
end
|
|
end
|
|
end
|
|
|
|
def fetch_view(db, view_name, opts, &block)
|
|
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
|
|
retryable = true
|
|
begin
|
|
design_doc.view_on(db, view_name, opts, &block)
|
|
# the design doc may not have been saved yet on this database
|
|
rescue RestClient::ResourceNotFound => e
|
|
if retryable
|
|
save_design_doc(db)
|
|
retryable = false
|
|
retry
|
|
else
|
|
raise e
|
|
end
|
|
end
|
|
end
|
|
|
|
end # module ClassMethods
|
|
|
|
end
|
|
end
|
|
end
|