lib/couchrest/model/designs/view.rb in couchrest_model-1.1.2 vs lib/couchrest/model/designs/view.rb in couchrest_model-1.2.0.beta
- old
+ new
@@ -12,24 +12,26 @@
# a normal relational database are not possible.
#
class View
include Enumerable
- attr_accessor :owner, :model, :name, :query, :result
+ attr_accessor :owner, :model, :design_doc, :name, :query, :result
# Initialize a new View object. This method should not be called from
# outside CouchRest Model.
- def initialize(parent, new_query = {}, name = nil)
+ def initialize(design_doc, parent, new_query = {}, name = nil)
+ self.design_doc = design_doc
+ proxy = new_query.delete(:proxy)
if parent.is_a?(Class) && parent < CouchRest::Model::Base
raise "Name must be provided for view to be initialized" if name.nil?
- self.model = parent
+ self.model = (proxy || parent)
self.owner = parent
self.name = name.to_s
# Default options:
self.query = { }
elsif parent.is_a?(self.class)
- self.model = (new_query.delete(:proxy) || parent.model)
+ self.model = (proxy || parent.model)
self.owner = parent.owner
self.name = parent.name
self.query = parent.query.dup
else
raise "View cannot be initialized without a parent Model or View"
@@ -269,11 +271,11 @@
def skip(value = 0)
update_query(:skip => value)
end
# Use the reduce function on the view. If none is available this method
- # will fail.
+ # will fail.
def reduce
raise "Cannot reduce a view without a reduce method" unless can_reduce?
update_query(:reduce => true, :include_docs => nil)
end
@@ -299,20 +301,31 @@
update_query.include_docs!
end
### Special View Filter Methods
+ # Allow the results of a query to be provided "stale". Setting to 'ok'
+ # will disable all view updates for the query.
+ # When 'update_after' is provided the index will be update after the
+ # result has been returned.
+ def stale(value)
+ unless (['ok', 'update_after'].include?(value.to_s))
+ raise "View#stale can only be set with 'ok' or 'update_after'."
+ end
+ update_query(:stale => value.to_s)
+ end
+
# Specify the database the view should use. If not defined,
# an attempt will be made to load its value from the model.
def database(value)
update_query(:database => value)
end
# Set the view's proxy that will be used instead of the model
# for any future searches. As soon as this enters the
- # new object's initializer it will be removed and replace
- # the model object.
+ # new view's initializer it will be removed and set as the model
+ # object.
#
# See the Proxyable mixin for more details.
#
def proxy(value)
update_query(:proxy => value)
@@ -378,17 +391,13 @@
def include_docs?
!!query[:include_docs]
end
def update_query(new_query = {})
- self.class.new(self, new_query)
+ self.class.new(design_doc, self, new_query)
end
- def design_doc
- model.design_doc
- end
-
def can_reduce?
!design_doc['views'][name]['reduce'].blank?
end
def use_database
@@ -400,31 +409,37 @@
raise "Database must be defined in model or view!" if use_database.nil?
# Remove the reduce value if its not needed to prevent CouchDB errors
query.delete(:reduce) unless can_reduce?
- model.save_design_doc(use_database)
+ design_doc.sync(use_database)
- self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
+ self.result = design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?})
end
# Class Methods
class << self
- # Simplified view creation. A new view will be added to the
- # provided model's design document using the name and options.
+
+ def define_and_create(design_doc, name, opts = {})
+ define(design_doc, name, opts)
+ create_model_methods(design_doc, name, opts)
+ end
+
+ # Simplified view definition. A new view will be added to the
+ # provided design document using the name and options.
#
# If the view name starts with "by_" and +:by+ is not provided in
# the options, the new view's map method will be interpreted and
# generated automatically. For example:
#
- # View.create(Meeting, "by_date_and_name")
+ # View.define(Meeting, design, "by_date_and_name")
#
# Will create a view that searches by the date and name properties.
# Explicity setting the attributes to use is possible using the
# +:by+ option. For example:
#
- # View.create(Meeting, "by_date_and_name", :by => [:date, :firstname, :lastname])
+ # View.define(Meeting, design, "by_date_and_name", :by => [:date, :firstname, :lastname])
#
# The view name is the same, but three keys would be used in the
# subsecuent index.
#
# By default, a check is made on each of the view's keys to ensure they
@@ -435,45 +450,73 @@
#
# Conversely, keys are not checked to see if they are empty or blank. If you'd
# like to enable this, set the <tt>:allow_blank</tt> option to false. The default
# is true, empty strings are permited in the indexes.
#
- def create(model, name, opts = {})
+ def define(design_doc, name, opts = {})
+ # Don't create the map or reduce method if auto updates are disabled
+ if design_doc.auto_update
+ model = design_doc.model
+ # Is this an all view?
+ if name.to_s == 'all'
+ opts[:map] = <<-EOF
+ function(doc) {
+ if (doc['#{model.model_type_key}'] == '#{model.to_s}') {
+ emit(doc._id, null);
+ }
+ }
+ EOF
+ elsif !opts[:map]
+ if opts[:by].nil? && name.to_s =~ /^by_(.+)/
+ opts[:by] = $1.split(/_and_/)
+ end
- unless opts[:map]
- if opts[:by].nil? && name.to_s =~ /^by_(.+)/
- opts[:by] = $1.split(/_and_/)
- end
+ raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
- raise "View cannot be created without recognised name, :map or :by options" if opts[:by].nil?
+ opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
+ opts[:guards] ||= []
+ opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
- opts[:allow_blank] = opts[:allow_blank].nil? ? true : opts[:allow_blank]
- opts[:guards] ||= []
- opts[:guards].push "(doc['#{model.model_type_key}'] == '#{model.to_s}')"
-
- keys = opts[:by].map{|o| "doc['#{o}']"}
- emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
- opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
- opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
- opts[:map] = <<-EOF
- function(doc) {
- if (#{opts[:guards].join(' && ')}) {
- emit(#{emit}, 1);
+ keys = opts[:by].map{|o| "doc['#{o}']"}
+ emit = keys.length == 1 ? keys.first : "[#{keys.join(', ')}]"
+ opts[:guards] += keys.map{|k| "(#{k} != null)"} unless opts[:allow_nil]
+ opts[:guards] += keys.map{|k| "(#{k} != '')"} unless opts[:allow_blank]
+ opts[:map] = <<-EOF
+ function(doc) {
+ if (#{opts[:guards].join(' && ')}) {
+ emit(#{emit}, 1);
+ }
}
- }
- EOF
- opts[:reduce] = <<-EOF
- function(key, values, rereduce) {
- return sum(values);
- }
- EOF
+ EOF
+ opts[:reduce] = <<-EOF
+ function(key, values, rereduce) {
+ return sum(values);
+ }
+ EOF
+ end
+ else
+ # Assume there is always a map method
+ opts[:map] ||= true
end
- model.design_doc['views'] ||= {}
- view = model.design_doc['views'][name.to_s] = { }
+ design_doc['views'] ||= {}
+ view = design_doc['views'][name.to_s] = { }
view['map'] = opts[:map]
view['reduce'] = opts[:reduce] if opts[:reduce]
view
+ end
+
+
+ def create_model_methods(design_doc, name, opts = {})
+ method = design_doc.method_name
+ design_doc.model.instance_eval <<-EOS, __FILE__, __LINE__ + 1
+ def #{name}(opts = {})
+ #{method}.view('#{name}', opts)
+ end
+ def find_#{name}(*key)
+ #{name}.key(*key).first()
+ end
+ EOS
end
end
end