Renaming to CouchRest Model

Refactored basic directory structure.
Moved to ActiveSupport for Validations and Callbacks.
Cleaned up older code, and removed support for text property types.
This commit is contained in:
Sam Lown 2010-06-20 22:01:11 +02:00
parent 9f1eea8d32
commit c280b3a29b
70 changed files with 1725 additions and 3733 deletions

View file

@ -0,0 +1,260 @@
module CouchRest
module Model
module Collection
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
# Creates a new class method, find_all_<collection_name>, that will
# execute the view specified with the design_doc and view_name
# parameters, along with the specified view_options. This method will
# return the results of the view as an Array of objects which are
# instances of the class.
#
# This method is handy for objects that do not use the view_by method
# to declare their views.
def provides_collection(collection_name, design_doc, view_name, view_options)
class_eval <<-END, __FILE__, __LINE__ + 1
def self.find_all_#{collection_name}(options = {})
view_options = #{view_options.inspect} || {}
CollectionProxy.new(options[:database] || database, "#{design_doc}", "#{view_name}", view_options.merge(options), Kernel.const_get('#{self}'))
end
END
end
# Fetch a group of objects from CouchDB. Options can include:
# :page - Specifies the page to load (starting at 1)
# :per_page - Specifies the number of objects to load per page
#
# Defaults are used if these options are not specified.
def paginate(options)
proxy = create_collection_proxy(options)
proxy.paginate(options)
end
# Iterate over the objects in a collection, fetching them from CouchDB
# in groups. Options can include:
# :page - Specifies the page to load
# :per_page - Specifies the number of objects to load per page
#
# Defaults are used if these options are not specified.
def paginated_each(options, &block)
search = options.delete(:search)
unless search == true
proxy = create_collection_proxy(options)
else
proxy = create_search_collection_proxy(options)
end
proxy.paginated_each(options, &block)
end
# Create a CollectionProxy for the specified view and options.
# CollectionProxy behaves just like an Array, but offers support for
# pagination.
def collection_proxy_for(design_doc, view_name, view_options = {})
options = view_options.merge(:design_doc => design_doc, :view_name => view_name)
create_collection_proxy(options)
end
private
def create_collection_proxy(options)
design_doc, view_name, view_options = parse_view_options(options)
CollectionProxy.new(options[:database] || database, design_doc, view_name, view_options, self)
end
def create_search_collection_proxy(options)
design_doc, search_name, search_options = parse_search_options(options)
CollectionProxy.new(options[:database] || database, design_doc, search_name, search_options, self, :search)
end
def parse_view_options(options)
design_doc = options.delete(:design_doc)
raise ArgumentError, 'design_doc is required' if design_doc.nil?
view_name = options.delete(:view_name)
raise ArgumentError, 'view_name is required' if view_name.nil?
default_view_options = (design_doc.class == Design &&
design_doc['views'][view_name.to_s] &&
design_doc['views'][view_name.to_s]["couchrest-defaults"]) || {}
view_options = default_view_options.merge(options)
[design_doc, view_name, view_options]
end
def parse_search_options(options)
design_doc = options.delete(:design_doc)
raise ArgumentError, 'design_doc is required' if design_doc.nil?
search_name = options.delete(:view_name)
raise ArgumentError, 'search_name is required' if search_name.nil?
search_options = options.clone
[design_doc, search_name, search_options]
end
end
class CollectionProxy
alias_method :proxy_respond_to?, :respond_to?
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 30
# Create a new CollectionProxy to represent the specified view. If a
# container class is specified, the proxy will create an object of the
# given type for each row that comes back from the view. If no
# container class is specified, the raw results are returned.
#
# The CollectionProxy provides support for paginating over a collection
# via the paginate, and paginated_each methods.
def initialize(database, design_doc, view_name, view_options = {}, container_class = nil, query_type = :view)
raise ArgumentError, "database is a required parameter" if database.nil?
@database = database
@container_class = container_class
@query_type = query_type
strip_pagination_options(view_options)
@view_options = view_options
if design_doc.class == Design
@view_name = "#{design_doc.name}/#{view_name}"
else
@view_name = "#{design_doc}/#{view_name}"
end
end
# See Collection.paginate
def paginate(options = {})
page, per_page = parse_options(options)
results = @database.send(@query_type, @view_name, pagination_options(page, per_page))
remember_where_we_left_off(results, page)
instances = convert_to_container_array(results)
begin
if Kernel.const_get('WillPaginate')
total_rows = results['total_rows'].to_i
paginated = WillPaginate::Collection.create(page, per_page, total_rows) do |pager|
pager.replace(instances)
end
return paginated
end
rescue NameError
# When not using will_paginate, not much we could do about this. :x
end
return instances
end
# See Collection.paginated_each
def paginated_each(options = {}, &block)
page, per_page = parse_options(options)
begin
collection = paginate({:page => page, :per_page => per_page})
collection.each(&block)
page += 1
end until collection.size < per_page
end
def respond_to?(*args)
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
end
# Explicitly proxy === because the instance method removal above
# doesn't catch it.
def ===(other)
load_target
other === @target
end
private
def method_missing(method, *args)
if load_target
if block_given?
@target.send(method, *args) { |*block_args| yield(*block_args) }
else
@target.send(method, *args)
end
end
end
def load_target
unless loaded?
@view_options.merge!({:include_docs => true}) if @query_type == :search
results = @database.send(@query_type, @view_name, @view_options)
@target = convert_to_container_array(results)
end
@loaded = true
@target
end
def loaded?
@loaded
end
def reload
reset
load_target
self unless @target.nil?
end
def reset
@loaded = false
@target = nil
end
def inspect
load_target
@target.inspect
end
def convert_to_container_array(results)
if @container_class.nil?
results
else
results['rows'].collect { |row| @container_class.create_from_database(row['doc']) } unless results['rows'].nil?
end
end
def pagination_options(page, per_page)
view_options = @view_options.clone
if @query_type == :view && @last_key && @last_docid && @last_page == page - 1
key = view_options.delete(:key)
end_key = view_options[:endkey] || key
options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
else
options = { :limit => per_page, :skip => per_page * (page - 1) }
end
view_options.merge(options)
end
def parse_options(options)
page = options.delete(:page) || DEFAULT_PAGE
per_page = options.delete(:per_page) || DEFAULT_PER_PAGE
[page.to_i, per_page.to_i]
end
def strip_pagination_options(options)
parse_options(options)
end
def remember_where_we_left_off(results, page)
last_row = results['rows'].last
if last_row
@last_key = last_row['key']
@last_docid = last_row['id']
end
@last_page = page
end
end
end
end
end