Skip to content

Commit

Permalink
Added support for views that emit 1 rather than the doc as a val.
Browse files Browse the repository at this point in the history
This is now the default behaviour. It's required to keep view index size manageable.
  • Loading branch information
paulcarey committed Apr 9, 2010
1 parent 9329e26 commit bce16e6
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 9 deletions.
1 change: 1 addition & 0 deletions lib/relaxdb.rb
Expand Up @@ -38,6 +38,7 @@
require 'relaxdb/relaxdb' require 'relaxdb/relaxdb'
require 'relaxdb/server' require 'relaxdb/server'
require 'relaxdb/uuid_generator' require 'relaxdb/uuid_generator'
require 'relaxdb/view_by_delegator'
require 'relaxdb/view_object' require 'relaxdb/view_object'
require 'relaxdb/view_result' require 'relaxdb/view_result'
require 'relaxdb/view_uploader' require 'relaxdb/view_uploader'
Expand Down
46 changes: 42 additions & 4 deletions lib/relaxdb/document.rb
Expand Up @@ -24,6 +24,9 @@ class Document
class_inheritable_accessor :__view_docs_by_list__ class_inheritable_accessor :__view_docs_by_list__
self.__view_docs_by_list__ = [] self.__view_docs_by_list__ = []


class_inheritable_accessor :__view_by_list__
self.__view_by_list__ = []

class_inheritable_accessor :belongs_to_rels, :reader => true class_inheritable_accessor :belongs_to_rels, :reader => true
self.belongs_to_rels = {} self.belongs_to_rels = {}


Expand Down Expand Up @@ -523,7 +526,7 @@ def after_save
end end


# #
# Creates the corresponding view and stores it in CouchDB # Creates the corresponding view, emitting the doc as the val
# Adds by_ and paginate_by_ methods to the class # Adds by_ and paginate_by_ methods to the class
# #
def self.view_docs_by *atts def self.view_docs_by *atts
Expand All @@ -539,11 +542,11 @@ def self.view_docs_by *atts
define_method by_name do |*params| define_method by_name do |*params|
view_name = "#{self.name}_#{by_name}" view_name = "#{self.name}_#{by_name}"
if params.empty? if params.empty?
res = RelaxDB.rf_view view_name, opts RelaxDB.rf_view view_name, opts
elsif params[0].is_a? Hash elsif params[0].is_a? Hash
res = RelaxDB.rf_view view_name, opts.merge(params[0]) RelaxDB.rf_view view_name, opts.merge(params[0])
else else
res = RelaxDB.rf_view(view_name, :key => params[0]).first RelaxDB.rf_view(view_name, :key => params[0]).first
end end
end end
end end
Expand All @@ -559,6 +562,37 @@ def self.view_docs_by *atts
end end
end end



#
# Creates the corresponding view, emitting 1 as the val
# Adds a by_ method to this class, but does not add a
# paginate method.
#
def self.view_by *atts
opts = atts.last.is_a?(Hash) ? atts.pop : {}
opts = opts.merge :raw => true, :reduce => false
__view_by_list__ << atts

if RelaxDB.create_views?
ViewCreator.by_att_list([self.name], *atts).add_to_design_doc
end

by_name = "by_#{atts.join "_and_"}"
meta_class.instance_eval do
define_method by_name do |*params|
view_name = "#{self.name}_#{by_name}"
if params.empty?
ViewByDelegator.new(RelaxDB.doc_ids(view_name, opts))
elsif params[0].is_a? Hash
ViewByDelegator.new(RelaxDB.doc_ids(view_name, opts.merge(params[0])))
else
ViewByDelegator.new(
RelaxDB.doc_ids(view_name, opts.merge(:key => params[0]))).load!.first
end
end
end
end

# Create a view allowing all instances of a particular class to be retreived # Create a view allowing all instances of a particular class to be retreived
def self.create_all_by_class_view def self.create_all_by_class_view
ViewCreator.all.add_to_design_doc if RelaxDB.create_views? ViewCreator.all.add_to_design_doc if RelaxDB.create_views?
Expand Down Expand Up @@ -589,6 +623,10 @@ def self.create_views chain
__view_docs_by_list__.each do |atts| __view_docs_by_list__.each do |atts|
ViewCreator.docs_by_att_list(@hierarchy, *atts).add_to_design_doc ViewCreator.docs_by_att_list(@hierarchy, *atts).add_to_design_doc
end end

