Array Properties accept hash with ordered keys and raise error for anything else

This commit is contained in:
Sam Lown 2010-06-18 01:24:49 +02:00
parent dd55466764
commit 1b89f1e1df
7 changed files with 82 additions and 10 deletions

View file

@ -12,6 +12,8 @@
* Refactoring of properties, added read_attribute and write_attribute methods. * Refactoring of properties, added read_attribute and write_attribute methods.
* Now possible to send anything to update_attribtues method. Invalid or readonly attributes will be ignored. * Now possible to send anything to update_attribtues method. Invalid or readonly attributes will be ignored.
* Attributes with arrays are *always* instantiated as a CastedArray. * Attributes with arrays are *always* instantiated as a CastedArray.
* Setting a property of type Array (or keyed hash) must be an array or an error will be raised.
* Now possible to set Array attribute from hash where keys determine order.
* Major enhancements * Major enhancements
* Added support for anonymous CastedModels defined in Documents * Added support for anonymous CastedModels defined in Documents

View file

@ -75,6 +75,9 @@ module CouchRest
# #
# Addtional options match those of the the belongs_to method. # Addtional options match those of the the belongs_to method.
# #
# NOTE: This method is *not* recommended for large collections or collections that change
# frequently! Use with prudence.
#
def collection_of(attrib, *options) def collection_of(attrib, *options)
opts = { opts = {
:foreign_key => attrib.to_s.singularize + '_ids', :foreign_key => attrib.to_s.singularize + '_ids',
@ -153,7 +156,6 @@ module CouchRest
EOS EOS
end end
end end
end end
@ -168,9 +170,9 @@ module CouchRest
def initialize(array, casted_by, property) def initialize(array, casted_by, property)
self.property = property self.property = property
self.casted_by = casted_by self.casted_by = casted_by
array ||= [] (array ||= []).compact!
casted_by[property.to_s] = [] # replace the original array! casted_by[property.to_s] = [] # replace the original array!
array.each do |obj| array.compact.each do |obj|
casted_by[property.to_s] << obj.id casted_by[property.to_s] << obj.id
end end
super(array) super(array)

View file

@ -28,8 +28,18 @@ module CouchRest
def cast(parent, value) def cast(parent, value)
return value unless casted return value unless casted
if type.is_a?(Array) if type.is_a?(Array)
# Convert to array if it is not already if value.nil?
value = [value].compact unless value.is_a?(Array) value = []
elsif [Hash, HashWithIndifferentAccess].include?(value.class)
# Assume provided as a Hash where key is index!
data = value
value = [ ]
data.keys.sort.each do |k|
value << data[k]
end
elsif value.class != Array
raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}"
end
arr = value.collect { |data| cast_value(parent, data) } arr = value.collect { |data| cast_value(parent, data) }
# allow casted_by calls to be passed up chain by wrapping in CastedArray # allow casted_by calls to be passed up chain by wrapping in CastedArray
value = type_class != String ? ::CouchRest::CastedArray.new(arr, self) : arr value = type_class != String ? ::CouchRest::CastedArray.new(arr, self) : arr

View file

@ -146,6 +146,12 @@ describe "Assocations" do
@invoice.entries.should be_empty @invoice.entries.should be_empty
end end
it "should ignore nil entries" do
@invoice.entries = [ nil ]
@invoice.entry_ids.should be_empty
@invoice.entries.should be_empty
end
describe "proxy" do describe "proxy" do
it "should ensure new entries to proxy are matched" do it "should ensure new entries to proxy are matched" do

View file

@ -348,7 +348,7 @@ describe CouchRest::CastedModel do
describe "calling base_doc from a nested casted model" do describe "calling base_doc from a nested casted model" do
before :each do before :each do
@course = Course.new(:title => 'Science 101') @course = Course.new(:title => 'Science 101')
@professor = Person.new(:name => 'Professor Plum') @professor = Person.new(:name => ['Professor', 'Plum'])
@cat = Cat.new(:name => 'Scratchy') @cat = Cat.new(:name => 'Scratchy')
@toy1 = CatToy.new @toy1 = CatToy.new
@toy2 = CatToy.new @toy2 = CatToy.new

View file

@ -42,14 +42,15 @@ describe "ExtendedDocument properties" do
end end
it "should let you use an alias for a casted attribute" do it "should let you use an alias for a casted attribute" do
@card.cast_alias = Person.new(:name => "Aimonetti") @card.cast_alias = Person.new(:name => ["Aimonetti"])
@card.cast_alias.name.should == ["Aimonetti"] @card.cast_alias.name.should == ["Aimonetti"]
@card.calias.name.should == ["Aimonetti"] @card.calias.name.should == ["Aimonetti"]
card = Card.new(:first_name => "matt", :cast_alias => {:name => "Aimonetti"}) card = Card.new(:first_name => "matt", :cast_alias => {:name => ["Aimonetti"]})
card.cast_alias.name.should == ["Aimonetti"] card.cast_alias.name.should == ["Aimonetti"]
card.calias.name.should == ["Aimonetti"] card.calias.name.should == ["Aimonetti"]
end end
it "should be auto timestamped" do it "should be auto timestamped" do
@card.created_at.should be_nil @card.created_at.should be_nil
@card.updated_at.should be_nil @card.updated_at.should be_nil
@ -660,6 +661,57 @@ describe "ExtendedDocument properties" do
end end
end end
describe "properties of array of casted models" do
before(:each) do
@course = Course.new :title => 'Test Course'
end
it "should allow attribute to be set from an array of objects" do
@course.questions = [Question.new(:q => "works?"), Question.new(:q => "Meaning of Life?")]
@course.questions.length.should eql(2)
end
it "should allow attribute to be set from an array of hashes" do
@course.questions = [{:q => "works?"}, {:q => "Meaning of Life?"}]
@course.questions.length.should eql(2)
@course.questions.last.q.should eql("Meaning of Life?")
@course.questions.last.class.should eql(Question) # typecasting
end
it "should allow attribute to be set from hash with ordered keys and objects" do
@course.questions = { '0' => Question.new(:q => "Test1"), '1' => Question.new(:q => 'Test2') }
@course.questions.length.should eql(2)
@course.questions.last.q.should eql('Test2')
@course.questions.last.class.should eql(Question)
end
it "should allow attribute to be set from hash with ordered keys and sub-hashes" do
@course.questions = { '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
@course.questions.length.should eql(2)
@course.questions.last.q.should eql('Test2')
@course.questions.last.class.should eql(Question)
end
it "should allow attribute to be set from hash with ordered keys and HashWithIndifferentAccess" do
# This is similar to what you'd find in an HTML POST parameters
hash = HashWithIndifferentAccess.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
@course.questions = hash
@course.questions.length.should eql(2)
@course.questions.last.q.should eql('Test2')
@course.questions.last.class.should eql(Question)
end
it "should raise an error if attempting to set single value for array type" do
lambda {
@course.questions = Question.new(:q => 'test1')
}.should raise_error
end
end
describe "a casted model retrieved from the database" do describe "a casted model retrieved from the database" do
before(:each) do before(:each) do
reset_test_db! reset_test_db!

View file

@ -1,7 +1,7 @@
class Person < Hash class Person < Hash
include ::CouchRest::CastedModel include ::CouchRest::CastedModel
property :pet, :cast_as => 'Cat' property :pet, :cast_as => 'Cat'
property :name, :type => ['String'] property :name, [String]
def last_name def last_name
name.last name.last