lib/aqua/store/couch_db/design_document.rb in aqua-0.1.6 vs lib/aqua/store/couch_db/design_document.rb in aqua-0.2.0
- old
+ new
@@ -29,13 +29,21 @@
alias :document_initialize :initialize
def initialize( hash={} )
hash = Mash.new( hash ) unless hash.empty?
self.id = hash.delete(:name) if hash[:name]
- document_initialize( hash )
- end
+ document_initialize( hash ) # TODO: can't this just be a call to super?
+ end
+ def do_rev( hash )
+ # TODO: This is a temp hack to deal with loading the right revision number so a design doc
+ # can be updated from the document. Without this hack, the rev is nil, and there is a conflict.
+
+ hash.delete(:rev) # This is omited to aleviate confusion
+ # hash.delete(:_rev) # CouchDB determines _rev attribute
+ end
+
# couchdb database url for the design document
# @return [String] representing CouchDB uri for document
# @api public
def uri
raise ArgumentError, 'DesignDocument must have a name' if name.nil? || name.empty?
@@ -47,9 +55,125 @@
# @param [Hash] Result returned by CouchDB document save
# @api private
def update_version( result )
self.id = result['id'].gsub(/\A_design\//, '')
self.rev = result['rev']
+ end
+
+ # Gets a design document by name.
+ # @param [String] Id/Name of design document
+ # @return [Aqua::Store::CouchDB::DesignDocument]
+ # @api public
+ def self.get( name )
+ design = CouchDB.get( "#{database.uri}/_design/#{CGI.escape(name)}" )
+ new( design )
+ end
+
+ # VIEWS --------------------
+
+ # An array of indexed views for the design document.
+ # @return [Array]
+ # @api public
+ def views
+ self[:views] ||= Mash.new
+ end
+
+ # Adds or updates a view with the given options
+ #
+ # @param [String, Hash] Name of the view, or options hash
+ # @option arg [String] :name The view name, required
+ # @option arg [String] :map Javascript map function, optional
+ # @option arg [String] :reduce Javascript reduce function, optional
+ #
+ # @return [Mash] Map/Reduce mash of javascript functions
+ #
+ # @example
+ # design_doc << 'attribute_name'
+ # design_doc << {:name => 'attribute_name', :map => 'function(doc){ ... }'}
+ #
+ # @api public
+ def <<( arg )
+ # handle different argument options
+ if [String, Symbol].include?( arg.class )
+ view_name = arg
+ opts = {}
+ elsif arg.class.ancestors.include?( Hash )
+ opts = Mash.new( arg )
+ view_name = opts.delete( :name )
+ raise ArgumentError, 'Option must include a :name that is the view\'s name' unless view_name
+ else
+ raise ArgumentError, "Must be a string or Hash like object of options"
+ end
+
+ # build the map/reduce query
+ map = opts[:map]
+ reduce = opts[:reduce]
+ views # to initialize self[:views]
+ self[:views][view_name] = {
+ :map => map || build_map( view_name, opts[:class_constraint] ),
+ }
+ self[:views][view_name][:reduce] = reduce if reduce
+ self[:views][view_name]
+ end
+
+ alias :add :<<
+
+ def add!( arg )
+ self << arg
+ save!
+ end
+
+ private
+ # Builds a generic map assuming that the view_name is the name of a document attribute.
+ # @param [String, Symbol] Name of document attribute
+ # @param [Class, String] Optional constraint on to limit view to a given class
+ # @return [String] Javascript map function
+ #
+ # @api private
+ def build_map( view_name, class_constraint=nil )
+ class_constraint = if class_constraint.class == Class
+ " && doc['type'] == '#{class_constraint}'"
+ elsif class_constraint.class == String
+ " && #{class_constraint}"
+ end
+ "function(doc) {
+ if( doc['#{view_name}'] #{class_constraint}){
+ emit( doc['#{view_name}'], 1 );
+ }
+ }"
+ end
+ public
+
+ # group=true Version 0.8.0 and forward
+ # group_level=int
+ # reduce=false Trunk only (0.9)
+
+ def query( view_name, opts={} )
+ opts = Mash.new( opts ) unless opts.empty?
+ doc_class = opts[:document_class]
+
+ params = []
+ params << 'include_docs=true' unless (opts[:select] && opts[:select] != 'all')
+ # TODO: this is according to couchdb really inefficent with large sets of data.
+ # A better way would involve, using start and end keys with limit. But this
+ # is a really hard one to figure with jumping around to different pages
+ params << "skip=#{opts[:offset]}" if opts[:offset]
+ params << "limit=#{opts[:limit]}" if opts[:limit]
+ params << "key=#{opts[:equals]}" if opts[:equals]
+ if opts[:order].to_s == 'desc' || opts[:order].to_s == 'descending'
+ desc = true
+ params << "descending=true"
+ end
+ if opts[:range] && opts[:range].size == 2
+ params << "startkey=#{opts[:range][desc == true ? 1 : 0 ]}"
+ params << "endkey=#{opts[:range][desc == true ? 0 : 1]}"
+ end
+
+ query_uri = "#{uri}/_view/#{CGI.escape(view_name.to_s)}?"
+ query_uri << params.join('&')
+
+ result = CouchDB.get( query_uri )
+ opts[:reduced] ? result['rows'].first['value'] : ResultSet.new( result, doc_class )
end
end
end
end
\ No newline at end of file