move to class_inheritable_accessor

This commit is contained in:
Chris Anderson 2008-10-02 23:30:41 -07:00
parent c170008deb
commit 355d408730
2 changed files with 96 additions and 39 deletions

View file

@ -1,3 +1,5 @@
require 'rubygems'
require 'extlib'
require 'digest/md5' require 'digest/md5'
# = CouchRest::Model - ORM, the CouchDB way # = CouchRest::Model - ORM, the CouchDB way
@ -62,20 +64,26 @@ module CouchRest
end end
end end
class << self # this is the CouchRest::Database that model classes will use unless
# this is the CouchRest::Database that model classes will use unless # they override it with <tt>use_database</tt>
# they override it with <tt>use_database</tt> cattr_accessor :default_database
attr_accessor :default_database
attr_accessor :template
class_inheritable_accessor :casts
class_inheritable_accessor :default_obj
class_inheritable_accessor :class_database
class_inheritable_accessor :generated_design_doc
class_inheritable_accessor :design_doc_slug_cache
class_inheritable_accessor :design_doc_fresh
class << self
# override the CouchRest::Model-wide default_database # override the CouchRest::Model-wide default_database
def use_database db def use_database db
@database = db self.class_database = db
end end
# returns the CouchRest::Database instance that this class uses # returns the CouchRest::Database instance that this class uses
def database def database
@database || CouchRest::Model.default_database self.class_database || CouchRest::Model.default_database
end end
# load a document from the database # load a document from the database
@ -84,18 +92,21 @@ module CouchRest
new(doc) new(doc)
end end
def all opts = {}
view_name = "#{design_doc_slug}/all"
raw = opts.delete(:raw)
view = fetch_view(view_name, opts)
process_view_results view, raw
end
# Cast a field as another class. The class must be happy to have the # Cast a field as another class. The class must be happy to have the
# field's primitive type as the argument to it's constucture. Classes # field's primitive type as the argument to it's constucture. Classes
# which inherit from CouchRest::Model are happy to act as sub-objects # which inherit from CouchRest::Model are happy to act as sub-objects
# for any fields that are stored in JSON as object (and therefore are # for any fields that are stored in JSON as object (and therefore are
# parsed from the JSON as Ruby Hashes). # parsed from the JSON as Ruby Hashes).
def cast field, opts = {} def cast field, opts = {}
@casts ||= {} self.casts ||= {}
@casts[field.to_s] = opts self.casts[field.to_s] = opts
end
def casts
@casts
end end
# Defines methods for reading and writing from fields in the document. # Defines methods for reading and writing from fields in the document.
@ -128,11 +139,11 @@ module CouchRest
end end
def default def default
@default self.default_obj
end end
def set_default hash def set_default hash
@default = hash self.default_obj = hash
end end
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
@ -236,7 +247,7 @@ module CouchRest
type = self.to_s type = self.to_s
method_name = "by_#{keys.join('_and_')}" method_name = "by_#{keys.join('_and_')}"
@@design_doc ||= default_design_doc self.generated_design_doc ||= default_design_doc
if opts[:map] if opts[:map]
view = {} view = {}
@ -245,7 +256,7 @@ module CouchRest
view['reduce'] = opts.delete(:reduce) view['reduce'] = opts.delete(:reduce)
opts[:reduce] = false opts[:reduce] = false
end end
@@design_doc['views'][method_name] = view generated_design_doc['views'][method_name] = view
else else
doc_keys = keys.collect{|k|"doc['#{k}']"} doc_keys = keys.collect{|k|"doc['#{k}']"}
key_protection = doc_keys.join(' && ') key_protection = doc_keys.join(' && ')
@ -257,30 +268,24 @@ module CouchRest
} }
} }
JAVASCRIPT JAVASCRIPT
@@design_doc['views'][method_name] = { generated_design_doc['views'][method_name] = {
'map' => map_function 'map' => map_function
} }
end end
@@design_doc_fresh = false self.design_doc_fresh = false
self.meta_class.instance_eval do self.meta_class.instance_eval do
define_method method_name do |*args| define_method method_name do |*args|
query = opts.merge(args[0] || {}) query = opts.merge(args[0] || {})
query[:raw] = true if query[:reduce] query[:raw] = true if query[:reduce]
unless @@design_doc_fresh unless design_doc_fresh
refresh_design_doc refresh_design_doc
end end
raw = query.delete(:raw) raw = query.delete(:raw)
view_name = "#{design_doc_slug}/#{method_name}" view_name = "#{design_doc_slug}/#{method_name}"
view = fetch_view(view_name, query) view = fetch_view(view_name, query)
if raw process_view_results view, raw
view
else
# TODO this can be optimized once the include-docs patch is applied
view['rows'].collect{|r|new(database.get(r['id']))}
end
end end
end end
end end
@ -292,6 +297,15 @@ module CouchRest
private private
def process_view_results view, raw=false
if raw
view
else
# TODO this can be optimized once the include-docs patch is applied
view['rows'].collect{|r|new(database.get(r['id']))}
end
end
def fetch_view view_name, opts def fetch_view view_name, opts
retryable = true retryable = true
begin begin
@ -309,19 +323,27 @@ module CouchRest
end end
def design_doc_slug def design_doc_slug
return @design_doc_slug if @design_doc_slug && @@design_doc_fresh return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
funcs = [] funcs = []
@@design_doc['views'].each do |name, view| generated_design_doc['views'].each do |name, view|
funcs << "#{name}/#{view}" funcs << "#{name}/#{view['map']}#{view['reduce']}"
end end
md5 = Digest::MD5.hexdigest(funcs.sort.join('')) md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
@design_doc_slug = "#{self.to_s}-#{md5}" self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
end end
def default_design_doc def default_design_doc
{ {
"language" => "javascript", "language" => "javascript",
"views" => {} "views" => {
'all' => {
'map' => "function(doc) {
if (doc['couchrest-type'] == '#{self.to_s}') {
emit(null,null);
}
}"
}
}
} }
end end
@ -329,21 +351,19 @@ module CouchRest
did = "_design/#{design_doc_slug}" did = "_design/#{design_doc_slug}"
saved = database.get(did) rescue nil saved = database.get(did) rescue nil
if saved if saved
@@design_doc['views'].each do |name, view| generated_design_doc['views'].each do |name, view|
saved['views'][name] = view saved['views'][name] = view
end end
database.save(saved) database.save(saved)
else else
@@design_doc['_id'] = did generated_design_doc['_id'] = did
database.save(@@design_doc) database.save(generated_design_doc)
end end
@@design_doc_fresh = true self.design_doc_fresh = true
end end
end # class << self end # class << self
# returns the database used by this model's class # returns the database used by this model's class
def database def database
self.class.database self.class.database

