709 lines
20 KiB
Ruby
709 lines
20 KiB
Ruby
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
|
|
class Basic < CouchRest::Model
|
|
end
|
|
|
|
class BasicWithValidation < CouchRest::Model
|
|
|
|
before :save, :validate
|
|
key_accessor :name
|
|
|
|
def validate
|
|
throw(:halt, false) unless name
|
|
end
|
|
end
|
|
|
|
class WithTemplateAndUniqueID < CouchRest::Model
|
|
unique_id do |model|
|
|
model['important-field']
|
|
end
|
|
set_default({
|
|
:preset => 'value',
|
|
'more-template' => [1,2,3]
|
|
})
|
|
key_accessor :preset
|
|
end
|
|
|
|
class Question < CouchRest::Model
|
|
key_accessor :q, :a
|
|
couchrest_type = 'Question'
|
|
end
|
|
|
|
class Person < CouchRest::Model
|
|
key_accessor :name
|
|
def last_name
|
|
name.last
|
|
end
|
|
end
|
|
|
|
class Course < CouchRest::Model
|
|
key_accessor :title
|
|
cast :questions, :as => ['Question']
|
|
cast :professor, :as => 'Person'
|
|
cast :final_test_at, :as => 'Time'
|
|
view_by :title
|
|
view_by :dept, :ducktype => true
|
|
end
|
|
|
|
class Article < CouchRest::Model
|
|
use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
|
|
unique_id :slug
|
|
|
|
view_by :date, :descending => true
|
|
view_by :user_id, :date
|
|
|
|
view_by :tags,
|
|
:map =>
|
|
"function(doc) {
|
|
if (doc['couchrest-type'] == 'Article' && doc.tags) {
|
|
doc.tags.forEach(function(tag){
|
|
emit(tag, 1);
|
|
});
|
|
}
|
|
}",
|
|
:reduce =>
|
|
"function(keys, values, rereduce) {
|
|
return sum(values);
|
|
}"
|
|
|
|
key_writer :date
|
|
key_reader :slug, :created_at, :updated_at
|
|
key_accessor :title, :tags
|
|
|
|
timestamps!
|
|
|
|
before(:save, :generate_slug_from_title)
|
|
def generate_slug_from_title
|
|
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
|
|
end
|
|
end
|
|
|
|
class Player < CouchRest::Model
|
|
unique_id :email
|
|
|
|
key_accessor :email, :name, :str, :coord, :int, :con, :spirit, :level, :xp, :points, :coins, :date, :items, :loc
|
|
|
|
view_by :name, :descending => true
|
|
view_by :loc
|
|
|
|
timestamps!
|
|
end
|
|
|
|
class Event < CouchRest::Model
|
|
key_accessor :subject, :occurs_at
|
|
|
|
cast :occurs_at, :as => 'Time', :send => 'parse'
|
|
end
|
|
|
|
describe "save bug" do
|
|
before(:each) do
|
|
CouchRest::Model.default_database = reset_test_db!
|
|
end
|
|
|
|
it "should fix" do
|
|
@p = Player.new
|
|
@p.email = 'insane@fakestreet.com'
|
|
@p.save
|
|
end
|
|
end
|
|
|
|
|
|
describe CouchRest::Model do
|
|
before(:all) do
|
|
@cr = CouchRest.new(COUCHHOST)
|
|
@db = @cr.database(TESTDB)
|
|
@db.delete! rescue nil
|
|
@db = @cr.create_db(TESTDB) rescue nil
|
|
@adb = @cr.database('couchrest-model-test')
|
|
@adb.delete! rescue nil
|
|
CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
|
|
CouchRest::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couchrest-test')
|
|
end
|
|
|
|
it "should use the default database" do
|
|
Basic.database.info['db_name'].should == 'couchrest-test'
|
|
end
|
|
|
|
it "should override the default db" do
|
|
Article.database.info['db_name'].should == 'couchrest-model-test'
|
|
end
|
|
|
|
describe "a new model" do
|
|
it "should be a new_record" do
|
|
@obj = Basic.new
|
|
@obj.rev.should be_nil
|
|
@obj.should be_a_new_record
|
|
end
|
|
end
|
|
|
|
describe "a model with key_accessors" do
|
|
it "should allow reading keys" do
|
|
@art = Article.new
|
|
@art['title'] = 'My Article Title'
|
|
@art.title.should == 'My Article Title'
|
|
end
|
|
it "should allow setting keys" do
|
|
@art = Article.new
|
|
@art.title = 'My Article Title'
|
|
@art['title'].should == 'My Article Title'
|
|
end
|
|
end
|
|
|
|
describe "a model with key_writers" do
|
|
it "should allow setting keys" do
|
|
@art = Article.new
|
|
t = Time.now
|
|
@art.date = t
|
|
@art['date'].should == t
|
|
end
|
|
it "should not allow reading keys" do
|
|
@art = Article.new
|
|
t = Time.now
|
|
@art.date = t
|
|
lambda{@art.date}.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "a model with key_readers" do
|
|
it "should allow reading keys" do
|
|
@art = Article.new
|
|
@art['slug'] = 'my-slug'
|
|
@art.slug.should == 'my-slug'
|
|
end
|
|
it "should not allow setting keys" do
|
|
@art = Article.new
|
|
lambda{@art.slug = 'My Article Title'}.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "update attributes without saving" do
|
|
before(:each) do
|
|
a = Article.get "big-bad-danger" rescue nil
|
|
a.destroy if a
|
|
@art = Article.new(:title => "big bad danger")
|
|
@art.save
|
|
end
|
|
it "should work for attribute= methods" do
|
|
@art['title'].should == "big bad danger"
|
|
@art.update_attributes('date' => Time.now, :title => "super danger")
|
|
@art['title'].should == "super danger"
|
|
end
|
|
|
|
it "should flip out if an attribute= method is missing" do
|
|
lambda {
|
|
@art.update_attributes('slug' => "new-slug", :title => "super danger")
|
|
}.should raise_error
|
|
end
|
|
|
|
it "should not change other attributes if there is an error" do
|
|
lambda {
|
|
@art.update_attributes('slug' => "new-slug", :title => "super danger")
|
|
}.should raise_error
|
|
@art['title'].should == "big bad danger"
|
|
end
|
|
|
|
end
|
|
|
|
describe "update attributes" do
|
|
before(:each) do
|
|
a = Article.get "big-bad-danger" rescue nil
|
|
a.destroy if a
|
|
@art = Article.new(:title => "big bad danger")
|
|
@art.save
|
|
end
|
|
it "should save" do
|
|
@art['title'].should == "big bad danger"
|
|
@art.update_attributes('date' => Time.now, :title => "super danger")
|
|
loaded = Article.get @art.id
|
|
loaded['title'].should == "super danger"
|
|
end
|
|
end
|
|
|
|
describe "a model with template values" do
|
|
before(:all) do
|
|
@tmpl = WithTemplateAndUniqueID.new
|
|
end
|
|
it "should have fields set when new" do
|
|
@tmpl.preset.should == 'value'
|
|
end
|
|
end
|
|
|
|
describe "getting a model" do
|
|
before(:all) do
|
|
@art = Article.new(:title => 'All About Getting')
|
|
@art.save
|
|
end
|
|
it "should load and instantiate it" do
|
|
foundart = Article.get @art.id
|
|
foundart.title.should == "All About Getting"
|
|
end
|
|
end
|
|
|
|
describe "getting a model with a subobjects array" do
|
|
before(:all) do
|
|
course_doc = {
|
|
"title" => "Metaphysics 200",
|
|
"questions" => [
|
|
{
|
|
"q" => "Carve the ___ of reality at the ___.",
|
|
"a" => ["beast","joints"]
|
|
},{
|
|
"q" => "Who layed the smack down on Leibniz's Law?",
|
|
"a" => "Willard Van Orman Quine"
|
|
}
|
|
]
|
|
}
|
|
r = Course.database.save course_doc
|
|
@course = Course.get r['id']
|
|
end
|
|
it "should load the course" do
|
|
@course.title.should == "Metaphysics 200"
|
|
end
|
|
it "should instantiate them as such" do
|
|
@course["questions"][0].a[0].should == "beast"
|
|
end
|
|
end
|
|
|
|
describe "finding all instances of a model" do
|
|
before(:all) do
|
|
WithTemplateAndUniqueID.new('important-field' => '1').save
|
|
WithTemplateAndUniqueID.new('important-field' => '2').save
|
|
WithTemplateAndUniqueID.new('important-field' => '3').save
|
|
WithTemplateAndUniqueID.new('important-field' => '4').save
|
|
end
|
|
it "should make the design doc" do
|
|
WithTemplateAndUniqueID.all
|
|
d = WithTemplateAndUniqueID.design_doc
|
|
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
|
end
|
|
it "should find all" do
|
|
rs = WithTemplateAndUniqueID.all
|
|
rs.length.should == 4
|
|
end
|
|
end
|
|
|
|
describe "finding the first instance of a model" do
|
|
before(:each) do
|
|
@db = reset_test_db!
|
|
WithTemplateAndUniqueID.new('important-field' => '1').save
|
|
WithTemplateAndUniqueID.new('important-field' => '2').save
|
|
WithTemplateAndUniqueID.new('important-field' => '3').save
|
|
WithTemplateAndUniqueID.new('important-field' => '4').save
|
|
end
|
|
it "should make the design doc" do
|
|
WithTemplateAndUniqueID.all
|
|
d = WithTemplateAndUniqueID.design_doc
|
|
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
|
end
|
|
it "should find first" do
|
|
rs = WithTemplateAndUniqueID.first
|
|
rs['important-field'].should == "1"
|
|
end
|
|
it "should return nil if no instances are found" do
|
|
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
|
|
WithTemplateAndUniqueID.first.should be_nil
|
|
end
|
|
end
|
|
|
|
describe "getting a model with a subobject field" do
|
|
before(:all) do
|
|
course_doc = {
|
|
"title" => "Metaphysics 410",
|
|
"professor" => {
|
|
"name" => ["Mark", "Hinchliff"]
|
|
},
|
|
"final_test_at" => "2008/12/19 13:00:00 +0800"
|
|
}
|
|
r = Course.database.save course_doc
|
|
@course = Course.get r['id']
|
|
end
|
|
it "should load the course" do
|
|
@course["professor"]["name"][1].should == "Hinchliff"
|
|
end
|
|
it "should instantiate the professor as a person" do
|
|
@course['professor'].last_name.should == "Hinchliff"
|
|
end
|
|
it "should instantiate the final_test_at as a Time" do
|
|
@course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
|
|
end
|
|
end
|
|
|
|
describe "cast keys to any type" do
|
|
before(:all) do
|
|
event_doc = { :subject => "Some event", :occurs_at => Time.now }
|
|
e = Event.database.save event_doc
|
|
|
|
@event = Event.get e['id']
|
|
end
|
|
it "should cast created_at to Time" do
|
|
@event['occurs_at'].should be_an_instance_of(Time)
|
|
end
|
|
end
|
|
|
|
describe "saving a model" do
|
|
before(:all) do
|
|
@obj = Basic.new
|
|
@obj.save.should == true
|
|
end
|
|
|
|
it "should save the doc" do
|
|
doc = @obj.database.get @obj.id
|
|
doc['_id'].should == @obj.id
|
|
end
|
|
|
|
it "should be set for resaving" do
|
|
rev = @obj.rev
|
|
@obj['another-key'] = "some value"
|
|
@obj.save
|
|
@obj.rev.should_not == rev
|
|
end
|
|
|
|
it "should set the id" do
|
|
@obj.id.should be_an_instance_of(String)
|
|
end
|
|
|
|
it "should set the type" do
|
|
@obj['couchrest-type'].should == 'Basic'
|
|
end
|
|
end
|
|
|
|
describe "saving a model with validation hooks added as extlib" do
|
|
before(:all) do
|
|
@obj = BasicWithValidation.new
|
|
end
|
|
|
|
it "save should return false is the model doesn't save as expected" do
|
|
@obj.save.should be_false
|
|
end
|
|
|
|
it "save! should raise and exception if the model doesn't save" do
|
|
lambda{ @obj.save!}.should raise_error("#{@obj.inspect} failed to save")
|
|
end
|
|
|
|
end
|
|
|
|
describe "saving a model with a unique_id configured" do
|
|
before(:each) do
|
|
@art = Article.new
|
|
@old = Article.database.get('this-is-the-title') rescue nil
|
|
Article.database.delete(@old) if @old
|
|
end
|
|
|
|
it "should be a new document" do
|
|
@art.should be_a_new_document
|
|
@art.title.should be_nil
|
|
end
|
|
|
|
it "should require the title" do
|
|
lambda{@art.save}.should raise_error
|
|
@art.title = 'This is the title'
|
|
@art.save.should == true
|
|
end
|
|
|
|
it "should not change the slug on update" do
|
|
@art.title = 'This is the title'
|
|
@art.save.should == true
|
|
@art.title = 'new title'
|
|
@art.save.should == true
|
|
@art.slug.should == 'this-is-the-title'
|
|
end
|
|
|
|
it "should raise an error when the slug is taken" do
|
|
@art.title = 'This is the title'
|
|
@art.save.should == true
|
|
@art2 = Article.new(:title => 'This is the title!')
|
|
lambda{@art2.save}.should raise_error
|
|
end
|
|
|
|
it "should set the slug" do
|
|
@art.title = 'This is the title'
|
|
@art.save.should == true
|
|
@art.slug.should == 'this-is-the-title'
|
|
end
|
|
|
|
it "should set the id" do
|
|
@art.title = 'This is the title'
|
|
@art.save.should == true
|
|
@art.id.should == 'this-is-the-title'
|
|
end
|
|
end
|
|
|
|
describe "saving a model with a unique_id lambda" do
|
|
before(:each) do
|
|
@templated = WithTemplateAndUniqueID.new
|
|
@old = WithTemplateAndUniqueID.get('very-important') rescue nil
|
|
@old.destroy if @old
|
|
end
|
|
|
|
it "should require the field" do
|
|
lambda{@templated.save}.should raise_error
|
|
@templated['important-field'] = 'very-important'
|
|
@templated.save.should == true
|
|
end
|
|
|
|
it "should save with the id" do
|
|
@templated['important-field'] = 'very-important'
|
|
@templated.save.should == true
|
|
t = WithTemplateAndUniqueID.get('very-important')
|
|
t.should == @templated
|
|
end
|
|
|
|
it "should not change the id on update" do
|
|
@templated['important-field'] = 'very-important'
|
|
@templated.save.should == true
|
|
@templated['important-field'] = 'not-important'
|
|
@templated.save.should == true
|
|
t = WithTemplateAndUniqueID.get('very-important')
|
|
t.should == @templated
|
|
end
|
|
|
|
it "should raise an error when the id is taken" do
|
|
@templated['important-field'] = 'very-important'
|
|
@templated.save.should == true
|
|
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
|
|
end
|
|
|
|
it "should set the id" do
|
|
@templated['important-field'] = 'very-important'
|
|
@templated.save.should == true
|
|
@templated.id.should == 'very-important'
|
|
end
|
|
end
|
|
|
|
describe "a model with timestamps" do
|
|
before(:each) do
|
|
oldart = Article.get "saving-this" rescue nil
|
|
oldart.destroy if oldart
|
|
@art = Article.new(:title => "Saving this")
|
|
@art.save
|
|
end
|
|
it "should set the time on create" do
|
|
(Time.now - @art.created_at).should < 2
|
|
foundart = Article.get @art.id
|
|
foundart.created_at.should == foundart.updated_at
|
|
end
|
|
it "should set the time on update" do
|
|
@art.save
|
|
@art.created_at.should < @art.updated_at
|
|
end
|
|
end
|
|
|
|
describe "a model with simple views and a default param" do
|
|
before(:all) do
|
|
written_at = Time.now - 24 * 3600 * 7
|
|
@titles = ["this and that", "also interesting", "more fun", "some junk"]
|
|
@titles.each do |title|
|
|
a = Article.new(:title => title)
|
|
a.date = written_at
|
|
a.save
|
|
written_at += 24 * 3600
|
|
end
|
|
end
|
|
|
|
it "should have a design doc" do
|
|
Article.design_doc["views"]["by_date"].should_not be_nil
|
|
end
|
|
|
|
it "should save the design doc" do
|
|
Article.by_date #rescue nil
|
|
doc = Article.database.get Article.design_doc.id
|
|
doc['views']['by_date'].should_not be_nil
|
|
end
|
|
|
|
it "should return the matching raw view result" do
|
|
view = Article.by_date :raw => true
|
|
view['rows'].length.should == 4
|
|
end
|
|
|
|
it "should not include non-Articles" do
|
|
Article.database.save({"date" => 1})
|
|
view = Article.by_date :raw => true
|
|
view['rows'].length.should == 4
|
|
end
|
|
|
|
it "should return the matching objects (with default argument :descending => true)" do
|
|
articles = Article.by_date
|
|
articles.collect{|a|a.title}.should == @titles.reverse
|
|
end
|
|
|
|
it "should allow you to override default args" do
|
|
articles = Article.by_date :descending => false
|
|
articles.collect{|a|a.title}.should == @titles
|
|
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
|
|
%w{aaa bbb ddd eee}.each do |title|
|
|
Course.new(:title => title).save
|
|
end
|
|
end
|
|
it "should make the design doc upon first query" do
|
|
Course.by_title
|
|
doc = Course.design_doc
|
|
doc['views']['all']['map'].should include('Course')
|
|
end
|
|
it "should can query via view" do
|
|
# register methods with method-missing, for local dispatch. method
|
|
# missing lookup table, no heuristics.
|
|
view = Course.view :by_title
|
|
designed = Course.by_title
|
|
view.should == designed
|
|
end
|
|
it "should get them" do
|
|
rs = Course.by_title
|
|
rs.length.should == 4
|
|
end
|
|
it "should yield" do
|
|
courses = []
|
|
rs = Course.by_title # remove me
|
|
Course.view(:by_title) do |course|
|
|
courses << course
|
|
end
|
|
courses[0]["doc"]["title"].should =='aaa'
|
|
end
|
|
end
|
|
|
|
|
|
describe "a ducktype view" do
|
|
before(:all) do
|
|
@id = @db.save({:dept => true})['id']
|
|
end
|
|
it "should setup" do
|
|
duck = Course.get(@id) # from a different db
|
|
duck["dept"].should == true
|
|
end
|
|
it "should make the design doc" do
|
|
@as = Course.by_dept
|
|
@doc = Course.design_doc
|
|
@doc["views"]["by_dept"]["map"].should_not include("couchrest")
|
|
end
|
|
it "should not look for class" do |variable|
|
|
@as = Course.by_dept
|
|
@as[0]['_id'].should == @id
|
|
end
|
|
end
|
|
|
|
describe "a model with a compound key view" do
|
|
before(:all) do
|
|
written_at = Time.now - 24 * 3600 * 7
|
|
@titles = ["uniq one", "even more interesting", "less fun", "not junk"]
|
|
@user_ids = ["quentin", "aaron"]
|
|
@titles.each_with_index do |title,i|
|
|
u = i % 2
|
|
a = Article.new(:title => title, :user_id => @user_ids[u])
|
|
a.date = written_at
|
|
a.save
|
|
written_at += 24 * 3600
|
|
end
|
|
end
|
|
it "should create the design doc" do
|
|
Article.by_user_id_and_date rescue nil
|
|
doc = Article.design_doc
|
|
doc['views']['by_date'].should_not be_nil
|
|
end
|
|
it "should sort correctly" do
|
|
articles = Article.by_user_id_and_date
|
|
articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
|
|
'quentin']
|
|
articles[1].title.should == 'not junk'
|
|
end
|
|
it "should be queryable with couchrest options" do
|
|
articles = Article.by_user_id_and_date :count => 1, :startkey => 'quentin'
|
|
articles.length.should == 1
|
|
articles[0].title.should == "even more interesting"
|
|
end
|
|
end
|
|
|
|
describe "with a custom view" do
|
|
before(:all) do
|
|
@titles = ["very uniq one", "even less interesting", "some fun",
|
|
"really junk", "crazy bob"]
|
|
@tags = ["cool", "lame"]
|
|
@titles.each_with_index do |title,i|
|
|
u = i % 2
|
|
a = Article.new(:title => title, :tags => [@tags[u]])
|
|
a.save
|
|
end
|
|
end
|
|
it "should be available raw" do
|
|
view = Article.by_tags :raw => true
|
|
view['rows'].length.should == 5
|
|
end
|
|
|
|
it "should be default to :reduce => false" do
|
|
ars = Article.by_tags
|
|
ars.first.tags.first.should == 'cool'
|
|
end
|
|
|
|
it "should be raw when reduce is true" do
|
|
view = Article.by_tags :reduce => true, :group => true
|
|
view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
|
|
end
|
|
end
|
|
|
|
# TODO: moved to Design, delete
|
|
describe "adding a view" do
|
|
before(:each) do
|
|
Article.by_date
|
|
@design_docs = Article.database.documents :startkey => "_design/",
|
|
:endkey => "_design/\u9999"
|
|
end
|
|
it "should not create a design doc on view definition" do
|
|
Article.view_by :created_at
|
|
newdocs = Article.database.documents :startkey => "_design/",
|
|
:endkey => "_design/\u9999"
|
|
newdocs["rows"].length.should == @design_docs["rows"].length
|
|
end
|
|
it "should create a new design document on view access" do
|
|
Article.view_by :updated_at
|
|
Article.by_updated_at
|
|
newdocs = Article.database.documents :startkey => "_design/",
|
|
:endkey => "_design/\u9999"
|
|
# puts @design_docs.inspect
|
|
# puts newdocs.inspect
|
|
newdocs["rows"].length.should == @design_docs["rows"].length + 1
|
|
end
|
|
end
|
|
|
|
describe "with a lot of designs left around" do
|
|
before(:each) do
|
|
Article.by_date
|
|
Article.view_by :field
|
|
Article.by_field
|
|
end
|
|
it "should clean them up" do
|
|
Article.view_by :stream
|
|
Article.by_stream
|
|
ddocs = Article.all_design_doc_versions
|
|
ddocs["rows"].length.should > 1
|
|
Article.cleanup_design_docs!
|
|
ddocs = Article.all_design_doc_versions
|
|
ddocs["rows"].length.should == 1
|
|
end
|
|
end
|
|
|
|
describe "destroying an instance" do
|
|
before(:each) do
|
|
@obj = Basic.new
|
|
@obj.save.should == true
|
|
end
|
|
it "should return true" do
|
|
result = @obj.destroy
|
|
result.should == true
|
|
end
|
|
it "should be resavable" do
|
|
@obj.destroy
|
|
@obj.rev.should be_nil
|
|
@obj.id.should be_nil
|
|
@obj.save.should == true
|
|
end
|
|
it "should make it go away" do
|
|
@obj.destroy
|
|
lambda{Basic.get(@obj.id)}.should raise_error
|
|
end
|
|
end
|
|
end
|