Skip to content

Commit

Permalink
Allow destructive replacement on has_one relations when using create_…
Browse files Browse the repository at this point in the history
… methods. Fixes #1442.
  • Loading branch information
durran committed Nov 24, 2011
1 parent 4f62b9e commit 73020aa
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 17 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,9 @@ For instructions on upgrading to newer versions, visit
* \#1445 Prevent duplicate documents in the loaded array on the target
enumerable for relational associations.

* \#1442 When using create_ methods for has one relations, the appropriate
destructive methods now get called when replacing an existing document.

* \#1431 Enumerable context should add to the loaded array post yield, so
that methods like #any? that short circuit based on the value of the block
dont falsely have extra documents.
Expand Down
26 changes: 22 additions & 4 deletions lib/mongoid/relations/builders.rb
Expand Up @@ -32,6 +32,23 @@ module Relations #:nodoc:
module Builders
extend ActiveSupport::Concern

private

# Parse out the attributes and the options from the args passed to a
# build_ or create_ methods.
#
# @example Parse the args.
# doc.parse_args(:name => "Joe")
#
# @param [ Array ] args The arguments.
#
# @return [ Array<Hash> ] The attributes and options.
#
# @since 2.3.4
def parse_args(*args)
[ args.first || {}, args.size > 1 ? args[1] : {} ]
end

module ClassMethods #:nodoc:

# Defines a builder method for an embeds_one relation. This is
Expand All @@ -48,8 +65,7 @@ module ClassMethods #:nodoc:
def builder(name, metadata)
tap do
define_method("build_#{name}") do |*args|
attributes = args.first || {}
options = args.size > 1 ? args[1] : {}
attributes, options = parse_args(*args)
document = Factory.build(metadata.klass, attributes, options)
_building do
send("#{name}=", document)
Expand All @@ -70,10 +86,12 @@ def builder(name, metadata)
# @return [ Class ] The class being set up.
#
# @since 2.0.0.rc.1
def creator(name)
def creator(name, metadata)
tap do
define_method("create_#{name}") do |*args|
send("build_#{name}", *args).tap { |doc| doc.save }
attributes, options = parse_args(*args)
document = Factory.build(metadata.klass, attributes, options)
send("#{name}=", document).tap { |doc| doc.save }
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/mongoid/relations/macros.rb
Expand Up @@ -103,7 +103,7 @@ def embeds_many(name, options = {}, &block)
def embeds_one(name, options = {}, &block)
characterize(name, Embedded::One, options, &block).tap do |meta|
relate(name, meta)
builder(name, meta).creator(name)
builder(name, meta).creator(name, meta)
validates_relation(meta)
end
end
Expand Down Expand Up @@ -219,7 +219,7 @@ def has_one(name, options = {}, &block)
characterize(name, Referenced::One, options, &block).tap do |meta|
relate(name, meta)
reference(meta)
builder(name, meta).creator(name).autosave(meta)
builder(name, meta).creator(name, meta).autosave(meta)
validates_relation(meta)
end
end
Expand Down
12 changes: 8 additions & 4 deletions spec/functional/mongoid/callbacks_spec.rb
Expand Up @@ -186,12 +186,12 @@
Band.create(:name => "Moderat")
end

let!(:label) do
band.create_label(:name => "Mute")
end

context "when the child is dirty" do

let!(:label) do
band.create_label(:name => "Mute")
end

before do
label.name = "Nothing"
band.save
Expand All @@ -204,6 +204,10 @@

context "when the child is not dirty" do

let!(:label) do
band.build_label(:name => "Mute")
end

before do
band.save
end
Expand Down
8 changes: 4 additions & 4 deletions spec/functional/mongoid/criterion/inclusion_spec.rb
Expand Up @@ -409,8 +409,8 @@
criteria.should eq([ person ])
end

it "inserts the first document into the identity map" do
Mongoid::IdentityMap[Game][game_one.id].should eq(game_one)
it "deletes the replaced document from the identity map" do
Mongoid::IdentityMap[Game][game_one.id].should be_nil
end

it "inserts the second document into the identity map" do
Expand Down Expand Up @@ -491,8 +491,8 @@
Mongoid::IdentityMap[Post][post_two.id].should eq(post_two)
end

it "inserts the first has one document into the identity map" do
Mongoid::IdentityMap[Game][game_one.id].should eq(game_one)
it "removes the first has one document from the identity map" do
Mongoid::IdentityMap[Game][game_one.id].should be_nil
end

it "inserts the second has one document into the identity map" do
Expand Down
39 changes: 38 additions & 1 deletion spec/functional/mongoid/relations/builders_spec.rb
Expand Up @@ -2,6 +2,10 @@

describe Mongoid::Relations::Builders do

before do
[ Person, Game ].each(&:delete_all)
end

describe "#build_#\{name}" do

let(:person) do
Expand Down Expand Up @@ -64,9 +68,43 @@
end
end
end

context "when the parent is persisted" do

let(:person) do
Person.create(:ssn => "123-45-1234")
end

context "when the relation is a has one" do

let!(:game_one) do
person.create_game(:name => "Starcraft")
end

context "when a document already exists" do

let!(:game_two) do
person.create_game(:name => "Skyrim")
end

it "replaces the existing document" do
person.game.should eq(game_two)
end

it "persists the change" do
person.game(true).should eq(game_two)
end

it "removes the old document from the database" do
Game.collection.count.should eq(1)
end
end
end
end
end

describe "#build" do

context "with criteria applied" do

let(:person) { Person.new }
Expand All @@ -76,7 +114,6 @@

it { should be_a_kind_of(Service) }
it { should_not be_persisted }

end
end
end
12 changes: 10 additions & 2 deletions spec/unit/mongoid/relations/builders_spec.rb
Expand Up @@ -66,21 +66,29 @@ class TestClass

describe ".creator" do

let(:metadata) do
Mongoid::Relations::Metadata.new(
:name => :name,
:relation => relation,
:inverse_class_name => "Person"
)
end

let(:document) do
klass.new
end

before do
document.instance_variable_set(:@attributes, {})
klass.creator("name")
klass.creator("name", metadata)
end

it "defines a create_* method" do
document.should respond_to(:create_name)
end

it "returns self" do
klass.creator("name").should == klass
klass.creator("name", metadata).should == klass
end

context "defined methods" do
Expand Down

0 comments on commit 73020aa

Please sign in to comment.