fixed a bug with the RestClient optimization, added more callbacks on the ExtendedDocument and added support for casted arrays of objects.
This commit is contained in:
parent
b79bb9a912
commit
3a57ed1414
33
README.md
33
README.md
|
@ -65,4 +65,35 @@ with CouchDB in your Rails or Merb app is no harder than working with the
|
||||||
standard SQL alternatives. See the CouchRest::Model documentation for an
|
standard SQL alternatives. See the CouchRest::Model documentation for an
|
||||||
example article class that illustrates usage.
|
example article class that illustrates usage.
|
||||||
|
|
||||||
CouchRest::Model will be removed from this package.
|
CouchRest::Model will be removed from this package.
|
||||||
|
|
||||||
|
|
||||||
|
## CouchRest::ExtendedDocument
|
||||||
|
|
||||||
|
### Callbacks
|
||||||
|
|
||||||
|
`CouchRest::ExtendedDocuments` instances have 2 callbacks already defined for you:
|
||||||
|
`create_callback`, `save_callback`, `update_callback` and `destroy_callback`
|
||||||
|
|
||||||
|
In your document inherits from `CouchRest::ExtendedDocument`, define your callback as follows:
|
||||||
|
|
||||||
|
save_callback :before, :generate_slug_from_name
|
||||||
|
|
||||||
|
CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted from Rails 3, here are some simple usage examples:
|
||||||
|
|
||||||
|
save_callback :before, :before_method
|
||||||
|
save_callback :after, :after_method, :if => :condition
|
||||||
|
save_callback :around {|r| stuff; yield; stuff }
|
||||||
|
|
||||||
|
Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
|
||||||
|
|
||||||
|
### Casting
|
||||||
|
|
||||||
|
Often, you will want to store multiple objects within a document, to be able to retrieve your objects when you load the document,
|
||||||
|
you can define some casting rules.
|
||||||
|
|
||||||
|
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
||||||
|
property :keywords, :cast_as => ["String"]
|
||||||
|
|
||||||
|
If you want to cast an array of instances from a specific Class, use the trick shown above ["ClassName"]
|
||||||
|
|
||||||
|
|
2
Rakefile
2
Rakefile
|
@ -23,7 +23,7 @@ spec = Gem::Specification.new do |s|
|
||||||
s.homepage = "http://github.com/jchris/couchrest"
|
s.homepage = "http://github.com/jchris/couchrest"
|
||||||
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
||||||
s.has_rdoc = true
|
s.has_rdoc = true
|
||||||
s.authors = ["J. Chris Anderson"]
|
s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
|
||||||
s.files = %w( LICENSE README.md Rakefile THANKS.md ) +
|
s.files = %w( LICENSE README.md Rakefile THANKS.md ) +
|
||||||
Dir["{examples,lib,spec,utils}/**/*"] -
|
Dir["{examples,lib,spec,utils}/**/*"] -
|
||||||
Dir["spec/tmp"]
|
Dir["spec/tmp"]
|
||||||
|
|
|
@ -27,7 +27,7 @@ require 'couchrest/monkeypatches'
|
||||||
|
|
||||||
# = CouchDB, close to the metal
|
# = CouchDB, close to the metal
|
||||||
module CouchRest
|
module CouchRest
|
||||||
VERSION = '0.13.1'
|
VERSION = '0.13.2'
|
||||||
|
|
||||||
autoload :Server, 'couchrest/core/server'
|
autoload :Server, 'couchrest/core/server'
|
||||||
autoload :Database, 'couchrest/core/database'
|
autoload :Database, 'couchrest/core/database'
|
||||||
|
|
|
@ -40,8 +40,8 @@ module CouchRest
|
||||||
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
||||||
target = property.type
|
target = property.type
|
||||||
if target.is_a?(Array)
|
if target.is_a?(Array)
|
||||||
|
next unless self[key]
|
||||||
klass = ::CouchRest.constantize(target[0])
|
klass = ::CouchRest.constantize(target[0])
|
||||||
|
|
||||||
self[property.name] = self[key].collect do |value|
|
self[property.name] = self[key].collect do |value|
|
||||||
# Auto parse Time objects
|
# Auto parse Time objects
|
||||||
obj = ( (property.init_method == 'new') && klass == Time) ? Time.parse(value) : klass.send(property.init_method, value)
|
obj = ( (property.init_method == 'new') && klass == Time) ? Time.parse(value) : klass.send(property.init_method, value)
|
||||||
|
|
|
@ -56,47 +56,58 @@ module RestClient
|
||||||
:url => url,
|
:url => url,
|
||||||
:headers => headers)
|
:headers => headers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# class Request
|
||||||
|
#
|
||||||
|
# def establish_connection(uri)
|
||||||
|
# Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
|
||||||
|
# p net_http_class
|
||||||
|
# net = net_http_class.new(uri.host, uri.port)
|
||||||
|
# net.use_ssl = uri.is_a?(URI::HTTPS)
|
||||||
|
# net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
# Thread.current[:connection] = net
|
||||||
|
# Thread.current[:connection].start
|
||||||
|
# Thread.current[:connection]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def transmit(uri, req, payload)
|
||||||
|
# setup_credentials(req)
|
||||||
|
#
|
||||||
|
# Thread.current[:host] ||= uri.host
|
||||||
|
# Thread.current[:port] ||= uri.port
|
||||||
|
#
|
||||||
|
# if (Thread.current[:connection].nil? || (Thread.current[:host] != uri.host))
|
||||||
|
# p "establishing a connection"
|
||||||
|
# establish_connection(uri)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# display_log request_log
|
||||||
|
# http = Thread.current[:connection]
|
||||||
|
# http.read_timeout = @timeout if @timeout
|
||||||
|
#
|
||||||
|
# begin
|
||||||
|
# res = http.request(req, payload)
|
||||||
|
# rescue
|
||||||
|
# p "Net::HTTP connection failed, reconnecting"
|
||||||
|
# establish_connection(uri)
|
||||||
|
# http = Thread.current[:connection]
|
||||||
|
# require 'ruby-debug'
|
||||||
|
# debugger
|
||||||
|
# req.body_stream = nil
|
||||||
|
#
|
||||||
|
# res = http.request(req, payload)
|
||||||
|
# display_log response_log(res)
|
||||||
|
# result res
|
||||||
|
# else
|
||||||
|
# display_log response_log(res)
|
||||||
|
# process_result res
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# rescue EOFError
|
||||||
|
# raise RestClient::ServerBrokeConnection
|
||||||
|
# rescue Timeout::Error
|
||||||
|
# raise RestClient::RequestTimeout
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
class Request
|
|
||||||
def transmit(uri, req, payload)
|
|
||||||
setup_credentials(req)
|
|
||||||
|
|
||||||
Thread.current[:host] ||= uri.host
|
|
||||||
Thread.current[:port] ||= uri.port
|
|
||||||
|
|
||||||
net = net_http_class.new(uri.host, uri.port)
|
|
||||||
|
|
||||||
if Thread.current[:connection].nil? || Thread.current[:host] != uri.host
|
|
||||||
Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
|
|
||||||
net.use_ssl = uri.is_a?(URI::HTTPS)
|
|
||||||
net.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
||||||
Thread.current[:connection] = net
|
|
||||||
Thread.current[:connection].start
|
|
||||||
end
|
|
||||||
|
|
||||||
display_log request_log
|
|
||||||
http = Thread.current[:connection]
|
|
||||||
|
|
||||||
http.read_timeout = @timeout if @timeout
|
|
||||||
begin
|
|
||||||
res = http.request(req, payload)
|
|
||||||
rescue
|
|
||||||
# p "Net::HTTP connection failed, reconnecting"
|
|
||||||
Thread.current[:connection].finish
|
|
||||||
http = Thread.current[:connection] = net
|
|
||||||
Thread.current[:connection].start
|
|
||||||
res = http.request(req, payload)
|
|
||||||
display_log response_log(res)
|
|
||||||
process_result res
|
|
||||||
else
|
|
||||||
display_log response_log(res)
|
|
||||||
process_result res
|
|
||||||
end
|
|
||||||
|
|
||||||
rescue EOFError
|
|
||||||
raise RestClient::ServerBrokeConnection
|
|
||||||
rescue Timeout::Error
|
|
||||||
raise RestClient::RequestTimeout
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
|
@ -23,7 +23,9 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
|
define_callbacks :create
|
||||||
define_callbacks :save
|
define_callbacks :save
|
||||||
|
define_callbacks :update
|
||||||
define_callbacks :destroy
|
define_callbacks :destroy
|
||||||
|
|
||||||
def initialize(keys={})
|
def initialize(keys={})
|
||||||
|
@ -105,12 +107,62 @@ module CouchRest
|
||||||
# for compatibility with old-school frameworks
|
# for compatibility with old-school frameworks
|
||||||
alias :new_record? :new_document?
|
alias :new_record? :new_document?
|
||||||
|
|
||||||
|
# Trigger the callbacks (before, after, around)
|
||||||
|
# and create the document
|
||||||
|
# It's important to have a create callback since you can't check if a document
|
||||||
|
# was new after you saved it
|
||||||
|
#
|
||||||
|
# When creating a document, both the create and the save callbacks will be triggered.
|
||||||
|
def create(bulk = false)
|
||||||
|
caught = catch(:halt) do
|
||||||
|
_run_create_callbacks do
|
||||||
|
_run_save_callbacks do
|
||||||
|
create_without_callbacks(bulk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# unlike save, create returns the newly created document
|
||||||
|
def create_without_callbacks(bulk =false)
|
||||||
|
raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
|
||||||
|
set_unique_id if new_document? && self.respond_to?(:set_unique_id)
|
||||||
|
result = database.save_doc(self, bulk)
|
||||||
|
(result["ok"] == true) ? self : false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates the document in the db. Raises an exception
|
||||||
|
# if the document is not created properly.
|
||||||
|
def create!
|
||||||
|
raise "#{self.inspect} failed to save" unless self.create
|
||||||
|
end
|
||||||
|
|
||||||
|
# Trigger the callbacks (before, after, around)
|
||||||
|
# only if the document isn't new
|
||||||
|
def update(bulk = false)
|
||||||
|
caught = catch(:halt) do
|
||||||
|
if self.new_document?
|
||||||
|
save(bulk)
|
||||||
|
else
|
||||||
|
_run_update_callbacks do
|
||||||
|
_run_save_callbacks do
|
||||||
|
save_without_callbacks(bulk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Trigger the callbacks (before, after, around)
|
# Trigger the callbacks (before, after, around)
|
||||||
# and save the document
|
# and save the document
|
||||||
def save(bulk = false)
|
def save(bulk = false)
|
||||||
caught = catch(:halt) do
|
caught = catch(:halt) do
|
||||||
_run_save_callbacks do
|
if self.new_document?
|
||||||
save_without_callbacks(bulk)
|
_run_save_callbacks do
|
||||||
|
save_without_callbacks(bulk)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
update(bulk)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -124,7 +176,7 @@ module CouchRest
|
||||||
result["ok"] == true
|
result["ok"] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Saves the document to the db using create or update. Raises an exception
|
# Saves the document to the db using save. Raises an exception
|
||||||
# if the document is not saved properly.
|
# if the document is not saved properly.
|
||||||
def save!
|
def save!
|
||||||
raise "#{self.inspect} failed to save" unless self.save
|
raise "#{self.inspect} failed to save" unless self.save
|
||||||
|
|
|
@ -7,13 +7,22 @@ module CouchRest
|
||||||
# attribute to define
|
# attribute to define
|
||||||
def initialize(name, type = nil, options = {})
|
def initialize(name, type = nil, options = {})
|
||||||
@name = name.to_s
|
@name = name.to_s
|
||||||
@type = type.nil? ? 'String' : type.to_s
|
parse_type(type)
|
||||||
parse_options(options)
|
parse_options(options)
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def parse_type(type)
|
||||||
|
if type.nil?
|
||||||
|
@type = 'String'
|
||||||
|
else
|
||||||
|
@type = type.is_a?(Array) ? [type.first.to_s] : type.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def parse_options(options)
|
def parse_options(options)
|
||||||
return if options.empty?
|
return if options.empty?
|
||||||
@validation_format = options.delete(:format) if options[:format]
|
@validation_format = options.delete(:format) if options[:format]
|
||||||
|
|
|
@ -10,6 +10,7 @@ class DummyModel < CouchRest::ExtendedDocument
|
||||||
use_database TEST_SERVER.default_database
|
use_database TEST_SERVER.default_database
|
||||||
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||||
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
||||||
|
property :keywords, :cast_as => ["String"]
|
||||||
end
|
end
|
||||||
|
|
||||||
describe CouchRest::CastedModel do
|
describe CouchRest::CastedModel do
|
||||||
|
@ -55,6 +56,18 @@ describe CouchRest::CastedModel do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "casted as an array of a different type" do
|
||||||
|
before(:each) do
|
||||||
|
@obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should cast the array propery" do
|
||||||
|
@obj.keywords.should be_an_instance_of(Array)
|
||||||
|
@obj.keywords.first.should == 'couch'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe "saved document with casted models" do
|
describe "saved document with casted models" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
|
@obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
|
||||||
|
|
|
@ -10,12 +10,41 @@ describe "ExtendedDocument" do
|
||||||
timestamps!
|
timestamps!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class WithCallBacks < CouchRest::ExtendedDocument
|
||||||
|
use_database TEST_SERVER.default_database
|
||||||
|
property :name
|
||||||
|
property :run_before_save
|
||||||
|
property :run_after_save
|
||||||
|
property :run_before_create
|
||||||
|
property :run_after_create
|
||||||
|
property :run_before_update
|
||||||
|
property :run_after_update
|
||||||
|
|
||||||
|
save_callback :before do |object|
|
||||||
|
object.run_before_save = true
|
||||||
|
end
|
||||||
|
save_callback :after do |object|
|
||||||
|
object.run_after_save = true
|
||||||
|
end
|
||||||
|
create_callback :before do |object|
|
||||||
|
object.run_before_create = true
|
||||||
|
end
|
||||||
|
create_callback :after do |object|
|
||||||
|
object.run_after_create = true
|
||||||
|
end
|
||||||
|
update_callback :before do |object|
|
||||||
|
object.run_before_update = true
|
||||||
|
end
|
||||||
|
update_callback :after do |object|
|
||||||
|
object.run_after_update = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@obj = WithDefaultValues.new
|
@obj = WithDefaultValues.new
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with default" do
|
describe "with default" do
|
||||||
|
|
||||||
it "should have the default value set at initalization" do
|
it "should have the default value set at initalization" do
|
||||||
@obj.preset.should == {:right => 10, :top_align => false}
|
@obj.preset.should == {:right => 10, :top_align => false}
|
||||||
end
|
end
|
||||||
|
@ -28,7 +57,6 @@ describe "ExtendedDocument" do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "timestamping" do
|
describe "timestamping" do
|
||||||
|
|
||||||
it "should define the updated_at and created_at getters and set the values" do
|
it "should define the updated_at and created_at getters and set the values" do
|
||||||
@obj.save
|
@obj.save
|
||||||
obj = WithDefaultValues.get(@obj.id)
|
obj = WithDefaultValues.get(@obj.id)
|
||||||
|
@ -36,12 +64,10 @@ describe "ExtendedDocument" do
|
||||||
obj.created_at.should be_an_instance_of(Time)
|
obj.created_at.should be_an_instance_of(Time)
|
||||||
obj.updated_at.should be_an_instance_of(Time)
|
obj.updated_at.should be_an_instance_of(Time)
|
||||||
obj.created_at.to_s.should == @obj.updated_at.to_s
|
obj.created_at.to_s.should == @obj.updated_at.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "saving and retrieving" do
|
describe "saving and retrieving" do
|
||||||
|
|
||||||
it "should work fine" do
|
it "should work fine" do
|
||||||
@obj.name = "should be easily saved and retrieved"
|
@obj.name = "should be easily saved and retrieved"
|
||||||
@obj.save
|
@obj.save
|
||||||
|
@ -57,7 +83,86 @@ describe "ExtendedDocument" do
|
||||||
saved_obj = WithDefaultValues.get(@obj.id)
|
saved_obj = WithDefaultValues.get(@obj.id)
|
||||||
saved_obj.set_by_proc.should be_an_instance_of(Time)
|
saved_obj.set_by_proc.should be_an_instance_of(Time)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "callbacks" do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@doc = WithCallBacks.new
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "save" do
|
||||||
|
it "should not run the before filter before saving if the save failed" do
|
||||||
|
@doc.run_before_save.should be_nil
|
||||||
|
@doc.save.should be_true
|
||||||
|
@doc.run_before_save.should be_true
|
||||||
|
end
|
||||||
|
it "should not run the before filter before saving if the save failed" do
|
||||||
|
@doc.should_receive(:save).and_return(false)
|
||||||
|
@doc.run_before_save.should be_nil
|
||||||
|
@doc.save.should be_false
|
||||||
|
@doc.run_before_save.should be_nil
|
||||||
|
end
|
||||||
|
it "should run the after filter after saving" do
|
||||||
|
@doc.run_after_save.should be_nil
|
||||||
|
@doc.save.should be_true
|
||||||
|
@doc.run_after_save.should be_true
|
||||||
|
end
|
||||||
|
it "should not run the after filter before saving if the save failed" do
|
||||||
|
@doc.should_receive(:save).and_return(false)
|
||||||
|
@doc.run_after_save.should be_nil
|
||||||
|
@doc.save.should be_false
|
||||||
|
@doc.run_after_save.should be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
describe "create" do
|
||||||
|
it "should run the before save filter when creating" do
|
||||||
|
@doc.run_before_save.should be_nil
|
||||||
|
@doc.create.should_not be_nil
|
||||||
|
@doc.run_before_save.should be_true
|
||||||
|
end
|
||||||
|
it "should not run the before save filter when the object creation fails" do
|
||||||
|
pending "need to ask wycats about chainable callbacks" do
|
||||||
|
@doc.should_receive(:create_without_callbacks).and_return(false)
|
||||||
|
@doc.run_before_save.should be_nil
|
||||||
|
@doc.save
|
||||||
|
@doc.run_before_save.should be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
it "should run the before create filter" do
|
||||||
|
@doc.run_before_create.should be_nil
|
||||||
|
@doc.create.should_not be_nil
|
||||||
|
@doc.create
|
||||||
|
@doc.run_before_create.should be_true
|
||||||
|
end
|
||||||
|
it "should run the after create filter" do
|
||||||
|
@doc.run_after_create.should be_nil
|
||||||
|
@doc.create.should_not be_nil
|
||||||
|
@doc.create
|
||||||
|
@doc.run_after_create.should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
describe "update" do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@doc.save
|
||||||
|
end
|
||||||
|
it "should run the before update filter when updating an existing document" do
|
||||||
|
@doc.run_before_update.should be_nil
|
||||||
|
@doc.update
|
||||||
|
@doc.run_before_update.should be_true
|
||||||
|
end
|
||||||
|
it "should run the after update filter when updating an existing document" do
|
||||||
|
@doc.run_after_update.should be_nil
|
||||||
|
@doc.update
|
||||||
|
@doc.run_after_update.should be_true
|
||||||
|
end
|
||||||
|
it "should run the before update filter when saving an existing document" do
|
||||||
|
@doc.run_before_update.should be_nil
|
||||||
|
@doc.save
|
||||||
|
@doc.run_before_update.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
Loading…
Reference in a new issue