Skip to content

Commit

Permalink
added support for arrays of embedded documents
Browse files Browse the repository at this point in the history
  • Loading branch information
Karl Seguin committed Jul 31, 2011
1 parent 15ec243 commit dde4976
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 4 deletions.
13 changes: 10 additions & 3 deletions lib/mongo_light/embedded_document.rb
Expand Up @@ -11,7 +11,7 @@ def mongo_accessor(map)
define_method(k) { @attributes[k] }
define_method("#{k}=") {|value| @attributes[k] = value }
if v.is_a?(Hash)
@unmap[v[:field].to_s] = {:prop => k, :class => v[:class]}
@unmap[v[:field].to_s] = {:prop => k, :class => v[:class], :array => v[:array]}
else
@unmap[v.to_s] = k
end
Expand All @@ -21,14 +21,17 @@ def map(raw)
return {} if raw.nil? || !raw.is_a?(Hash)
hash = {}
raw.each do |key, value|
sym = key.to_sym
if value.is_a?(EmbeddedDocument)
v = value.class.map(value.attributes)
elsif value.is_a?(Hash)
v = map(value)
elsif value.is_a?(Array) && @map[sym].is_a?(Hash) && @map[sym][:array]
v = value.map{|vv| vv.class.map(vv.attributes)}
else
v = value
end
hash[map_key(key.to_sym)] = v
hash[map_key(sym)] = v
end
return hash
end
Expand All @@ -44,7 +47,11 @@ def unmap(data, raw = false)
if @unmap[key].is_a?(Hash)
real_key = @unmap[key][:prop]
c = @unmap[key][:class]
v = raw ? c.unmap(value) : c.new(c.unmap(value))
if @unmap[key][:array]
v = value.map{|vv| raw ? c.unmap(vv) : c.new(c.unmap(vv))}
else
v = raw ? c.unmap(value) : c.new(c.unmap(value))
end
else
real_key = key == '_id' ? :_id : @unmap[key]
v = value
Expand Down
7 changes: 6 additions & 1 deletion readme.markdown
Expand Up @@ -54,11 +54,16 @@ Next, use the following fancy syntax for your root document:

class User
include MongoLight::Document
mongo_accessor({:name => :name, password => :pw, :email => :e, :comments => {:field => :c, :class => Comment}})
mongo_accessor({:name => :name, password => :pw, :email => :e, :comment => {:field => :c, :class => Comment}})
end

Again, this'll make `comments` accessible on your objects, but store it within the `c` field.

If you want an array of embedded documents, simply specify `:array => true`:

...
:comments => {:field => :c, :class => Comment, array => true}})

Please note that aliasing completely fails when querying embedded documents - you need to use the shortened name:

User.find({:name => 'blah', 'c.t' => 'blah2}) #yes, it sucks
Expand Down
25 changes: 25 additions & 0 deletions spec/embedded_array_spec.rb
@@ -0,0 +1,25 @@
require 'spec_helper'

describe 'embedded array behavior' do
it "saves a single embedded document" do
child = Child.new({:name => 'child', :power => 88})
parent = BusyParent.new({:name => 'my name', :children => [child]})
parent.save
found = BusyParent.find_one({:name => 'my name'})
found.children.length.should == 1
found.children[0].name.should == 'child'
found.children[0].power.should == 88
end
it "saves a muliple embedded document" do
child1 = Child.new({:name => 'child1', :power => 88})
child2 = Child.new({:name => 'child2', :power => 89})
parent = BusyParent.new({:name => 'my name', :children => [child1, child2]})
parent.save
found = BusyParent.find_one({:name => 'my name'})
found.children.length.should == 2
found.children[0].name.should == 'child1'
found.children[0].power.should == 88
found.children[1].name.should == 'child2'
found.children[1].power.should == 89
end
end
25 changes: 25 additions & 0 deletions spec/embedded_spec.rb
@@ -0,0 +1,25 @@
require 'spec_helper'

describe 'embedded behavior' do
it "saves the embedded document" do
child = Child.new({:name => 'child', :power => 88})
parent = Parent.new({:name => 'my name', :child => child})
parent.save
found = Parent.find_one({:name => 'my name'})
found.child.name.should == 'child'
found.child.power.should == 88
end
it "returns the raw document with embedded objects" do
child = Child.new({:name => 'child', :power => 88})
parent = Parent.new({:name => 'my name', :child => child})
parent.save
found = Parent.find_one({:name => 'my name'}, {:raw => true})
found.should == {:_id => parent.id, :name => 'my name', :child => {:name => 'child', :power => 88}}
end
it "finds a document by it's child" do
child = Child.new({:name => 'child', :power => 88})
parent = Parent.new({:name => 'my name', :child => child})
parent.save
Parent.count({'c.p' => 88}).should == 1
end
end
25 changes: 25 additions & 0 deletions spec/remove_spec.rb
@@ -0,0 +1,25 @@
require 'spec_helper'

describe 'remove behavior' do
it "removes the matching documents" do
Factory.create(:simple, {:name => 'paul', :power => 49})
Factory.create(:simple, {:name => 'jessica', :power => 123})
Simple.remove({:power => 49})
Simple.count.should == 1
Simple.count({:name => 'jessica'}).should == 1
end

it "removes all documents" do
Factory.create(:simple, {:name => 'paul', :power => 49})
Factory.create(:simple, {:name => 'jessica', :power => 123})
Simple.remove
Simple.count.should == 0
end

it "removes no document" do
Factory.create(:simple, {:name => 'paul', :power => 49})
Factory.create(:simple, {:name => 'jessica', :power => 123})
Simple.remove({:over9000 => true})
Simple.count.should == 2
end
end
7 changes: 7 additions & 0 deletions spec/support/busy_parent.rb
@@ -0,0 +1,7 @@
require 'child'
class BusyParent
include MongoLight::Document
mongo_accessor({
:name => :name,
:children => {:field => :c, :class => Child, :array => true}})
end
6 changes: 6 additions & 0 deletions spec/support/child.rb
@@ -0,0 +1,6 @@
class Child
include MongoLight::EmbeddedDocument
mongo_accessor({:name => :n, :power => :p})
end


7 changes: 7 additions & 0 deletions spec/support/parent.rb
@@ -0,0 +1,7 @@
require 'child'
class Parent
include MongoLight::Document
mongo_accessor({
:name => :name,
:child => {:field => :c, :class => Child}})
end
27 changes: 27 additions & 0 deletions spec/update_spec.rb
@@ -0,0 +1,27 @@
require 'spec_helper'

describe 'update behavior' do
it "updates the matching documents" do
Factory.create(:simple, {:name => 'paul', :power => 2})
Factory.create(:simple, {:name => 'jessica', :power => 2})
Simple.update({:power => 2}, {'$set' => {:power => 4}}, {:multi => true})
Simple.count.should == 2
Simple.count({:power => 4}).should == 2
end

it "updates a single document" do
Factory.create(:simple, {:name => 'paul', :power => 2})
Factory.create(:simple, {:name => 'jessica', :power => 2})
Simple.update({:power => 2}, {:power => 4})
Simple.count.should == 2
Simple.count({:power => 4}).should == 1
end

it "updates no document" do
Factory.create(:simple, {:name => 'paul', :power => 49})
Factory.create(:simple, {:name => 'jessica', :power => 123})
Simple.remove({:over9000 => true, :power => 442})
Simple.count.should == 2
Simple.count({:power => 442}).should == 0
end
end

0 comments on commit dde4976

Please sign in to comment.