merged in sporkd
This commit is contained in:
commit
7246801f57
|
@ -68,10 +68,13 @@ CouchRest::Model has been deprecated and replaced by CouchRest::ExtendedDocument
|
||||||
|
|
||||||
### Callbacks
|
### Callbacks
|
||||||
|
|
||||||
`CouchRest::ExtendedDocuments` instances have 2 callbacks already defined for you:
|
`CouchRest::ExtendedDocuments` instances have 4 callbacks already defined for you:
|
||||||
`create_callback`, `save_callback`, `update_callback` and `destroy_callback`
|
`validate_callback`, `create_callback`, `save_callback`, `update_callback` and `destroy_callback`
|
||||||
|
|
||||||
In your document inherits from `CouchRest::ExtendedDocument`, define your callback as follows:
|
`CouchRest::CastedModel` instances have 1 callback already defined for you:
|
||||||
|
`validate_callback`
|
||||||
|
|
||||||
|
Define your callback as follows:
|
||||||
|
|
||||||
save_callback :before, :generate_slug_from_name
|
save_callback :before, :generate_slug_from_name
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns true if the document has never been saved
|
# returns true if the document has never been saved
|
||||||
def new_document?
|
def new?
|
||||||
!rev
|
!rev
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,8 +67,8 @@ module CouchRest
|
||||||
|
|
||||||
# Returns the CouchDB uri for the document
|
# Returns the CouchDB uri for the document
|
||||||
def uri(append_rev = false)
|
def uri(append_rev = false)
|
||||||
return nil if new_document?
|
return nil if new?
|
||||||
couch_uri = "http://#{database.root}/#{CGI.escape(id)}"
|
couch_uri = "http://#{database.uri}/#{CGI.escape(id)}"
|
||||||
if append_rev == true
|
if append_rev == true
|
||||||
couch_uri << "?rev=#{rev}"
|
couch_uri << "?rev=#{rev}"
|
||||||
elsif append_rev.kind_of?(Integer)
|
elsif append_rev.kind_of?(Integer)
|
||||||
|
|
|
@ -17,7 +17,7 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_defaults
|
def apply_defaults
|
||||||
return if self.respond_to?(:new_document?) && (new_document? == false)
|
return if self.respond_to?(:new?) && (new? == false)
|
||||||
return unless self.class.respond_to?(:properties)
|
return unless self.class.respond_to?(:properties)
|
||||||
return if self.class.properties.empty?
|
return if self.class.properties.empty?
|
||||||
# TODO: cache the default object
|
# TODO: cache the default object
|
||||||
|
@ -44,12 +44,14 @@ module CouchRest
|
||||||
target = property.type
|
target = property.type
|
||||||
if target.is_a?(Array)
|
if target.is_a?(Array)
|
||||||
klass = ::CouchRest.constantize(target[0])
|
klass = ::CouchRest.constantize(target[0])
|
||||||
self[property.name] = self[key].collect do |value|
|
arr = 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)
|
||||||
obj.casted_by = self if obj.respond_to?(:casted_by)
|
obj.casted_by = self if obj.respond_to?(:casted_by)
|
||||||
|
obj.document_saved = true if obj.respond_to?(:document_saved)
|
||||||
obj
|
obj
|
||||||
end
|
end
|
||||||
|
self[property.name] = target[0] != 'String' ? CastedArray.new(arr) : arr
|
||||||
else
|
else
|
||||||
# Auto parse Time objects
|
# Auto parse Time objects
|
||||||
self[property.name] = if ((property.init_method == 'new') && target == 'Time')
|
self[property.name] = if ((property.init_method == 'new') && target == 'Time')
|
||||||
|
@ -60,7 +62,9 @@ module CouchRest
|
||||||
klass.send(property.init_method, self[key].dup)
|
klass.send(property.init_method, self[key].dup)
|
||||||
end
|
end
|
||||||
self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
|
self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
|
||||||
|
self[property.name].document_saved = true if self[property.name].respond_to?(:document_saved)
|
||||||
end
|
end
|
||||||
|
self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -107,6 +111,14 @@ module CouchRest
|
||||||
meth = property.name
|
meth = property.name
|
||||||
class_eval <<-EOS
|
class_eval <<-EOS
|
||||||
def #{meth}=(value)
|
def #{meth}=(value)
|
||||||
|
if #{property.casted} && value.is_a?(Array)
|
||||||
|
arr = CastedArray.new
|
||||||
|
arr.casted_by = self
|
||||||
|
value.each { |v| arr << v }
|
||||||
|
value = arr
|
||||||
|
elsif #{property.casted}
|
||||||
|
value.casted_by = self if value.respond_to?(:casted_by)
|
||||||
|
end
|
||||||
self['#{meth}'] = value
|
self['#{meth}'] = value
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
|
|
|
@ -51,6 +51,9 @@ module CouchRest
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.extlib_inheritable_accessor(:auto_validation)
|
base.extlib_inheritable_accessor(:auto_validation)
|
||||||
base.class_eval <<-EOS, __FILE__, __LINE__
|
base.class_eval <<-EOS, __FILE__, __LINE__
|
||||||
|
# Callbacks
|
||||||
|
define_callbacks :validate
|
||||||
|
|
||||||
# Turn off auto validation by default
|
# Turn off auto validation by default
|
||||||
self.auto_validation ||= false
|
self.auto_validation ||= false
|
||||||
|
|
||||||
|
@ -72,6 +75,7 @@ module CouchRest
|
||||||
|
|
||||||
base.extend(ClassMethods)
|
base.extend(ClassMethods)
|
||||||
base.class_eval <<-EOS, __FILE__, __LINE__
|
base.class_eval <<-EOS, __FILE__, __LINE__
|
||||||
|
define_callbacks :validate
|
||||||
if method_defined?(:_run_save_callbacks)
|
if method_defined?(:_run_save_callbacks)
|
||||||
save_callback :before, :check_validations
|
save_callback :before, :check_validations
|
||||||
end
|
end
|
||||||
|
@ -115,8 +119,7 @@ module CouchRest
|
||||||
# Check if a resource is valid in a given context
|
# Check if a resource is valid in a given context
|
||||||
#
|
#
|
||||||
def valid?(context = :default)
|
def valid?(context = :default)
|
||||||
result = self.class.validators.execute(context, self)
|
recursive_valid?(self, context, true)
|
||||||
result && validate_casted_arrays
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# checking on casted objects
|
# checking on casted objects
|
||||||
|
@ -133,29 +136,24 @@ module CouchRest
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
# Begin a recursive walk of the model checking validity
|
|
||||||
#
|
|
||||||
def all_valid?(context = :default)
|
|
||||||
recursive_valid?(self, context, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Do recursive validity checking
|
# Do recursive validity checking
|
||||||
#
|
#
|
||||||
def recursive_valid?(target, context, state)
|
def recursive_valid?(target, context, state)
|
||||||
valid = state
|
valid = state
|
||||||
target.instance_variables.each do |ivar|
|
target.each do |key, prop|
|
||||||
ivar_value = target.instance_variable_get(ivar)
|
if prop.is_a?(Array)
|
||||||
if ivar_value.validatable?
|
prop.each do |item|
|
||||||
valid = valid && recursive_valid?(ivar_value, context, valid)
|
|
||||||
elsif ivar_value.respond_to?(:each)
|
|
||||||
ivar_value.each do |item|
|
|
||||||
if item.validatable?
|
if item.validatable?
|
||||||
valid = valid && recursive_valid?(item, context, valid)
|
valid = recursive_valid?(item, context, valid) && valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
elsif prop.validatable?
|
||||||
|
valid = recursive_valid?(prop, context, valid) && valid
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return valid && target.valid?
|
target._run_validate_callbacks do
|
||||||
|
target.class.validators.execute(context, target) && valid
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -218,15 +216,6 @@ module CouchRest
|
||||||
end # end
|
end # end
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
all = "all_valid_for_#{context.to_s}?" # all_valid_for_signup?
|
|
||||||
if !self.instance_methods.include?(all)
|
|
||||||
class_eval <<-EOS, __FILE__, __LINE__
|
|
||||||
def #{all} # def all_valid_for_signup?
|
|
||||||
all_valid?('#{context.to_s}'.to_sym) # all_valid?('signup'.to_sym)
|
|
||||||
end # end
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create a new validator of the given klazz and push it onto the
|
# Create a new validator of the given klazz and push it onto the
|
||||||
|
|
|
@ -4,8 +4,10 @@ module CouchRest
|
||||||
module CastedModel
|
module CastedModel
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
|
base.send(:include, CouchRest::Callbacks)
|
||||||
base.send(:include, CouchRest::Mixins::Properties)
|
base.send(:include, CouchRest::Mixins::Properties)
|
||||||
base.send(:attr_accessor, :casted_by)
|
base.send(:attr_accessor, :casted_by)
|
||||||
|
base.send(:attr_accessor, :document_saved)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(keys={})
|
def initialize(keys={})
|
||||||
|
@ -25,5 +27,31 @@ module CouchRest
|
||||||
def [] key
|
def [] key
|
||||||
super(key.to_s)
|
super(key.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets a reference to the top level extended
|
||||||
|
# document that a model is saved inside of
|
||||||
|
def base_doc
|
||||||
|
return nil unless @casted_by
|
||||||
|
@casted_by.base_doc
|
||||||
|
end
|
||||||
|
|
||||||
|
# False if the casted model has already
|
||||||
|
# been saved in the containing document
|
||||||
|
def new?
|
||||||
|
!@document_saved
|
||||||
|
end
|
||||||
|
alias :new_record? :new?
|
||||||
|
|
||||||
|
# Sets the attributes from a hash
|
||||||
|
def update_attributes_without_saving(hash)
|
||||||
|
hash.each do |k, v|
|
||||||
|
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
||||||
|
end
|
||||||
|
hash.each do |k, v|
|
||||||
|
self.send("#{k}=",v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias :attributes= :update_attributes_without_saving
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -64,7 +64,7 @@ module CouchRest
|
||||||
|
|
||||||
save_callback :before do |object|
|
save_callback :before do |object|
|
||||||
object['updated_at'] = Time.now
|
object['updated_at'] = Time.now
|
||||||
object['created_at'] = object['updated_at'] if object.new_document?
|
object['created_at'] = object['updated_at'] if object.new?
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
@ -110,6 +110,19 @@ module CouchRest
|
||||||
self.class.properties
|
self.class.properties
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gets a reference to the actual document in the DB
|
||||||
|
# Calls up to the next document if there is one,
|
||||||
|
# Otherwise we're at the top and we return self
|
||||||
|
def base_doc
|
||||||
|
return self if base_doc?
|
||||||
|
@casted_by.base_doc
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if we're the top document
|
||||||
|
def base_doc?
|
||||||
|
!@casted_by
|
||||||
|
end
|
||||||
|
|
||||||
# Takes a hash as argument, and applies the values by using writer methods
|
# Takes a hash as argument, and applies the values by using writer methods
|
||||||
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
||||||
# missing. In case of error, no attributes are changed.
|
# missing. In case of error, no attributes are changed.
|
||||||
|
@ -121,6 +134,7 @@ module CouchRest
|
||||||
self.send("#{k}=",v)
|
self.send("#{k}=",v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
alias :attributes= :update_attributes_without_saving
|
||||||
|
|
||||||
# Takes a hash as argument, and applies the values by using writer methods
|
# Takes a hash as argument, and applies the values by using writer methods
|
||||||
# for each key. Raises a NoMethodError if the corresponding methods are
|
# for each key. Raises a NoMethodError if the corresponding methods are
|
||||||
|
@ -131,7 +145,7 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
# for compatibility with old-school frameworks
|
# for compatibility with old-school frameworks
|
||||||
alias :new_record? :new_document?
|
alias :new_record? :new?
|
||||||
|
|
||||||
# Trigger the callbacks (before, after, around)
|
# Trigger the callbacks (before, after, around)
|
||||||
# and create the document
|
# and create the document
|
||||||
|
@ -152,7 +166,7 @@ module CouchRest
|
||||||
# unlike save, create returns the newly created document
|
# unlike save, create returns the newly created document
|
||||||
def create_without_callbacks(bulk =false)
|
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
|
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)
|
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||||
result = database.save_doc(self, bulk)
|
result = database.save_doc(self, bulk)
|
||||||
(result["ok"] == true) ? self : false
|
(result["ok"] == true) ? self : false
|
||||||
end
|
end
|
||||||
|
@ -167,7 +181,7 @@ module CouchRest
|
||||||
# only if the document isn't new
|
# only if the document isn't new
|
||||||
def update(bulk = false)
|
def update(bulk = false)
|
||||||
caught = catch(:halt) do
|
caught = catch(:halt) do
|
||||||
if self.new_document?
|
if self.new?
|
||||||
save(bulk)
|
save(bulk)
|
||||||
else
|
else
|
||||||
_run_update_callbacks do
|
_run_update_callbacks do
|
||||||
|
@ -183,7 +197,7 @@ module CouchRest
|
||||||
# and save the document
|
# and save the document
|
||||||
def save(bulk = false)
|
def save(bulk = false)
|
||||||
caught = catch(:halt) do
|
caught = catch(:halt) do
|
||||||
if self.new_document?
|
if self.new?
|
||||||
_run_save_callbacks do
|
_run_save_callbacks do
|
||||||
save_without_callbacks(bulk)
|
save_without_callbacks(bulk)
|
||||||
end
|
end
|
||||||
|
@ -197,8 +211,9 @@ module CouchRest
|
||||||
# Returns a boolean value
|
# Returns a boolean value
|
||||||
def save_without_callbacks(bulk = false)
|
def save_without_callbacks(bulk = false)
|
||||||
raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
|
raise ArgumentError, "a document requires a database to be saved 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)
|
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||||
result = database.save_doc(self, bulk)
|
result = database.save_doc(self, bulk)
|
||||||
|
mark_as_saved if result["ok"] == true
|
||||||
result["ok"] == true
|
result["ok"] == true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -224,5 +239,22 @@ module CouchRest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# Set document_saved flag on all casted models to true
|
||||||
|
def mark_as_saved
|
||||||
|
self.each do |key, prop|
|
||||||
|
if prop.is_a?(Array)
|
||||||
|
prop.each do |item|
|
||||||
|
if item.respond_to?(:document_saved)
|
||||||
|
item.send(:document_saved=, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif prop.respond_to?(:document_saved)
|
||||||
|
prop.send(:document_saved=, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,3 +38,22 @@ module CouchRest
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class CastedArray < Array
|
||||||
|
attr_accessor :casted_by
|
||||||
|
|
||||||
|
def << obj
|
||||||
|
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||||
|
super(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def push(obj)
|
||||||
|
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||||
|
super(obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
def []= index, obj
|
||||||
|
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
||||||
|
super(index, obj)
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,6 +14,9 @@ end
|
||||||
|
|
||||||
|
|
||||||
CouchRest::Document.class_eval do
|
CouchRest::Document.class_eval do
|
||||||
|
# Need this when passing doc to a resourceful route
|
||||||
|
alias_method :to_param, :id
|
||||||
|
|
||||||
# Hack so that CouchRest::Document, which descends from Hash,
|
# Hack so that CouchRest::Document, which descends from Hash,
|
||||||
# doesn't appear to Rails routing as a Hash of options
|
# doesn't appear to Rails routing as a Hash of options
|
||||||
def is_a?(o)
|
def is_a?(o)
|
||||||
|
@ -21,8 +24,22 @@ CouchRest::Document.class_eval do
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
alias_method :kind_of?, :is_a?
|
alias_method :kind_of?, :is_a?
|
||||||
|
|
||||||
|
# Gives extended doc a seamless logger
|
||||||
|
def logger
|
||||||
|
ActiveRecord::Base.logger
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CouchRest::CastedModel.class_eval do
|
||||||
|
# The to_param method is needed for rails to generate resourceful routes.
|
||||||
|
# In your controller, remember that it's actually the id of the document.
|
||||||
|
def id
|
||||||
|
return nil if base_doc.nil?
|
||||||
|
base_doc.id
|
||||||
|
end
|
||||||
|
alias_method :to_param, :id
|
||||||
|
end
|
||||||
|
|
||||||
require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
|
require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
|
||||||
|
|
||||||
|
|
|
@ -253,7 +253,7 @@ describe CouchRest::Database do
|
||||||
describe "PUT attachment from file" do
|
describe "PUT attachment from file" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
filename = FIXTURE_PATH + '/attachments/couchdb.png'
|
filename = FIXTURE_PATH + '/attachments/couchdb.png'
|
||||||
@file = File.open(filename)
|
@file = File.open(filename, "rb")
|
||||||
end
|
end
|
||||||
after(:each) do
|
after(:each) do
|
||||||
@file.close
|
@file.close
|
||||||
|
|
|
@ -4,6 +4,8 @@ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||||
|
require File.join(FIXTURE_PATH, 'more', 'question')
|
||||||
|
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||||
|
|
||||||
|
|
||||||
class WithCastedModelMixin < Hash
|
class WithCastedModelMixin < Hash
|
||||||
|
@ -21,6 +23,26 @@ class DummyModel < CouchRest::ExtendedDocument
|
||||||
property :keywords, :cast_as => ["String"]
|
property :keywords, :cast_as => ["String"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class CastedCallbackDoc < CouchRest::ExtendedDocument
|
||||||
|
use_database TEST_SERVER.default_database
|
||||||
|
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||||
|
property :callback_model, :cast_as => 'WithCastedCallBackModel'
|
||||||
|
end
|
||||||
|
class WithCastedCallBackModel < Hash
|
||||||
|
include CouchRest::CastedModel
|
||||||
|
include CouchRest::Validation
|
||||||
|
property :name
|
||||||
|
property :run_before_validate
|
||||||
|
property :run_after_validate
|
||||||
|
|
||||||
|
validate_callback :before do |object|
|
||||||
|
object.run_before_validate = true
|
||||||
|
end
|
||||||
|
validate_callback :after do |object|
|
||||||
|
object.run_after_validate = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe CouchRest::CastedModel do
|
describe CouchRest::CastedModel do
|
||||||
|
|
||||||
describe "A non hash class including CastedModel" do
|
describe "A non hash class including CastedModel" do
|
||||||
|
@ -106,7 +128,40 @@ describe CouchRest::CastedModel do
|
||||||
@obj.keywords.should be_an_instance_of(Array)
|
@obj.keywords.should be_an_instance_of(Array)
|
||||||
@obj.keywords.first.should == 'couch'
|
@obj.keywords.first.should == 'couch'
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update attributes without saving" do
|
||||||
|
before(:each) do
|
||||||
|
@question = Question.new(:q => "What is your quest?", :a => "To seek the Holy Grail")
|
||||||
|
end
|
||||||
|
it "should work for attribute= methods" do
|
||||||
|
@question.q.should == "What is your quest?"
|
||||||
|
@question['a'].should == "To seek the Holy Grail"
|
||||||
|
@question.update_attributes_without_saving(:q => "What is your favorite color?", 'a' => "Blue")
|
||||||
|
@question['q'].should == "What is your favorite color?"
|
||||||
|
@question.a.should == "Blue"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should also work for attributes= alias" do
|
||||||
|
@question.respond_to?(:attributes=).should be_true
|
||||||
|
@question.attributes = {:q => "What is your favorite color?", 'a' => "Blue"}
|
||||||
|
@question['q'].should == "What is your favorite color?"
|
||||||
|
@question.a.should == "Blue"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should flip out if an attribute= method is missing" do
|
||||||
|
lambda {
|
||||||
|
@q.update_attributes_without_saving('foo' => "something", :a => "No green")
|
||||||
|
}.should raise_error(NoMethodError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not change any attributes if there is an error" do
|
||||||
|
lambda {
|
||||||
|
@q.update_attributes_without_saving('foo' => "something", :a => "No green")
|
||||||
|
}.should raise_error(NoMethodError)
|
||||||
|
@question.q.should == "What is your quest?"
|
||||||
|
@question.a.should == "To seek the Holy Grail"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "saved document with casted models" do
|
describe "saved document with casted models" do
|
||||||
|
@ -171,7 +226,173 @@ describe CouchRest::CastedModel do
|
||||||
cat.masters.push Person.new
|
cat.masters.push Person.new
|
||||||
cat.should be_valid
|
cat.should be_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "calling valid?" do
|
||||||
|
before :each do
|
||||||
|
@cat = Cat.new
|
||||||
|
@toy1 = CatToy.new
|
||||||
|
@toy2 = CatToy.new
|
||||||
|
@toy3 = CatToy.new
|
||||||
|
@cat.favorite_toy = @toy1
|
||||||
|
@cat.toys << @toy2
|
||||||
|
@cat.toys << @toy3
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "on the top document" do
|
||||||
|
it "should put errors on all invalid casted models" do
|
||||||
|
@cat.should_not be_valid
|
||||||
|
@cat.errors.should_not be_empty
|
||||||
|
@toy1.errors.should_not be_empty
|
||||||
|
@toy2.errors.should_not be_empty
|
||||||
|
@toy3.errors.should_not be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not put errors on valid casted models" do
|
||||||
|
@toy1.name = "Feather"
|
||||||
|
@toy2.name = "Twine"
|
||||||
|
@cat.should_not be_valid
|
||||||
|
@cat.errors.should_not be_empty
|
||||||
|
@toy1.errors.should be_empty
|
||||||
|
@toy2.errors.should be_empty
|
||||||
|
@toy3.errors.should_not be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "on a casted model property" do
|
||||||
|
it "should only validate itself" do
|
||||||
|
@toy1.should_not be_valid
|
||||||
|
@toy1.errors.should_not be_empty
|
||||||
|
@cat.errors.should be_empty
|
||||||
|
@toy2.errors.should be_empty
|
||||||
|
@toy3.errors.should be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "on a casted model inside a casted collection" do
|
||||||
|
it "should only validate itself" do
|
||||||
|
@toy2.should_not be_valid
|
||||||
|
@toy2.errors.should_not be_empty
|
||||||
|
@cat.errors.should be_empty
|
||||||
|
@toy1.errors.should be_empty
|
||||||
|
@toy3.errors.should be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "calling new? on a casted model" do
|
||||||
|
before :each do
|
||||||
|
reset_test_db!
|
||||||
|
@cat = Cat.new(:name => 'Sockington')
|
||||||
|
@cat.favorite_toy = CatToy.new(:name => 'Catnip Ball')
|
||||||
|
@cat.toys << CatToy.new(:name => 'Fuzzy Stick')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be true on new" do
|
||||||
|
CatToy.new.should be_new
|
||||||
|
CatToy.new.new_record?.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be true after assignment" do
|
||||||
|
@cat.favorite_toy.should be_new
|
||||||
|
@cat.toys.first.should be_new
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be true after create or save" do
|
||||||
|
@cat.create
|
||||||
|
@cat.save
|
||||||
|
@cat.favorite_toy.should_not be_new
|
||||||
|
@cat.toys.first.should_not be_new
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be true after get from the database" do
|
||||||
|
@cat.save
|
||||||
|
@cat = Cat.get(@cat.id)
|
||||||
|
@cat.favorite_toy.should_not be_new
|
||||||
|
@cat.toys.first.should_not be_new
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should still be true after a failed create or save" do
|
||||||
|
@cat.name = nil
|
||||||
|
@cat.create.should be_false
|
||||||
|
@cat.save.should be_false
|
||||||
|
@cat.favorite_toy.should be_new
|
||||||
|
@cat.toys.first.should be_new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "calling base_doc from a nested casted model" do
|
||||||
|
before :each do
|
||||||
|
@course = Course.new(:title => 'Science 101')
|
||||||
|
@professor = Person.new(:name => 'Professor Plum')
|
||||||
|
@cat = Cat.new(:name => 'Scratchy')
|
||||||
|
@toy1 = CatToy.new
|
||||||
|
@toy2 = CatToy.new
|
||||||
|
@course.professor = @professor
|
||||||
|
@professor.pet = @cat
|
||||||
|
@cat.favorite_toy = @toy1
|
||||||
|
@cat.toys << @toy2
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should reference the top document for" do
|
||||||
|
@course.base_doc.should === @course
|
||||||
|
@professor.base_doc.should === @course
|
||||||
|
@cat.base_doc.should === @course
|
||||||
|
@toy1.base_doc.should === @course
|
||||||
|
@toy2.base_doc.should === @course
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should call setter on top document" do
|
||||||
|
@toy1.base_doc.title = 'Tom Foolery'
|
||||||
|
@course.title.should == 'Tom Foolery'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return nil if not yet casted" do
|
||||||
|
person = Person.new
|
||||||
|
person.base_doc.should == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "calling base_doc.save from a nested casted model" do
|
||||||
|
before :each do
|
||||||
|
reset_test_db!
|
||||||
|
@cat = Cat.new(:name => 'Snowball')
|
||||||
|
@toy = CatToy.new
|
||||||
|
@cat.favorite_toy = @toy
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not save parent document when casted model is invalid" do
|
||||||
|
@toy.should_not be_valid
|
||||||
|
@toy.base_doc.save.should be_false
|
||||||
|
lambda{@toy.base_doc.save!}.should raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should save parent document when nested casted model is valid" do
|
||||||
|
@toy.name = "Mr Squeaks"
|
||||||
|
@toy.should be_valid
|
||||||
|
@toy.base_doc.save.should be_true
|
||||||
|
lambda{@toy.base_doc.save!}.should_not raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "callbacks" do
|
||||||
|
before(:each) do
|
||||||
|
@doc = CastedCallbackDoc.new
|
||||||
|
@model = WithCastedCallBackModel.new
|
||||||
|
@doc.callback_model = @model
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "validate" do
|
||||||
|
it "should run before_validate before validating" do
|
||||||
|
@model.run_before_validate.should be_nil
|
||||||
|
@model.should be_valid
|
||||||
|
@model.run_before_validate.should be_true
|
||||||
|
end
|
||||||
|
it "should run after_validate after validating" do
|
||||||
|
@model.run_after_validate.should be_nil
|
||||||
|
@model.should be_valid
|
||||||
|
@model.run_after_validate.should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
require File.dirname(__FILE__) + '/../../spec_helper'
|
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||||
require File.join(FIXTURE_PATH, 'more', 'article')
|
require File.join(FIXTURE_PATH, 'more', 'article')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||||
|
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||||
|
|
||||||
|
|
||||||
describe "ExtendedDocument" do
|
describe "ExtendedDocument" do
|
||||||
|
@ -16,8 +17,11 @@ describe "ExtendedDocument" do
|
||||||
end
|
end
|
||||||
|
|
||||||
class WithCallBacks < CouchRest::ExtendedDocument
|
class WithCallBacks < CouchRest::ExtendedDocument
|
||||||
|
include ::CouchRest::Validation
|
||||||
use_database TEST_SERVER.default_database
|
use_database TEST_SERVER.default_database
|
||||||
property :name
|
property :name
|
||||||
|
property :run_before_validate
|
||||||
|
property :run_after_validate
|
||||||
property :run_before_save
|
property :run_before_save
|
||||||
property :run_after_save
|
property :run_after_save
|
||||||
property :run_before_create
|
property :run_before_create
|
||||||
|
@ -25,6 +29,12 @@ describe "ExtendedDocument" do
|
||||||
property :run_before_update
|
property :run_before_update
|
||||||
property :run_after_update
|
property :run_after_update
|
||||||
|
|
||||||
|
validate_callback :before do |object|
|
||||||
|
object.run_before_validate = true
|
||||||
|
end
|
||||||
|
validate_callback :after do |object|
|
||||||
|
object.run_after_validate = true
|
||||||
|
end
|
||||||
save_callback :before do |object|
|
save_callback :before do |object|
|
||||||
object.run_before_save = true
|
object.run_before_save = true
|
||||||
end
|
end
|
||||||
|
@ -87,12 +97,12 @@ describe "ExtendedDocument" do
|
||||||
it "should be a new_record" do
|
it "should be a new_record" do
|
||||||
@obj = Basic.new
|
@obj = Basic.new
|
||||||
@obj.rev.should be_nil
|
@obj.rev.should be_nil
|
||||||
@obj.should be_a_new_record
|
@obj.should be_new
|
||||||
end
|
end
|
||||||
it "should be a new_document" do
|
it "should be a new_document" do
|
||||||
@obj = Basic.new
|
@obj = Basic.new
|
||||||
@obj.rev.should be_nil
|
@obj.rev.should be_nil
|
||||||
@obj.should be_a_new_document
|
@obj.should be_new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -109,6 +119,12 @@ describe "ExtendedDocument" do
|
||||||
@art['title'].should == "super danger"
|
@art['title'].should == "super danger"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should also work using attributes= alias" do
|
||||||
|
@art.respond_to?(:attributes=).should be_true
|
||||||
|
@art.attributes = {'date' => Time.now, :title => "something else"}
|
||||||
|
@art['title'].should == "something else"
|
||||||
|
end
|
||||||
|
|
||||||
it "should flip out if an attribute= method is missing" do
|
it "should flip out if an attribute= method is missing" do
|
||||||
lambda {
|
lambda {
|
||||||
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||||
|
@ -389,7 +405,7 @@ describe "ExtendedDocument" do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be a new document" do
|
it "should be a new document" do
|
||||||
@art.should be_a_new_document
|
@art.should be_new
|
||||||
@art.title.should be_nil
|
@art.title.should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -497,6 +513,19 @@ describe "ExtendedDocument" do
|
||||||
@doc = WithCallBacks.new
|
@doc = WithCallBacks.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
describe "validate" do
|
||||||
|
it "should run before_validate before validating" do
|
||||||
|
@doc.run_before_validate.should be_nil
|
||||||
|
@doc.should be_valid
|
||||||
|
@doc.run_before_validate.should be_true
|
||||||
|
end
|
||||||
|
it "should run after_validate after validating" do
|
||||||
|
@doc.run_after_validate.should be_nil
|
||||||
|
@doc.should be_valid
|
||||||
|
@doc.run_after_validate.should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
describe "save" do
|
describe "save" do
|
||||||
it "should run the after filter after saving" do
|
it "should run the after filter after saving" do
|
||||||
@doc.run_after_save.should be_nil
|
@doc.run_after_save.should be_nil
|
||||||
|
@ -555,4 +584,49 @@ describe "ExtendedDocument" do
|
||||||
@doc.other_arg.should == "foo-foo"
|
@doc.other_arg.should == "foo-foo"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "recursive validation on an extended document" do
|
||||||
|
before :each do
|
||||||
|
reset_test_db!
|
||||||
|
@cat = Cat.new(:name => 'Sockington')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not save if a nested casted model is invalid" do
|
||||||
|
@cat.favorite_toy = CatToy.new
|
||||||
|
@cat.should_not be_valid
|
||||||
|
@cat.save.should be_false
|
||||||
|
lambda{@cat.save!}.should raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should save when nested casted model is valid" do
|
||||||
|
@cat.favorite_toy = CatToy.new(:name => 'Squeaky')
|
||||||
|
@cat.should be_valid
|
||||||
|
@cat.save.should be_true
|
||||||
|
lambda{@cat.save!}.should_not raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not save when nested collection contains an invalid casted model" do
|
||||||
|
@cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
|
||||||
|
@cat.should_not be_valid
|
||||||
|
@cat.save.should be_false
|
||||||
|
lambda{@cat.save!}.should raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should save when nested collection contains valid casted models" do
|
||||||
|
@cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
|
||||||
|
@cat.should be_valid
|
||||||
|
@cat.save.should be_true
|
||||||
|
lambda{@cat.save!}.should_not raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not fail if the nested casted model doesn't have validation" do
|
||||||
|
Cat.property :trainer, :cast_as => 'Person'
|
||||||
|
Cat.validates_present :name
|
||||||
|
cat = Cat.new(:name => 'Mr Bigglesworth')
|
||||||
|
cat.trainer = Person.new
|
||||||
|
cat.trainer.validatable?.should be_false
|
||||||
|
cat.should be_valid
|
||||||
|
cat.save.should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ require File.join(FIXTURE_PATH, 'more', 'card')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'invoice')
|
require File.join(FIXTURE_PATH, 'more', 'invoice')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'service')
|
require File.join(FIXTURE_PATH, 'more', 'service')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'event')
|
require File.join(FIXTURE_PATH, 'more', 'event')
|
||||||
|
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||||
|
|
||||||
|
|
||||||
describe "ExtendedDocument properties" do
|
describe "ExtendedDocument properties" do
|
||||||
|
@ -94,7 +95,7 @@ describe "ExtendedDocument properties" do
|
||||||
@invoice.location = nil
|
@invoice.location = nil
|
||||||
@invoice.should_not be_valid
|
@invoice.should_not be_valid
|
||||||
@invoice.save.should be_false
|
@invoice.save.should be_false
|
||||||
@invoice.should be_new_document
|
@invoice.should be_new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -142,5 +143,69 @@ describe "ExtendedDocument properties" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "a newly created casted model" do
|
||||||
|
before(:each) do
|
||||||
|
reset_test_db!
|
||||||
|
@cat = Cat.new(:name => 'Toonces')
|
||||||
|
@squeaky_mouse = CatToy.new(:name => 'Squeaky')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "assigned assigned to a casted property" do
|
||||||
|
it "should have casted_by set to its parent" do
|
||||||
|
@squeaky_mouse.casted_by.should be_nil
|
||||||
|
@cat.favorite_toy = @squeaky_mouse
|
||||||
|
@squeaky_mouse.casted_by.should === @cat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "appended to a casted collection" do
|
||||||
|
it "should have casted_by set to its parent" do
|
||||||
|
@squeaky_mouse.casted_by.should be_nil
|
||||||
|
@cat.toys << @squeaky_mouse
|
||||||
|
@squeaky_mouse.casted_by.should === @cat
|
||||||
|
@cat.save
|
||||||
|
@cat.toys.first.casted_by.should === @cat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "list assigned to a casted collection" do
|
||||||
|
it "should have casted_by set on all elements" do
|
||||||
|
toy1 = CatToy.new(:name => 'Feather')
|
||||||
|
toy2 = CatToy.new(:name => 'Mouse')
|
||||||
|
@cat.toys = [toy1, toy2]
|
||||||
|
toy1.casted_by.should === @cat
|
||||||
|
toy2.casted_by.should === @cat
|
||||||
|
@cat.save
|
||||||
|
@cat = Cat.get(@cat.id)
|
||||||
|
@cat.toys[0].casted_by.should === @cat
|
||||||
|
@cat.toys[1].casted_by.should === @cat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "a casted model retrieved from the database" do
|
||||||
|
before(:each) do
|
||||||
|
reset_test_db!
|
||||||
|
@cat = Cat.new(:name => 'Stimpy')
|
||||||
|
@cat.favorite_toy = CatToy.new(:name => 'Stinky')
|
||||||
|
@cat.toys << CatToy.new(:name => 'Feather')
|
||||||
|
@cat.toys << CatToy.new(:name => 'Mouse')
|
||||||
|
@cat.save
|
||||||
|
@cat = Cat.get(@cat.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "as a casted property" do
|
||||||
|
it "should already be casted_by its parent" do
|
||||||
|
@cat.favorite_toy.casted_by.should === @cat
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "from a casted collection" do
|
||||||
|
it "should already be casted_by its parent" do
|
||||||
|
@cat.toys[0].casted_by.should === @cat
|
||||||
|
@cat.toys[1].casted_by.should === @cat
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
2
spec/fixtures/more/article.rb
vendored
2
spec/fixtures/more/article.rb
vendored
|
@ -29,6 +29,6 @@ class Article < CouchRest::ExtendedDocument
|
||||||
save_callback :before, :generate_slug_from_title
|
save_callback :before, :generate_slug_from_title
|
||||||
|
|
||||||
def generate_slug_from_title
|
def generate_slug_from_title
|
||||||
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
|
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new?
|
||||||
end
|
end
|
||||||
end
|
end
|
1
spec/fixtures/more/cat.rb
vendored
1
spec/fixtures/more/cat.rb
vendored
|
@ -6,6 +6,7 @@ class Cat < CouchRest::ExtendedDocument
|
||||||
|
|
||||||
property :name
|
property :name
|
||||||
property :toys, :cast_as => ['CatToy'], :default => []
|
property :toys, :cast_as => ['CatToy'], :default => []
|
||||||
|
property :favorite_toy, :cast_as => 'CatToy'
|
||||||
end
|
end
|
||||||
|
|
||||||
class CatToy < Hash
|
class CatToy < Hash
|
||||||
|
|
1
spec/fixtures/more/person.rb
vendored
1
spec/fixtures/more/person.rb
vendored
|
@ -1,6 +1,7 @@
|
||||||
class Person < Hash
|
class Person < Hash
|
||||||
include ::CouchRest::CastedModel
|
include ::CouchRest::CastedModel
|
||||||
property :name
|
property :name
|
||||||
|
property :pet, :cast_as => 'Cat'
|
||||||
|
|
||||||
def last_name
|
def last_name
|
||||||
name.last
|
name.last
|
||||||
|
|
Loading…
Reference in a new issue