View file

@ -29,6 +29,7 @@ class Course < CouchRest::Model
key_accessor :title key_accessor :title
cast :questions, :as => [Question] cast :questions, :as => [Question]
cast :professor, :as => Person cast :professor, :as => Person
view_by :title
end end
class Article < CouchRest::Model class Article < CouchRest::Model
@ -177,6 +178,25 @@ describe CouchRest::Model do
end end
end end
describe "finding all instances of a model" do
before(:all) do
WithTemplate.new('important-field' => '1').save
WithTemplate.new('important-field' => '2').save
WithTemplate.new('important-field' => '3').save
WithTemplate.new('important-field' => '4').save
end
it "should make the design doc" do
WithTemplate.all
puts d = WithTemplate.design_doc.to_json
d.should == 'xs'
end
it "should find all" do
rs = WithTemplate.all :raw => true
rs.should == 'x'
rs.length.should == 4
end
end
describe "getting a model with a subobject field" do describe "getting a model with a subobject field" do
before(:all) do before(:all) do
course_doc = { course_doc = {
@ -356,6 +376,23 @@ describe CouchRest::Model do
end end
end end
describe "another model with a simple view" do
before(:all) do
Course.database.delete! rescue nil
@db = @cr.create_db(TESTDB) rescue nil
Course.new(:title => 'aaa').save
Course.new(:title => 'bbb').save
end
it "should make the design doc" do
doc = Course.design_doc
doc['views']['all']['map'].should include('Course')
end
it "should get them" do
rs = Course.by_title
rs.length.should == 2
end
end
describe "a model with a compound key view" do describe "a model with a compound key view" do
before(:all) do before(:all) do
written_at = Time.now - 24 * 3600 * 7 written_at = Time.now - 24 * 3600 * 7