<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -27,7 +27,7 @@ module CouchRest
     end
     
     # returns true if the document has never been saved
-    def new_document?
+    def new?
       !rev
     end
     
@@ -67,8 +67,8 @@ module CouchRest
     
     # Returns the CouchDB uri for the document
     def uri(append_rev = false)
-      return nil if new_document?
-      couch_uri = &quot;http://#{database.root}/#{CGI.escape(id)}&quot;
+      return nil if new?
+      couch_uri = &quot;http://#{database.uri}/#{CGI.escape(id)}&quot;
       if append_rev == true
         couch_uri &lt;&lt; &quot;?rev=#{rev}&quot;
       elsif append_rev.kind_of?(Integer)</diff>
      <filename>lib/couchrest/core/document.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,7 +17,7 @@ module CouchRest
       end
       
       def apply_defaults
-        return if self.respond_to?(:new_document?) &amp;&amp; (new_document? == false)
+        return if self.respond_to?(:new?) &amp;&amp; (new? == false)
         return unless self.class.respond_to?(:properties) 
         return if self.class.properties.empty?
         # TODO: cache the default object
@@ -44,12 +44,14 @@ module CouchRest
           target = property.type
           if target.is_a?(Array)
             klass = ::CouchRest.constantize(target[0])
-            self[property.name] = self[key].collect do |value|
+            arr = self[key].collect do |value|
               # Auto parse Time objects
               obj = ( (property.init_method == 'new') &amp;&amp; klass == Time) ? Time.parse(value) : klass.send(property.init_method, value)
               obj.casted_by = self if obj.respond_to?(:casted_by)
+              obj.document_saved = true if obj.respond_to?(:document_saved)
               obj
             end
+            self[property.name] = target[0] != 'String' ? CastedArray.new(arr) : arr
           else
             # Auto parse Time objects
             self[property.name] = if ((property.init_method == 'new') &amp;&amp; target == 'Time') 
@@ -60,7 +62,9 @@ module CouchRest
               klass.send(property.init_method, self[key].dup)
             end
             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
+          self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
         end
       end
       
@@ -107,6 +111,14 @@ module CouchRest
             meth = property.name
             class_eval &lt;&lt;-EOS
               def #{meth}=(value)
+                if #{property.casted} &amp;&amp; value.is_a?(Array)
+                  arr = CastedArray.new
+                  arr.casted_by = self
+                  value.each { |v| arr &lt;&lt; v }
+                  value = arr
+                elsif #{property.casted}
+                  value.casted_by = self if value.respond_to?(:casted_by)
+                end
                 self['#{meth}'] = value
               end
             EOS</diff>
      <filename>lib/couchrest/mixins/properties.rb</filename>
    </modified>
    <modified>
      <diff>@@ -51,6 +51,9 @@ module CouchRest
     def self.included(base)
       base.extlib_inheritable_accessor(:auto_validation)
       base.class_eval &lt;&lt;-EOS, __FILE__, __LINE__
+          # Callbacks
+          define_callbacks :validate
+          
           # Turn off auto validation by default
           self.auto_validation ||= false
           
@@ -72,6 +75,7 @@ module CouchRest
       
       base.extend(ClassMethods)
       base.class_eval &lt;&lt;-EOS, __FILE__, __LINE__
+        define_callbacks :validate
         if method_defined?(:_run_save_callbacks)
           save_callback :before, :check_validations
         end
@@ -115,8 +119,7 @@ module CouchRest
     # Check if a resource is valid in a given context
     #
     def valid?(context = :default)
-      result = self.class.validators.execute(context, self)
-      result &amp;&amp; validate_casted_arrays
+      recursive_valid?(self, context, true)
     end
     
     # checking on casted objects
@@ -133,29 +136,24 @@ module CouchRest
       result
     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
     #
     def recursive_valid?(target, context, state)
       valid = state