__view_by_list__.each do |atts|
ViewCreator.by_att_list(@hierarchy, *atts).add_to_design_doc
end
end end
end end


Expand Down
11 changes: 11 additions & 0 deletions lib/relaxdb/view_by_delegator.rb
@@ -0,0 +1,11 @@
module RelaxDB

class ViewByDelegator < DelegateClass(Array)

def load!
RelaxDB.load! self.to_a
end

end

end
13 changes: 10 additions & 3 deletions lib/relaxdb/views.rb
Expand Up @@ -17,6 +17,14 @@ def self.all(kls)
end end


def self.docs_by_att_list(kls, *atts) def self.docs_by_att_list(kls, *atts)
create_by_att_list "doc", "_count", kls, *atts
end

def self.by_att_list(kls, *atts)
create_by_att_list 1, "_sum", kls, *atts
end

def self.create_by_att_list emit_val, reduce_func, kls, *atts
class_name = kls[0] class_name = kls[0]
key = atts.map { |a| "doc.#{a}" }.join(", ") key = atts.map { |a| "doc.#{a}" }.join(", ")
key = atts.size > 1 ? key.sub(/^/, "[").sub(/$/, "]") : key key = atts.size > 1 ? key.sub(/^/, "[").sub(/$/, "]") : key
Expand All @@ -26,15 +34,14 @@ def self.docs_by_att_list(kls, *atts)
function(doc) { function(doc) {
var class_match = #{kls_check kls} var class_match = #{kls_check kls}
if (class_match && #{prop_check}) { if (class_match && #{prop_check}) {
emit(#{key}, doc); emit(#{key}, #{emit_val});
} }
} }
QUERY QUERY


view_name = "#{class_name}_by_" << atts.join("_and_") view_name = "#{class_name}_by_" << atts.join("_and_")
View.new view_name, map, "_count" View.new view_name, map, reduce_func
end end



def self.has_n(client_class, relationship, target_class, relationship_to_client) def self.has_n(client_class, relationship, target_class, relationship_to_client)
map = <<-QUERY map = <<-QUERY
Expand Down
4 changes: 2 additions & 2 deletions relaxdb.gemspec
Expand Up @@ -35,6 +35,7 @@ Gem::Specification.new do |s|
"lib/relaxdb/uuid_generator.rb", "lib/relaxdb/uuid_generator.rb",
"lib/relaxdb/taf2_curb_server.rb", "lib/relaxdb/taf2_curb_server.rb",
"lib/relaxdb/validators.rb", "lib/relaxdb/validators.rb",
"lib/relaxdb/view_by_delegator",
"lib/relaxdb/view_object.rb", "lib/relaxdb/view_object.rb",
"lib/relaxdb/view_result.rb", "lib/relaxdb/view_result.rb",
"lib/relaxdb/view_uploader.rb", "lib/relaxdb/view_uploader.rb",
Expand All @@ -61,8 +62,7 @@ Gem::Specification.new do |s|
"spec/spec_helper.rb", "spec/spec_helper.rb",
"spec/spec_models.rb", "spec/spec_models.rb",
"spec/view_docs_by_spec.rb", "spec/view_docs_by_spec.rb",
"spec/view_object_spec.rb", "spec/view_object_spec.rb"]
"spec/view_spec.rb"]
s.bindir = "bin" s.bindir = "bin"
s.autorequire = "relaxdb" s.autorequire = "relaxdb"
s.add_dependency "extlib", ">= 0.9.4" # removed ", runtime" as was failing locally s.add_dependency "extlib", ">= 0.9.4" # removed ", runtime" as was failing locally
Expand Down
71 changes: 71 additions & 0 deletions scratch/view_by_bench.rb
@@ -0,0 +1,71 @@
require 'benchmark'

$:.unshift(File.dirname(__FILE__) + "/../lib")
require 'relaxdb'
require File.dirname(__FILE__) + "/../spec/spec_models"

