refactoring how casting works
This commit is contained in:
parent
209e36f61b
commit
92d7fdb94d
|
@ -37,37 +37,55 @@ module CouchRest
|
||||||
def cast_keys
|
def cast_keys
|
||||||
return unless self.class.properties
|
return unless self.class.properties
|
||||||
self.class.properties.each do |property|
|
self.class.properties.each do |property|
|
||||||
next unless property.casted
|
cast_property(property)
|
||||||
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
|
||||||
# Don't cast the property unless it has a value
|
|
||||||
next unless self[key]
|
|
||||||
target = property.type
|
|
||||||
if target.is_a?(Array)
|
|
||||||
klass = ::CouchRest.constantize(target[0])
|
|
||||||
arr = self[key].collect do |value|
|
|
||||||
# Auto parse Time objects
|
|
||||||
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.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') && target == 'Time')
|
|
||||||
self[key].is_a?(String) ? Time.parse(self[key].dup) : self[key]
|
|
||||||
else
|
|
||||||
# Let people use :send as a Time parse arg
|
|
||||||
klass = ::CouchRest.constantize(target)
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cast_property(property)
|
||||||
|
return unless property.casted
|
||||||
|
key = self.has_key?(property.name) ? property.name : property.name.to_sym
|
||||||
|
# Don't cast the property unless it has a value
|
||||||
|
return unless self[key]
|
||||||
|
target = property.type
|
||||||
|
if target.is_a?(Array)
|
||||||
|
klass = ::CouchRest.constantize(target[0])
|
||||||
|
arr = self[key].dup.collect do |value|
|
||||||
|
unless value.instance_of?(klass)
|
||||||
|
value = convert_property_value(property, klass, value)
|
||||||
|
end
|
||||||
|
associate_casted_to_parent(value)
|
||||||
|
value
|
||||||
|
end
|
||||||
|
self[key] = target[0] != 'String' ? CastedArray.new(arr) : arr
|
||||||
|
else
|
||||||
|
klass = ::CouchRest.constantize(target)
|
||||||
|
unless self[key].instance_of?(klass)
|
||||||
|
self[key] = convert_property_value(property, klass, self[property.name])
|
||||||
|
end
|
||||||
|
associate_casted_to_parent(self[property.name])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def associate_casted_to_parent(casted)
|
||||||
|
casted.casted_by = self if casted.respond_to?(:casted_by)
|
||||||
|
casted.document_saved = true if casted.respond_to?(:document_saved)
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_property_value(property, klass, value)
|
||||||
|
if ((property.init_method == 'new') && klass.to_s == 'Time')
|
||||||
|
value.is_a?(String) ? Time.parse(value.dup) : value
|
||||||
|
else
|
||||||
|
klass.send(property.init_method, value.dup)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_property_by_name(property_name)
|
||||||
|
return unless self.class.properties
|
||||||
|
property = self.class.properties.detect{|property| property.name == property_name}
|
||||||
|
return unless property
|
||||||
|
cast_property(property)
|
||||||
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
def property(name, options={})
|
def property(name, options={})
|
||||||
|
@ -108,28 +126,17 @@ module CouchRest
|
||||||
|
|
||||||
# defines the setter for the property (and optional aliases)
|
# defines the setter for the property (and optional aliases)
|
||||||
def create_property_setter(property)
|
def create_property_setter(property)
|
||||||
meth = property.name
|
property_name = property.name
|
||||||
class_eval <<-EOS
|
class_eval <<-EOS
|
||||||
def #{meth}=(value)
|
def #{property_name}=(value)
|
||||||
if #{property.casted} && value.is_a?(Array)
|
self['#{property_name}'] = value
|
||||||
arr = CastedArray.new
|
cast_property_by_name('#{property_name}')
|
||||||
arr.casted_by = self
|
|
||||||
value.each do |v|
|
|
||||||
obj = #{property.type}.new(v)
|
|
||||||
arr << obj
|
|
||||||
end
|
|
||||||
value = arr
|
|
||||||
elsif #{property.casted}
|
|
||||||
value = #{property.type}.new(v)
|
|
||||||
value.casted_by = self if value.respond_to?(:casted_by)
|
|
||||||
end
|
|
||||||
self['#{meth}'] = value
|
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
|
|
||||||
if property.alias
|
if property.alias
|
||||||
class_eval <<-EOS
|
class_eval <<-EOS
|
||||||
alias #{property.alias.to_sym}= #{meth.to_sym}=
|
alias #{property.alias.to_sym}= #{property_name.to_sym}=
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,9 +51,6 @@ module CouchRest
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
||||||
# on the document whenever saving occurs. CouchRest uses a pretty
|
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||||
# decent time format by default. See Time#to_json
|
# decent time format by default. See Time#to_json
|
||||||
|
@ -62,9 +59,6 @@ module CouchRest
|
||||||
property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
||||||
property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
|
||||||
|
|
||||||
def created_at=(ignored); end
|
|
||||||
def updated_at=(ignored); end
|
|
||||||
|
|
||||||
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?
|
object['created_at'] = object['updated_at'] if object.new?
|
||||||
|
@ -130,10 +124,15 @@ module CouchRest
|
||||||
# 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.
|
||||||
def update_attributes_without_saving(hash)
|
def update_attributes_without_saving(hash)
|
||||||
hash.each do |k, v|
|
# remove attributes that cannot be updated, silently ignoring them
|
||||||
|
# which matches Rails behavior when, for instance, setting created_at.
|
||||||
|
# make a copy, we don't want to change arguments
|
||||||
|
attrs = hash.dup
|
||||||
|
%w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
|
||||||
|
attrs.each do |k, v|
|
||||||
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
||||||
end
|
end
|
||||||
hash.each do |k, v|
|
attrs.each do |k, v|
|
||||||
self.send("#{k}=",v)
|
self.send("#{k}=",v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,16 +43,14 @@ describe "assigning a value to casted attribute after initializing an object" do
|
||||||
@car.driver.should be_nil
|
@car.driver.should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Note that this isn't casting the attribute, it's just assigning it a value
|
|
||||||
# (see "should not cast attribute")
|
|
||||||
it "should let you assign the value" do
|
it "should let you assign the value" do
|
||||||
@car.driver = @driver
|
@car.driver = @driver
|
||||||
@car.driver.name.should == 'Matt'
|
@car.driver.name.should == 'Matt'
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not cast attribute" do
|
it "should cast attribute" do
|
||||||
@car.driver = JSON.parse(JSON.generate(@driver))
|
@car.driver = JSON.parse(JSON.generate(@driver))
|
||||||
@car.driver.should_not be_instance_of(Driver)
|
@car.driver.should be_instance_of(Driver)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -284,7 +284,8 @@ describe CouchRest::CastedModel do
|
||||||
before :each do
|
before :each do
|
||||||
reset_test_db!
|
reset_test_db!
|
||||||
@cat = Cat.new(:name => 'Sockington')
|
@cat = Cat.new(:name => 'Sockington')
|
||||||
@cat.favorite_toy = CatToy.new(:name => 'Catnip Ball')
|
@favorite_toy = CatToy.new(:name => 'Catnip Ball')
|
||||||
|
@cat.favorite_toy = @favorite_toy
|
||||||
@cat.toys << CatToy.new(:name => 'Fuzzy Stick')
|
@cat.toys << CatToy.new(:name => 'Fuzzy Stick')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -294,6 +295,7 @@ describe CouchRest::CastedModel do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be true after assignment" do
|
it "should be true after assignment" do
|
||||||
|
@cat.should be_new
|
||||||
@cat.favorite_toy.should be_new
|
@cat.favorite_toy.should be_new
|
||||||
@cat.toys.first.should be_new
|
@cat.toys.first.should be_new
|
||||||
end
|
end
|
||||||
|
@ -336,6 +338,7 @@ describe CouchRest::CastedModel do
|
||||||
|
|
||||||
it "should reference the top document for" do
|
it "should reference the top document for" do
|
||||||
@course.base_doc.should === @course
|
@course.base_doc.should === @course
|
||||||
|
@professor.casted_by.should === @course
|
||||||
@professor.base_doc.should === @course
|
@professor.base_doc.should === @course
|
||||||
@cat.base_doc.should === @course
|
@cat.base_doc.should === @course
|
||||||
@toy1.base_doc.should === @course
|
@toy1.base_doc.should === @course
|
||||||
|
@ -343,6 +346,7 @@ describe CouchRest::CastedModel do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should call setter on top document" do
|
it "should call setter on top document" do
|
||||||
|
@toy1.base_doc.should_not be_nil
|
||||||
@toy1.base_doc.title = 'Tom Foolery'
|
@toy1.base_doc.title = 'Tom Foolery'
|
||||||
@course.title.should == 'Tom Foolery'
|
@course.title.should == 'Tom Foolery'
|
||||||
end
|
end
|
||||||
|
|
|
@ -118,7 +118,22 @@ describe "ExtendedDocument" do
|
||||||
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
||||||
@art['title'].should == "super danger"
|
@art['title'].should == "super danger"
|
||||||
end
|
end
|
||||||
|
it "should silently ignore _id" do
|
||||||
|
@art.update_attributes_without_saving('_id' => 'foobar')
|
||||||
|
@art['_id'].should_not == 'foobar'
|
||||||
|
end
|
||||||
|
it "should silently ignore _rev" do
|
||||||
|
@art.update_attributes_without_saving('_rev' => 'foobar')
|
||||||
|
@art['_rev'].should_not == 'foobar'
|
||||||
|
end
|
||||||
|
it "should silently ignore created_at" do
|
||||||
|
@art.update_attributes_without_saving('created_at' => 'foobar')
|
||||||
|
@art['created_at'].should_not == 'foobar'
|
||||||
|
end
|
||||||
|
it "should silently ignore updated_at" do
|
||||||
|
@art.update_attributes_without_saving('updated_at' => 'foobar')
|
||||||
|
@art['updated_at'].should_not == 'foobar'
|
||||||
|
end
|
||||||
it "should also work using attributes= alias" do
|
it "should also work using attributes= alias" do
|
||||||
@art.respond_to?(:attributes=).should be_true
|
@art.respond_to?(:attributes=).should be_true
|
||||||
@art.attributes = {'date' => Time.now, :title => "something else"}
|
@art.attributes = {'date' => Time.now, :title => "something else"}
|
||||||
|
|
Loading…
Reference in a new issue