-      target.instance_variables.each do |ivar|
-        ivar_value = target.instance_variable_get(ivar)
-        if ivar_value.validatable?
-          valid = valid &amp;&amp; recursive_valid?(ivar_value, context, valid)
-        elsif ivar_value.respond_to?(:each)
-          ivar_value.each do |item|
+      target.each do |key, prop|
+        if prop.is_a?(Array)
+          prop.each do |item|
             if item.validatable?
-              valid = valid &amp;&amp; recursive_valid?(item, context, valid)
+              valid = recursive_valid?(item, context, valid) &amp;&amp; valid
             end
           end
+        elsif prop.validatable?
+          valid = recursive_valid?(prop, context, valid) &amp;&amp; valid
         end
       end
-      return valid &amp;&amp; target.valid?
+      target._run_validate_callbacks do
+        target.class.validators.execute(context, target) &amp;&amp; valid
+      end
     end
 
 
@@ -218,15 +216,6 @@ module CouchRest
             end                                       # end
           EOS
         end
-
-        all = &quot;all_valid_for_#{context.to_s}?&quot;        # all_valid_for_signup?
-        if !self.instance_methods.include?(all)
-          class_eval &lt;&lt;-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
 
       # Create a new validator of the given klazz and push it onto the</diff>
      <filename>lib/couchrest/mixins/validation.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,8 +4,10 @@ module CouchRest
   module CastedModel
     
     def self.included(base)
+      base.send(:include, CouchRest::Callbacks)
       base.send(:include, CouchRest::Mixins::Properties)
       base.send(:attr_accessor, :casted_by)
+      base.send(:attr_accessor, :document_saved)
     end
     
     def initialize(keys={})
@@ -25,5 +27,31 @@ module CouchRest
     def [] key
       super(key.to_s)
     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, &quot;#{k}= method not available, use property :#{k}&quot; unless self.respond_to?(&quot;#{k}=&quot;)