RelaxDB.configure :host => "localhost", :port => 5984, :design_doc => "spec_doc"

RelaxDB.delete_db "relaxdb_spec" rescue :ok
RelaxDB.use_db "relaxdb_spec"
RelaxDB.replicate_db "relaxdb_spec_base", "relaxdb_spec"


docs = (1..100).map { |i| Primitives.new :_id => "id#{i}", :str => i.to_s }
RelaxDB.bulk_save! *docs

count = 100

Benchmark.bm do |x|

# Before a delegator existed
# x.report("simple") do
# count.times do
# doc_ids = Primitives.by_str
# RelaxDB.load! doc_ids
# end
# end

x.report("delegator") do
count.times do
docs = Primitives.by_str.load!
end
end

end

# for 1000 docs and count = 100
# user system total real
# simple 131.520000 2.930000 134.450000 (174.333752)

# for 100 docs and count = 1000
# user system total real
# simple 121.880000 3.470000 125.350000 (161.864292)

#
# Take Ruby out of the equation and we get...
#
# time for i in {1..100}
# do
# curl -s 'localhost:5984/relaxdb_spec/_design/spec_doc/_view/Primitives_by_str?reduce=false' > /dev/null
# curl -s -X POST 'localhost:5984/relaxdb_spec/_all_docs?include_docs=true' -d '{"keys":["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10","id11","id12","id13","id14","id15","id16","id17","id18","id19","id20","id21","id22","id23","id24","id25","id26","id27","id28","id29","id30","id31","id32","id33","id34","id35","id36","id37","id38","id39","id40","id41","id42","id43","id44","id45","id46","id47","id48","id49","id50","id51","id52","id53","id54","id55","id56","id57","id58","id59","id60","id61","id62","id63","id64","id65","id66","id67","id68","id69","id70","id71","id72","id73","id74","id75","id76","id77","id78","id79","id80","id81","id82","id83","id84","id85","id86","id87","id88","id89","id90","id91","id92","id93","id94","id95","id96","id97","id98","id99","id100"]}' > /dev/null
# done
#
# for 100 docs and count = 1000 via curl and bash
#
# real 0m6.177s
# user 0m0.618s
# sys 0m1.066s


# for 100 docs and count = 100
#
# user system total real
# simple 12.240000 0.350000 12.590000 ( 16.987776)
#
# user system total real
# delegator 12.300000 0.350000 12.650000 ( 16.810736)

# Conclusion - if a penalty exists for using a delegator, it's minimal
# The simplification offered by a delegator to client code is well worth it
#
2 changes: 2 additions & 0 deletions spec/spec_models.rb
Expand Up @@ -22,6 +22,8 @@ class Primitives < RelaxDB::Document
property :empty property :empty


view_docs_by :num view_docs_by :num

view_by :str


end end


Expand Down
47 changes: 47 additions & 0 deletions spec/view_by_spec.rb
@@ -0,0 +1,47 @@
require File.dirname(__FILE__) + '/spec_helper.rb'
require File.dirname(__FILE__) + '/spec_models.rb'

describe "view_by" do

before(:each) do
setup_test_db

docs = (1..10).map { |i| Primitives.new :_id => "id#{i}", :str => i.to_s }
RelaxDB.bulk_save! *docs
end

it "should return an array of doc ids for a key" do
docs = Primitives.by_str :key => "5"
docs.first.should == "id5"
end

it "should obey startkey and endkey directives" do
docs = Primitives.by_str :startkey => "3", :endkey => "6"
docs.should == %w(id3 id4 id5 id6)
end

it "should return all when none specified" do
docs = Primitives.by_str
docs.size.should == 10
end

describe "delegator" do

it "should load the returned doc ids" do
docs = Primitives.by_str :key => "5"
docs.load!.first.str.should == "5"
end

it "should load the doc for a single param" do
res = Primitives.by_str "8"
res.str.should == "8"
end

it "should return the delegator when no params given" do
docs = Primitives.by_str.load!
docs.map { |d| d.str }.join.length.should == 11
end

end

end

0 comments on commit bce16e6

Please sign in to comment.