module CouchRest
module Mixins
module WillPaginate
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
# Define a CouchDB will paginate view. The name of the view will be the concatenation
# of paginate_by and the keys joined by _and_
#
# ==== Example paginated views:
#
# class Post
# # view with default options
# # query with Post.paginate_by_date
# paginated_view_by :date, :descending => true
#
# # view with compound sort-keys
# # query with Post.by_user_id_and_date
# paginated_view_by :user_id, :date
#
# # view with custom map/reduce functions
# # query with Post.by_tags :reduce => true
# paginated_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
#
# paginated_view_by :date will create a view defined by this Javascript
# function:
#
# function(doc) {
# if (doc['couchrest-type'] == 'Post' && doc.date) {
# emit(doc.date, 1);
# }
# }
#
# And a standard summing reduce function like the following:
#
# function(keys, values, rereduce) {
# return sum(values);
# }
#
# It can be queried by calling Post.paginate_by_date which accepts all
# valid options for CouchRest::Database#view. In addition, calling with
# the :raw => true option will return the view rows
# themselves. By default Post.by_date will return the
# documents included in the generated view.
#
# For further details on view_by's other options, please see the
# standard documentation.
def paginated_view_by(*keys)
# Prepare the Traditional view
opts = keys.last.is_a?(Hash) ? keys.pop : {}
view_name = "by_#{keys.join('_and_')}"
method_name = "paginate_#{view_name}"
doc_keys = keys.collect{|k| "doc['#{k}']"}
key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
guards = opts.delete(:guards) || []
guards.push("(doc['couchrest-type'] == '#{self.to_s}')")
guards.concat doc_keys
opts = {
:map => "
function( doc ) {
if (#{guards.join(' && ')}) {
emit(#{key_emit}, 1 );
}
}
",
:reduce => "
function(keys, values, rereduce) {
return sum(values);
}
"
}.merge(opts)
# View prepared, send to traditional view_by
view_by keys, opts
instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{method_name}(options = {})
paginated_view('#{view_name}', options)
end
RUBY_EVAL
end
##
# Generate a Will Paginate collection from all the available
# documents stored with a matching couchrest-type.
#
# Requires no declaration as the 'all' view is built into couchrest
# Extended Documents.
#
def paginate_all(options = {})
paginated_view(:all, options)
end
protected
##
# Return a WillPaginate collection suitable for usage
#
def paginated_view(view_name, options = {})
raise "Missing per_page parameter" if options[:per_page].nil?
options[:page] ||= 1
::WillPaginate::Collection.create( options[:page], options[:per_page] ) do |pager|
# perform view count first (should create designs if missing)
if view_name.to_sym == :all
pager.total_entries = count()
else
total = view( view_name, options.update(:reduce => true) )['rows'].pop
pager.total_entries = total ? total['value'] : 0
end
p_options = options.merge(
:design_doc => self.to_s, :view_name => view_name,
:include_docs => true
)
# Only provide the reduce parameter when necessary. This is when the view has
# been set with a reduce method and requires the reduce boolean parameter
# to be either true or false on all requests.
p_options[:reduce] = false unless view_name.to_sym == :all
results = paginate(p_options)
pager.replace( results )
end
end
end
end
end # module mixins
end # module CouchRest