+      end      
+      hash.each do |k, v|
+        self.send(&quot;#{k}=&quot;,v)
+      end
+    end
+    alias :attributes= :update_attributes_without_saving
+    
   end
-end
\ No newline at end of file
+end</diff>
      <filename>lib/couchrest/more/casted_model.rb</filename>
    </modified>
    <modified>
      <diff>@@ -64,7 +64,7 @@ module CouchRest
         
         save_callback :before do |object|
           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
       EOS
     end
@@ -110,6 +110,19 @@ module CouchRest
       self.class.properties
     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
     # 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.    
@@ -121,6 +134,7 @@ module CouchRest
         self.send(&quot;#{k}=&quot;,v)
       end
     end
+    alias :attributes= :update_attributes_without_saving
 
     # Takes a hash as argument, and applies the values by using writer methods
     # for each key. Raises a NoMethodError if the corresponding methods are
@@ -131,7 +145,7 @@ module CouchRest
     end
 
     # for compatibility with old-school frameworks
-    alias :new_record? :new_document?
+    alias :new_record? :new?
     
     # Trigger the callbacks (before, after, around)
     # and create the document
@@ -152,7 +166,7 @@ module CouchRest
     # unlike save, create returns the newly created document
     def create_without_callbacks(bulk =false)
       raise ArgumentError, &quot;a document requires a database to be created to (The document or the #{self.class} default database were not set)&quot; unless database
-      set_unique_id if new_document? &amp;&amp; self.respond_to?(:set_unique_id)
+      set_unique_id if new? &amp;&amp; self.respond_to?(:set_unique_id)
       result = database.save_doc(self, bulk)
       (result[&quot;ok&quot;] == true) ? self : false
     end
@@ -167,7 +181,7 @@ module CouchRest
     # only if the document isn't new
     def update(bulk = false)
       caught = catch(:halt)  do
-        if self.new_document?
+        if self.new?
           save(bulk)
         else
           _run_update_callbacks do
@@ -183,7 +197,7 @@ module CouchRest
     # and save the document
     def save(bulk = false)
       caught = catch(:halt)  do
-        if self.new_document?
+        if self.new?
           _run_save_callbacks do
             save_without_callbacks(bulk)
           end
@@ -197,8 +211,9 @@ module CouchRest
     # Returns a boolean value
     def save_without_callbacks(bulk = false)
       raise ArgumentError, &quot;a document requires a database to be saved to (The document or the #{self.class} default database were not set)&quot; unless database
-      set_unique_id if new_document? &amp;&amp; self.respond_to?(:set_unique_id)
+      set_unique_id if new? &amp;&amp; self.respond_to?(:set_unique_id)
       result = database.save_doc(self, bulk)
+      mark_as_saved if result[&quot;ok&quot;] == true
       result[&quot;ok&quot;] == true
     end
     
@@ -224,5 +239,22 @@ module CouchRest
       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</diff>
      <filename>lib/couchrest/more/extended_document.rb</filename>
    </modified>
    <modified>
      <diff>@@ -38,3 +38,22 @@ module CouchRest
     
   end
 end
+
+class CastedArray &lt; Array
+  attr_accessor :casted_by
+  
+  def &lt;&lt; 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
\ No newline at end of file</diff>
      <filename>lib/couchrest/more/property.rb</filename>
    </modified>
    <modified>
      <diff>@@ -14,6 +14,9 @@ end
 
 
 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,
   # doesn't appear to Rails routing as a Hash of options
   def is_a?(o)
@@ -21,8 +24,22 @@ CouchRest::Document.class_eval do
     super
   end
   alias_method :kind_of?, :is_a?
+  
+  # Gives extended doc a seamless logger
+  def logger
+    ActiveRecord::Base.logger
+  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')
 </diff>
      <filename>lib/couchrest/support/rails.rb</filename>
    </modified>
    <modified>
      <diff>@@ -253,7 +253,7 @@ describe CouchRest::Database do
   describe &quot;PUT attachment from file&quot; do
     before(:each) do
       filename = FIXTURE_PATH + '/attachments/couchdb.png'
-      @file = File.open(filename)
+      @file = File.open(filename, &quot;rb&quot;)
     end
     after(:each) do
       @file.close</diff>
      <filename>spec/couchrest/core/database_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -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', 'cat')
 require File.join(FIXTURE_PATH, 'more', 'person')
+require File.join(FIXTURE_PATH, 'more', 'question')
+require File.join(FIXTURE_PATH, 'more', 'course')
 
 
 class WithCastedModelMixin &lt; Hash
@@ -21,6 +23,26 @@ class DummyModel &lt; CouchRest::ExtendedDocument
   property :keywords,         :cast_as =&gt; [&quot;String&quot;]
 end
 
+class CastedCallbackDoc &lt; CouchRest::ExtendedDocument
+  use_database TEST_SERVER.default_database
+  raise &quot;Default DB not set&quot; if TEST_SERVER.default_database.nil?
+  property :callback_model, :cast_as =&gt; 'WithCastedCallBackModel'
+end
+class WithCastedCallBackModel &lt; 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 &quot;A non hash class including CastedModel&quot; do
@@ -106,7 +128,40 @@ describe CouchRest::CastedModel do
       @obj.keywords.should be_an_instance_of(Array)
       @obj.keywords.first.should == 'couch'
     end
+  end
+  
+  describe &quot;update attributes without saving&quot; do
+    before(:each) do
+      @question = Question.new(:q =&gt; &quot;What is your quest?&quot;, :a =&gt; &quot;To seek the Holy Grail&quot;)
+    end
+    it &quot;should work for attribute= methods&quot; do
+      @question.q.should == &quot;What is your quest?&quot;
+      @question['a'].should == &quot;To seek the Holy Grail&quot;
+      @question.update_attributes_without_saving(:q =&gt; &quot;What is your favorite color?&quot;, 'a' =&gt; &quot;Blue&quot;)
+      @question['q'].should == &quot;What is your favorite color?&quot;
+      @question.a.should == &quot;Blue&quot;
+    end
+    
+    it &quot;should also work for attributes= alias&quot; do
+      @question.respond_to?(:attributes=).should be_true
+      @question.attributes = {:q =&gt; &quot;What is your favorite color?&quot;, 'a' =&gt; &quot;Blue&quot;}
+      @question['q'].should == &quot;What is your favorite color?&quot;
+      @question.a.should == &quot;Blue&quot;
+    end
+    
+    it &quot;should flip out if an attribute= method is missing&quot; do
+      lambda {
+        @q.update_attributes_without_saving('foo' =&gt; &quot;something&quot;, :a =&gt; &quot;No green&quot;)
+      }.should raise_error(NoMethodError)
+    end
     
+    it &quot;should not change any attributes if there is an error&quot; do
+      lambda {
+        @q.update_attributes_without_saving('foo' =&gt; &quot;something&quot;, :a =&gt; &quot;No green&quot;)
+      }.should raise_error(NoMethodError)
+      @question.q.should == &quot;What is your quest?&quot;
+      @question.a.should == &quot;To seek the Holy Grail&quot;
+    end
   end
   
   describe &quot;saved document with casted models&quot; do
@@ -171,7 +226,173 @@ describe CouchRest::CastedModel do
       cat.masters.push Person.new
       cat.should be_valid
     end
+  end
+  
+  describe &quot;calling valid?&quot; do
+    before :each do
+      @cat = Cat.new
+      @toy1 = CatToy.new
+      @toy2 = CatToy.new
+      @toy3 = CatToy.new
+      @cat.favorite_toy = @toy1
+      @cat.toys &lt;&lt; @toy2
+      @cat.toys &lt;&lt; @toy3
+    end
+    
+    describe &quot;on the top document&quot; do
+      it &quot;should put errors on all invalid casted models&quot; 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 &quot;should not put errors on valid casted models&quot; do
+        @toy1.name = &quot;Feather&quot;
+        @toy2.name = &quot;Twine&quot; 
+        @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 &quot;on a casted model property&quot; do
+      it &quot;should only validate itself&quot; 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 &quot;on a casted model inside a casted collection&quot; do
+      it &quot;should only validate itself&quot; 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 &quot;calling new? on a casted model&quot; do
+    before :each do
+      reset_test_db!
+      @cat = Cat.new(:name =&gt; 'Sockington')
+      @cat.favorite_toy = CatToy.new(:name =&gt; 'Catnip Ball')
+      @cat.toys &lt;&lt; CatToy.new(:name =&gt; 'Fuzzy Stick')
+    end
     
+    it &quot;should be true on new&quot; do
+      CatToy.new.should be_new
+      CatToy.new.new_record?.should be_true
+    end
+    
+    it &quot;should be true after assignment&quot; do
+      @cat.favorite_toy.should be_new
+      @cat.toys.first.should be_new
+    end
+    
+    it &quot;should not be true after create or save&quot; do
+      @cat.create
+      @cat.save
+      @cat.favorite_toy.should_not be_new
+      @cat.toys.first.should_not be_new
+    end
+    
+    it &quot;should not be true after get from the database&quot; do
+      @cat.save
+      @cat = Cat.get(@cat.id)
+      @cat.favorite_toy.should_not be_new
+      @cat.toys.first.should_not be_new
+    end
+    
+    it &quot;should still be true after a failed create or save&quot; 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 &quot;calling base_doc from a nested casted model&quot; do
+    before :each do
+      @course = Course.new(:title =&gt; 'Science 101')
+      @professor = Person.new(:name =&gt; 'Professor Plum')
+      @cat = Cat.new(:name =&gt; 'Scratchy')
+      @toy1 = CatToy.new
+      @toy2 = CatToy.new
+      @course.professor = @professor
+      @professor.pet = @cat
+      @cat.favorite_toy = @toy1
+      @cat.toys &lt;&lt; @toy2
+    end
+    
+    it &quot;should reference the top document for&quot; 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 &quot;should call setter on top document&quot; do
+      @toy1.base_doc.title = 'Tom Foolery'
+      @course.title.should == 'Tom Foolery'
+    end
+    
+    it &quot;should return nil if not yet casted&quot; do
+      person = Person.new
+      person.base_doc.should == nil
+    end
+  end
+  
+  describe &quot;calling base_doc.save from a nested casted model&quot; do
+    before :each do
+      reset_test_db!
+      @cat = Cat.new(:name =&gt; 'Snowball')
+      @toy = CatToy.new
+      @cat.favorite_toy = @toy
+    end
+    
+    it &quot;should not save parent document when casted model is invalid&quot; do
+      @toy.should_not be_valid
+      @toy.base_doc.save.should be_false
+      lambda{@toy.base_doc.save!}.should raise_error
+    end
+    
+    it &quot;should save parent document when nested casted model is valid&quot; do
+      @toy.name = &quot;Mr Squeaks&quot;
+      @toy.should be_valid
+      @toy.base_doc.save.should be_true
+      lambda{@toy.base_doc.save!}.should_not raise_error
+    end
   end
   
+  describe &quot;callbacks&quot; do
+    before(:each) do
+      @doc = CastedCallbackDoc.new
+      @model = WithCastedCallBackModel.new
+      @doc.callback_model = @model
+    end
+    
+    describe &quot;validate&quot; do
+      it &quot;should run before_validate before validating&quot; do
+        @model.run_before_validate.should be_nil
+        @model.should be_valid
+        @model.run_before_validate.should be_true
+      end
+      it &quot;should run after_validate after validating&quot; do
+        @model.run_after_validate.should be_nil
+        @model.should be_valid
+        @model.run_after_validate.should be_true
+      end      
+    end
+  end
 end</diff>
      <filename>spec/couchrest/more/casted_model_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,7 @@
 require File.dirname(__FILE__) + '/../../spec_helper'
 require File.join(FIXTURE_PATH, 'more', 'article')
 require File.join(FIXTURE_PATH, 'more', 'course')
+require File.join(FIXTURE_PATH, 'more', 'cat')
 
 
 describe &quot;ExtendedDocument&quot; do
@@ -16,8 +17,11 @@ describe &quot;ExtendedDocument&quot; do
   end
   
   class WithCallBacks &lt; CouchRest::ExtendedDocument
+    include ::CouchRest::Validation
     use_database TEST_SERVER.default_database
     property :name
+    property :run_before_validate
+    property :run_after_validate
     property :run_before_save
     property :run_after_save
     property :run_before_create
@@ -25,6 +29,12 @@ describe &quot;ExtendedDocument&quot; do
     property :run_before_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| 
       object.run_before_save = true
     end
@@ -87,12 +97,12 @@ describe &quot;ExtendedDocument&quot; do
     it &quot;should be a new_record&quot; do
       @obj = Basic.new
       @obj.rev.should be_nil
-      @obj.should be_a_new_record
+      @obj.should be_new
     end
     it &quot;should be a new_document&quot; do
       @obj = Basic.new
       @obj.rev.should be_nil
-      @obj.should be_a_new_document
+      @obj.should be_new
     end
   end
   
@@ -109,6 +119,12 @@ describe &quot;ExtendedDocument&quot; do
       @art['title'].should == &quot;super danger&quot;
     end
     
+    it &quot;should also work using attributes= alias&quot; do
+      @art.respond_to?(:attributes=).should be_true
+      @art.attributes = {'date' =&gt; Time.now, :title =&gt; &quot;something else&quot;}
+      @art['title'].should == &quot;something else&quot;
+    end
+    
     it &quot;should flip out if an attribute= method is missing&quot; do
       lambda {
         @art.update_attributes_without_saving('slug' =&gt; &quot;new-slug&quot;, :title =&gt; &quot;super danger&quot;)        
@@ -389,7 +405,7 @@ describe &quot;ExtendedDocument&quot; do
     end
     
     it &quot;should be a new document&quot; do
-      @art.should be_a_new_document
+      @art.should be_new
       @art.title.should be_nil
     end
     
@@ -497,6 +513,19 @@ describe &quot;ExtendedDocument&quot; do
       @doc = WithCallBacks.new
     end
     
+    
+    describe &quot;validate&quot; do
+      it &quot;should run before_validate before validating&quot; do
+        @doc.run_before_validate.should be_nil
+        @doc.should be_valid
+        @doc.run_before_validate.should be_true
+      end
+      it &quot;should run after_validate after validating&quot; do
+        @doc.run_after_validate.should be_nil
+        @doc.should be_valid
+        @doc.run_after_validate.should be_true
+      end
+    end
     describe &quot;save&quot; do
       it &quot;should run the after filter after saving&quot; do
         @doc.run_after_save.should be_nil
@@ -555,4 +584,49 @@ describe &quot;ExtendedDocument&quot; do
       @doc.other_arg.should == &quot;foo-foo&quot;
     end
   end
+  
+  describe &quot;recursive validation on an extended document&quot; do
+    before :each do
+      reset_test_db!
+      @cat = Cat.new(:name =&gt; 'Sockington')
+    end
+    
+    it &quot;should not save if a nested casted model is invalid&quot; do
+      @cat.favorite_toy = CatToy.new
+      @cat.should_not be_valid
+      @cat.save.should be_false
+      lambda{@cat.save!}.should raise_error
+    end
+    
+    it &quot;should save when nested casted model is valid&quot; do
+      @cat.favorite_toy = CatToy.new(:name =&gt; 'Squeaky')
+      @cat.should be_valid
+      @cat.save.should be_true
+      lambda{@cat.save!}.should_not raise_error
+    end
+    
+    it &quot;should not save when nested collection contains an invalid casted model&quot; do
+      @cat.toys = [CatToy.new(:name =&gt; 'Feather'), CatToy.new]
+      @cat.should_not be_valid
+      @cat.save.should be_false
+      lambda{@cat.save!}.should raise_error
+    end
+    
+    it &quot;should save when nested collection contains valid casted models&quot; do
+      @cat.toys = [CatToy.new(:name =&gt; 'feather'), CatToy.new(:name =&gt; 'ball-o-twine')]
+      @cat.should be_valid
+      @cat.save.should be_true
+      lambda{@cat.save!}.should_not raise_error
+    end
+    
+    it &quot;should not fail if the nested casted model doesn't have validation&quot; do
+      Cat.property :trainer, :cast_as =&gt; 'Person'
+      Cat.validates_present :name
+      cat = Cat.new(:name =&gt; 'Mr Bigglesworth')
+      cat.trainer = Person.new
+      cat.trainer.validatable?.should be_false
+      cat.should be_valid
+      cat.save.should be_true
+    end
+  end
 end</diff>
      <filename>spec/couchrest/more/extended_doc_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,6 +4,7 @@ require File.join(FIXTURE_PATH, 'more', 'card')
 require File.join(FIXTURE_PATH, 'more', 'invoice')
 require File.join(FIXTURE_PATH, 'more', 'service')
 require File.join(FIXTURE_PATH, 'more', 'event')
+require File.join(FIXTURE_PATH, 'more', 'cat')
 
 
 describe &quot;ExtendedDocument properties&quot; do
@@ -94,7 +95,7 @@ describe &quot;ExtendedDocument properties&quot; do
       @invoice.location = nil
       @invoice.should_not be_valid
       @invoice.save.should be_false
-      @invoice.should be_new_document
+      @invoice.should be_new
     end
   end
   
@@ -141,6 +142,70 @@ describe &quot;ExtendedDocument properties&quot; do
         @event['occurs_at'].should be_an_instance_of(Time)
       end
     end
+  end  
+end
+
+describe &quot;a newly created casted model&quot; do
+  before(:each) do
+    reset_test_db!
+    @cat = Cat.new(:name =&gt; 'Toonces')
+    @squeaky_mouse = CatToy.new(:name =&gt; 'Squeaky')
   end
   
+  describe &quot;assigned assigned to a casted property&quot; do
+    it &quot;should have casted_by set to its parent&quot; do
+      @squeaky_mouse.casted_by.should be_nil
+      @cat.favorite_toy = @squeaky_mouse
+      @squeaky_mouse.casted_by.should === @cat
+    end
+  end
+  
+  describe &quot;appended to a casted collection&quot; do
+    it &quot;should have casted_by set to its parent&quot; do
+      @squeaky_mouse.casted_by.should be_nil
+      @cat.toys &lt;&lt; @squeaky_mouse
+      @squeaky_mouse.casted_by.should === @cat
+      @cat.save
+      @cat.toys.first.casted_by.should === @cat
+    end
+  end
+  
+  describe &quot;list assigned to a casted collection&quot; do
+    it &quot;should have casted_by set on all elements&quot; do
+      toy1 = CatToy.new(:name =&gt; 'Feather')
+      toy2 = CatToy.new(:name =&gt; '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 &quot;a casted model retrieved from the database&quot; do
+  before(:each) do
+    reset_test_db!
+    @cat = Cat.new(:name =&gt; 'Stimpy')
+    @cat.favorite_toy = CatToy.new(:name =&gt; 'Stinky')
+    @cat.toys &lt;&lt; CatToy.new(:name =&gt; 'Feather')
+    @cat.toys &lt;&lt; CatToy.new(:name =&gt; 'Mouse')
+    @cat.save
+    @cat = Cat.get(@cat.id)
+  end
+  
+  describe &quot;as a casted property&quot; do
+    it &quot;should already be casted_by its parent&quot; do
+      @cat.favorite_toy.casted_by.should === @cat
+    end
+  end
+  
+  describe &quot;from a casted collection&quot; do
+    it &quot;should already be casted_by its parent&quot; do
+      @cat.toys[0].casted_by.should === @cat
+      @cat.toys[1].casted_by.should === @cat
+    end
+  end
+end
\ No newline at end of file</diff>
      <filename>spec/couchrest/more/property_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -29,6 +29,6 @@ class Article &lt; CouchRest::ExtendedDocument
   save_callback :before, :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
\ No newline at end of file</diff>
      <filename>spec/fixtures/more/article.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,6 +6,7 @@ class Cat &lt; CouchRest::ExtendedDocument
 
   property :name
   property :toys, :cast_as =&gt; ['CatToy'], :default =&gt; []
+  property :favorite_toy, :cast_as =&gt; 'CatToy'
 end
 
 class CatToy &lt; Hash</diff>
      <filename>spec/fixtures/more/cat.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,7 @@
 class Person &lt; Hash
   include ::CouchRest::CastedModel
   property :name
+  property :pet, :cast_as =&gt; 'Cat'
   
   def last_name
     name.last</diff>
      <filename>spec/fixtures/more/person.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>c18567f8fcb0ed9d11ef1218afffc426d7c5f6a1</id>
    </parent>
    <parent>
      <id>1c6e073b47cdf09f47103d18fe4ba3f8dc2332a4</id>
    </parent>
  </parents>
  <author>
    <name>Seth Ladd</name>
    <email>sladd@camber.com</email>
  </author>
  <url>http://github.com/will/couchrest/commit/7246801f5781571783c1ed7e6c87bafac2adcfc0</url>
  <id>7246801f5781571783c1ed7e6c87bafac2adcfc0</id>
  <committed-date>2009-06-08T13:10:59-07:00</committed-date>
  <authored-date>2009-06-08T13:10:59-07:00</authored-date>
  <message>merged in sporkd</message>
  <tree>03d48bec99fac89875f53f7f4f9bb8cfde2473d9</tree>
  <committer>
    <name>Seth Ladd</name>
    <email>sladd@camber.com</email>
  </committer>
</commit>
