Skip to content

Commit

Permalink
Implemented scoped find on many document associations.
Browse files Browse the repository at this point in the history
  • Loading branch information
jnunemaker committed Jul 27, 2009
1 parent 4ae6cda commit 58a8c08
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 18 deletions.
34 changes: 28 additions & 6 deletions lib/mongomapper/associations/has_many_proxy.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
module MongoMapper
module Associations
class HasManyProxy < ArrayProxy
def replace(v)
delegate :klass, :to => :@association

def find(*args)
options = args.extract_options!
klass.find(*args << scoped_options(options))
end

def all(options={})
find(:all, scoped_options(options))
end

def first(options={})
find(:first, scoped_options(options))
end

def last(options={})
find(:last, scoped_options(options))
end

def replace(docs)
if load_target
@target.map(&:destroy)
end

v.each do |o|
docs.each do |doc|
@owner.save if @owner.new?
o.__send__(:write_attribute, self.foreign_key, @owner.id)
o.save
o
doc.send(:write_attribute, self.foreign_key, @owner.id)
doc.save
end

reload_target
end

protected
def scoped_options(options)
options.dup.deep_merge({:conditions => {self.foreign_key => @owner.id}})
end

def find_target
@association.klass.find(:all, {:conditions => {self.foreign_key => @owner.id}})
find(:all)
end

def foreign_key
Expand Down
20 changes: 14 additions & 6 deletions lib/mongomapper/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def find(*args)
when :first then find_first(options)
when :last then find_last(options)
when :all then find_every(options)
else find_from_ids(args)
else find_from_ids(args, options)
end
end

Expand Down Expand Up @@ -166,25 +166,33 @@ def find_last(options)
find_every(options.merge(:limit => 1, :order => 'created_at desc')).first
end

def find_some(ids)
documents = find_every(:conditions => {'_id' => ids})
def find_some(ids, options={})
documents = find_every(options.deep_merge(:conditions => {'_id' => ids}))
if ids.size == documents.size
documents
else
raise DocumentNotFound, "Couldn't find all of the ids (#{ids.to_sentence}). Found #{documents.size}, but was expecting #{ids.size}"
end
end

def find_from_ids(*ids)
def find_one(id, options={})
if doc = find_every(options.deep_merge(:conditions => {:_id => id})).first
doc
else
raise DocumentNotFound, "Document with id of #{id} does not exist in collection named #{collection.name}"
end
end

def find_from_ids(ids, options={})
ids = ids.flatten.compact.uniq

case ids.size
when 0
raise(DocumentNotFound, "Couldn't find without an ID")
when 1
find_by_id(ids[0]) || raise(DocumentNotFound, "Document with id of #{ids[0]} does not exist in collection named #{collection.name}")
find_one(ids[0], options)
else
find_some(ids)
find_some(ids, options)
end
end

Expand Down
124 changes: 119 additions & 5 deletions test/functional/test_associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@

class AssociationsTest < Test::Unit::TestCase
def setup
Project.collection.clear
Status.collection.clear
RealPerson.collection.clear
Catalog.collection.clear
TrModels::Fleet.collection.clear
clear_all_collections
end

context "Modularized Polymorphic Many Embedded" do
Expand Down Expand Up @@ -219,6 +215,124 @@ def setup
from_db.statuses.size.should == 1
from_db.statuses[0].name.should == "ready"
end

context "Finding scoped to association" do
setup do
@project1 = Project.new(:name => 'Project 1')
@brand_new = Status.create(:name => 'New')
@complete = Status.create(:name => 'Complete')
@project1.statuses = [@brand_new, @complete]
@project1.save

@project2 = Project.create(:name => 'Project 2')
@in_progress = Status.create(:name => 'In Progress')
@archived = Status.create(:name => 'Archived')
@another_complete = Status.create(:name => 'Complete')
@project2.statuses = [@in_progress, @archived, @another_complete]
@project2.save
end

context "with :all" do
should "work" do
@project1.statuses.find(:all).should == [@brand_new, @complete]
end

should "work with conditions" do
statuses = @project1.statuses.find(:all, :conditions => {'name' => 'Complete'})
statuses.should == [@complete]
end

should "work with order" do
statuses = @project1.statuses.find(:all, :order => 'name asc')
statuses.should == [@complete, @brand_new]
end
end

context "with #all" do
should "work" do
@project1.statuses.all.should == [@brand_new, @complete]
end

should "work with conditions" do
statuses = @project1.statuses.all(:conditions => {'name' => 'Complete'})
statuses.should == [@complete]
end

should "work with order" do
statuses = @project1.statuses.all(:order => 'name asc')
statuses.should == [@complete, @brand_new]
end
end

context "with :first" do
should "work" do
@project1.statuses.find(:first).should == @brand_new
end

should "work with conditions" do
status = @project1.statuses.find(:first, :conditions => {:name => 'Complete'})
status.should == @complete
end
end

context "with #first" do
should "work" do
@project1.statuses.first.should == @brand_new
end

should "work with conditions" do
status = @project1.statuses.first(:conditions => {:name => 'Complete'})
status.should == @complete
end
end

context "with :last" do
should "work" do
@project1.statuses.find(:last).should == @complete
end

should "work with conditions" do
status = @project1.statuses.find(:last, :conditions => {:name => 'New'})
status.should == @brand_new
end
end

context "with #last" do
should "work" do
@project1.statuses.last.should == @complete
end

should "work with conditions" do
status = @project1.statuses.last(:conditions => {:name => 'New'})
status.should == @brand_new
end
end

context "with one id" do
should "work for id in association" do
@project1.statuses.find(@complete.id).should == @complete
end

should "not work for id not in association" do
lambda {
@project1.statuses.find(@archived.id)
}.should raise_error(MongoMapper::DocumentNotFound)
end
end

context "with multiple ids" do
should "work for ids in association" do
statuses = @project1.statuses.find(@brand_new.id, @complete.id)
statuses.should == [@brand_new, @complete]
end

should "not work for ids not in association" do
lambda {
@project1.statuses.find(@brand_new.id, @complete.id, @archived.id)
}.should raise_error(MongoMapper::DocumentNotFound)
end
end
end
end

context "Many embedded documents" do
Expand Down
2 changes: 1 addition & 1 deletion test/functional/test_callbacks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def clear_history
end
end

@document.collection.clear
clear_all_collections
end

should "get the order right for creating documents" do
Expand Down
4 changes: 4 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
require dir + 'mongomapper'

class Test::Unit::TestCase
def clear_all_collections
MongoMapper::Document.descendants.map(&:delete_all)
end

custom_matcher :be_nil do |receiver, matcher, args|
matcher.positive_failure_message = "Expected #{receiver} to be nil but it wasn't"
matcher.negative_failure_message = "Expected #{receiver} not to be nil but it was"
Expand Down

0 comments on commit 58a8c08

Please sign in to comment.