most of the callbacks are installed, adding view generation
This commit is contained in:
parent
ce3a3258bc
commit
5e0cb81ad7
|
@ -1,3 +1,7 @@
|
||||||
|
require 'rubygems'
|
||||||
|
gem 'extlib'
|
||||||
|
require 'extlib'
|
||||||
|
|
||||||
module CouchRest
|
module CouchRest
|
||||||
module Model
|
module Model
|
||||||
class << self
|
class << self
|
||||||
|
@ -8,8 +12,11 @@ module CouchRest
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
attr_accessor :doc
|
attr_accessor :doc
|
||||||
|
|
||||||
def initialize doc = {}
|
def initialize keys = {}
|
||||||
self.doc = doc
|
self.doc = {}
|
||||||
|
keys.each do |k,v|
|
||||||
|
doc[k.to_s] = v
|
||||||
|
end
|
||||||
unless doc['_id'] && doc['_rev']
|
unless doc['_id'] && doc['_rev']
|
||||||
init_doc
|
init_doc
|
||||||
end
|
end
|
||||||
|
@ -27,7 +34,32 @@ module CouchRest
|
||||||
doc['_rev']
|
doc['_rev']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new_record?
|
||||||
|
!doc['_rev']
|
||||||
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
|
if new_record?
|
||||||
|
create
|
||||||
|
else
|
||||||
|
update
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def create
|
||||||
|
set_uniq_id if respond_to?(:set_uniq_id) # hack
|
||||||
|
save_doc
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
save_doc
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def save_doc
|
||||||
result = database.save doc
|
result = database.save doc
|
||||||
if result['ok']
|
if result['ok']
|
||||||
doc['_id'] = result['id']
|
doc['_id'] = result['id']
|
||||||
|
@ -36,8 +68,6 @@ module CouchRest
|
||||||
result['ok']
|
result['ok']
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def init_doc
|
def init_doc
|
||||||
doc['type'] = self.class.to_s
|
doc['type'] = self.class.to_s
|
||||||
end
|
end
|
||||||
|
@ -53,19 +83,126 @@ module CouchRest
|
||||||
@database || CouchRest::Model.default_database
|
@database || CouchRest::Model.default_database
|
||||||
end
|
end
|
||||||
|
|
||||||
def uniq_id method
|
def get id
|
||||||
before_create do |model|
|
doc = database.get id
|
||||||
model.doc['_id'] = model.send(method)
|
new(doc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def key_accessor *keys
|
||||||
|
key_writer *keys
|
||||||
|
key_reader *keys
|
||||||
|
end
|
||||||
|
|
||||||
|
def key_writer *keys
|
||||||
|
keys.each do |method|
|
||||||
|
key = method.to_s
|
||||||
|
define_method "#{method}=" do |value|
|
||||||
|
doc[key] = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def key_reader *keys
|
||||||
|
keys.each do |method|
|
||||||
|
key = method.to_s
|
||||||
|
define_method method do
|
||||||
|
doc[key]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def timestamps!
|
||||||
|
before(:create) do
|
||||||
|
doc['updated_at'] = doc['created_at'] = Time.now
|
||||||
|
end
|
||||||
|
before(:update) do
|
||||||
|
doc['updated_at'] = Time.now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def uniq_id method
|
||||||
|
define_method :set_uniq_id do
|
||||||
|
doc['_id'] ||= self.send(method)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end # module ClassMethods
|
end # module ClassMethods
|
||||||
|
|
||||||
|
module MagicViews
|
||||||
|
def view_by *keys
|
||||||
|
type = self.to_s
|
||||||
|
doc_keys = keys.collect{|k|"doc['#{k}']"}
|
||||||
|
key_protection = doc_keys.join(' && ')
|
||||||
|
key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
|
||||||
|
map_function = <<-JAVASCRIPT
|
||||||
|
function(doc) {
|
||||||
|
if (doc.type == '#{type}' && #{key_protection}) {
|
||||||
|
emit(#{key_emit}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JAVASCRIPT
|
||||||
|
|
||||||
|
method_name = "by_#{keys.join('_and_')}"
|
||||||
|
|
||||||
|
@@design_doc ||= default_design_doc
|
||||||
|
@@design_doc['views'][method_name] = {
|
||||||
|
'map' => map_function
|
||||||
|
}
|
||||||
|
@@design_doc_fresh = false
|
||||||
|
self.meta_class.instance_eval do
|
||||||
|
define_method method_name do
|
||||||
|
unless @@design_doc_fresh
|
||||||
|
refresh_design_doc
|
||||||
|
end
|
||||||
|
@@design_doc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def design_doc_id
|
||||||
|
"_design/#{self.to_s}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_design_doc
|
||||||
|
{
|
||||||
|
"_id" => design_doc_id,
|
||||||
|
"language" => "javascript",
|
||||||
|
"views" => {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_design_doc
|
||||||
|
saved = database.get(design_doc_id) rescue nil
|
||||||
|
if saved
|
||||||
|
# merge the new views in and save if it needs to be saved
|
||||||
|
else
|
||||||
|
database.save(@@design_doc)
|
||||||
|
end
|
||||||
|
@@design_doc_fresh = true
|
||||||
|
end
|
||||||
|
|
||||||
|
end # module MagicViews
|
||||||
|
|
||||||
|
module Callbacks
|
||||||
|
def self.included(model)
|
||||||
|
model.class_eval <<-EOS, __FILE__, __LINE__
|
||||||
|
include Extlib::Hook
|
||||||
|
register_instance_hooks :save #, :create, :update, :destroy
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
end # module Callbacks
|
||||||
|
|
||||||
# bookkeeping section
|
# bookkeeping section
|
||||||
|
|
||||||
# load the code into the model class
|
# load the code into the model class
|
||||||
def self.included(klass)
|
def self.included(model)
|
||||||
klass.extend ClassMethods
|
model.send(:include, InstanceMethods)
|
||||||
klass.send(:include, InstanceMethods)
|
model.extend ClassMethods
|
||||||
|
model.extend MagicViews
|
||||||
|
model.send(:include, Callbacks)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,20 @@ class Article
|
||||||
include CouchRest::Model
|
include CouchRest::Model
|
||||||
use_database CouchRest.database!('http://localhost:5984/couchrest-model-test')
|
use_database CouchRest.database!('http://localhost:5984/couchrest-model-test')
|
||||||
uniq_id :slug
|
uniq_id :slug
|
||||||
|
|
||||||
|
key_accessor :title
|
||||||
|
key_reader :slug, :created_at, :updated_at
|
||||||
|
before(:create, :generate_slug_from_title)
|
||||||
|
|
||||||
|
timestamps!
|
||||||
|
|
||||||
|
def generate_slug_from_title
|
||||||
|
doc['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
|
||||||
|
end
|
||||||
|
|
||||||
|
key_writer :date
|
||||||
|
|
||||||
|
view_by :date
|
||||||
end
|
end
|
||||||
|
|
||||||
describe CouchRest::Model do
|
describe CouchRest::Model do
|
||||||
|
@ -16,6 +30,9 @@ describe CouchRest::Model do
|
||||||
@db = @cr.database(TESTDB)
|
@db = @cr.database(TESTDB)
|
||||||
@db.delete! rescue nil
|
@db.delete! rescue nil
|
||||||
@db = @cr.create_db(TESTDB) rescue nil
|
@db = @cr.create_db(TESTDB) rescue nil
|
||||||
|
@adb = @cr.database('couchrest-model-test')
|
||||||
|
@adb.delete! rescue nil
|
||||||
|
CouchRest.database!('http://localhost:5984/couchrest-model-test')
|
||||||
CouchRest::Model.default_database = CouchRest.database!('http://localhost:5984/couchrest-test')
|
CouchRest::Model.default_database = CouchRest.database!('http://localhost:5984/couchrest-test')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -27,6 +44,64 @@ describe CouchRest::Model do
|
||||||
Article.database.info['db_name'].should == 'couchrest-model-test'
|
Article.database.info['db_name'].should == 'couchrest-model-test'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "a new model" do
|
||||||
|
it "should be a new_record" do
|
||||||
|
@obj = Basic.new
|
||||||
|
@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.doc['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.doc['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.doc['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.doc['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 "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 "saving a model" do
|
describe "saving a model" do
|
||||||
before(:all) do
|
before(:all) do
|
||||||
@obj = Basic.new
|
@obj = Basic.new
|
||||||
|
@ -55,13 +130,85 @@ describe CouchRest::Model do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "saving a model with a uniq_id configured" do
|
describe "saving a model with a uniq_id configured" do
|
||||||
before(:all) do
|
before(:each) do
|
||||||
@art = Article.new
|
@art = Article.new
|
||||||
|
@old = Article.database.get('this-is-the-title') rescue nil
|
||||||
|
Article.database.delete(@old) if @old
|
||||||
end
|
end
|
||||||
it "should require the slug" do
|
|
||||||
|
it "should require the title" do
|
||||||
lambda{@art.save}.should raise_error
|
lambda{@art.save}.should raise_error
|
||||||
@art.slug = 'this-becomes-the-id'
|
@art.title = 'This is the title'
|
||||||
@art.save.should == true
|
@art.save.should == true
|
||||||
end
|
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 "a model with timestamps" do
|
||||||
|
before(:all) do
|
||||||
|
@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" do
|
||||||
|
before(:all) do
|
||||||
|
written_at = Time.now - 24 * 3600 * 7
|
||||||
|
["this and that", "also interesting", "more fun", "some junk"].each do |title|
|
||||||
|
a = Article.new(:title => title)
|
||||||
|
a.date = written_at
|
||||||
|
a.save
|
||||||
|
written_at += 24 * 3600
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should create the design doc" do
|
||||||
|
Article.by_date
|
||||||
|
doc = Article.database.get("_design/Article")
|
||||||
|
doc['views']['by_date'].should_not be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return the matching view result" do
|
||||||
|
view = Article.by_date :raw => true
|
||||||
|
# view.should == 'x'
|
||||||
|
# view['rows'].should == 4
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
Loading…
Reference in a new issue