diff --git a/lib/mongoid/associations.rb b/lib/mongoid/associations.rb index 2278ddd79d..80baa88ccc 100644 --- a/lib/mongoid/associations.rb +++ b/lib/mongoid/associations.rb @@ -227,7 +227,7 @@ def add_association(type, options) def add_builder(type, options) name = options.name.to_s define_method("build_#{name}") do |attrs| - reset(name) { type.new(self, attrs, options) } + reset(name) { type.new(self, attrs.stringify_keys, options) } end end diff --git a/lib/mongoid/associations/belongs_to.rb b/lib/mongoid/associations/belongs_to.rb index ce273bba30..f5fbdbdc45 100644 --- a/lib/mongoid/associations/belongs_to.rb +++ b/lib/mongoid/associations/belongs_to.rb @@ -5,7 +5,7 @@ class BelongsTo #:nodoc: include Proxy # Creates the new association by setting the internal - # document as the passed in Document. This should be the + # target as the passed in Document. This should be the # parent. # # All method calls on this object will then be delegated @@ -13,7 +13,7 @@ class BelongsTo #:nodoc: # # Options: # - # document: The parent +Document+ + # target: The parent +Document+ # options: The association options def initialize(target, options) @target, @options = target, options @@ -26,11 +26,6 @@ def find(id) @target end - # Delegate all missing methods over to the parent +Document+. - def method_missing(name, *args, &block) - @target.send(name, *args, &block) - end - class << self # Creates the new association by setting the internal # document as the passed in Document. This should be the @@ -53,6 +48,10 @@ def macro # Perform an update of the relationship of the parent and child. This # is initialized by setting a parent object as the association on the # +Document+. Will properly set a has_one or a has_many. + # + # Returns: + # + # A new +BelongsTo+ association proxy. def update(target, child, options) child.parentize(target, options.inverse_of) child.notify diff --git a/lib/mongoid/associations/has_one.rb b/lib/mongoid/associations/has_one.rb index 2e5de32f96..86834d9829 100644 --- a/lib/mongoid/associations/has_one.rb +++ b/lib/mongoid/associations/has_one.rb @@ -4,7 +4,10 @@ module Associations #:nodoc: class HasOne #:nodoc: include Proxy - attr_reader :association_name, :document, :parent, :options + # Build a new object for the association. + def build(attrs = {}, type = nil) + @target = attrs.assimilate(@parent, @options, type); self + end # Creates the new association by finding the attributes in # the parent document with its name, and instantiating a @@ -16,43 +19,31 @@ class HasOne #:nodoc: # Options: # # document: The parent +Document+ - # attributes: The attributes of the decorated object. + # attributes: The attributes of the target object. # options: The association options. - def initialize(document, attributes, options) - @parent, @options, @association_name = document, options, options.name - attrs = attributes.stringify_keys - klass = attrs["_type"] ? attrs["_type"].constantize : nil - @document = attrs.assimilate(@parent, @options, klass) - end - - # Delegate all missing methods over to the +Document+. - def method_missing(name, *args, &block) - @document.send(name, *args, &block) + # + # Returns: + # + # A new +HashOne+ association proxy. + def initialize(document, attrs, options) + @parent, @options = document, options + @target = attrs.assimilate(@parent, @options, attrs.klass) end # Used for setting the association via a nested attributes setter on the - # parent +Document+. + # parent +Document+. Called when using accepts_nested_attributes_for. + # + # Options: + # + # attributes: The attributes for the new association + # + # Returns: + # + # A new target document. def nested_build(attributes) build(attributes) end - # This will get deprecated - def to_a - [@document] - end - - # Need to override here for when the underlying document is nil. - def valid? - @document.valid? - end - - protected - # Build a new object for the association. - def build(attrs = {}, type = nil) - @document = attrs.assimilate(@parent, @options, type) - self - end - class << self # Preferred method of instantiating a new +HasOne+, since nil values # will be handled properly. @@ -84,8 +75,13 @@ def macro # Example: # # HasOne.update({:first_name => "Hank"}, person, options) + # + # Returns: + # + # A new +HasOne+ association proxy. def update(child, parent, options) child.assimilate(parent, options) + instantiate(parent, options) end end diff --git a/lib/mongoid/associations/proxy.rb b/lib/mongoid/associations/proxy.rb index ffddd92d64..f93f4f9916 100644 --- a/lib/mongoid/associations/proxy.rb +++ b/lib/mongoid/associations/proxy.rb @@ -14,6 +14,12 @@ module InstanceMethods #:nodoc: attr_reader \ :options, :target + + # Default behavior of method missing should be to delegate all calls + # to the target of the proxy. This can be overridden in special cases. + def method_missing(name, *args, &block) + @target.send(name, *args, &block) + end end end end diff --git a/lib/mongoid/extensions/hash/accessors.rb b/lib/mongoid/extensions/hash/accessors.rb index 92cfd4ee09..fdf61e7beb 100644 --- a/lib/mongoid/extensions/hash/accessors.rb +++ b/lib/mongoid/extensions/hash/accessors.rb @@ -26,6 +26,12 @@ def insert(key, attrs) self[key] = key.singular? ? attrs : [attrs] end end + + # If a _type key exists in the hash, return the class for the value. + def klass + class_name = self["_type"] + class_name ? class_name.constantize : nil + end end end end diff --git a/spec/unit/mongoid/associations/has_one_spec.rb b/spec/unit/mongoid/associations/has_one_spec.rb index 5674e301e6..d087e6ba62 100644 --- a/spec/unit/mongoid/associations/has_one_spec.rb +++ b/spec/unit/mongoid/associations/has_one_spec.rb @@ -178,7 +178,7 @@ before do @name = Name.new(:first_name => "Donald") @person = Person.new(:title => "Sir") - Mongoid::Associations::HasOne.update( + @association = Mongoid::Associations::HasOne.update( @name, @person, Mongoid::Associations::Options.new(:name => :name) @@ -194,6 +194,10 @@ { "_id" => "donald", "first_name" => "Donald", "_type" => "Name" } end + it "returns the proxy" do + @association.target.should == @name + end + end context "when setting the object to nil" do @@ -216,6 +220,22 @@ end + describe "#to_a" do + + before do + @association = Mongoid::Associations::HasOne.new( + @document, + @attributes["mixed_drink"], + Mongoid::Associations::Options.new(:name => :mixed_drink) + ) + end + + it "returns the target in a new array" do + @association.to_a.first.should be_a_kind_of(MixedDrink) + end + + end + describe "#valid?" do context "when the document is not nil" do diff --git a/spec/unit/mongoid/extensions/hash/accessors_spec.rb b/spec/unit/mongoid/extensions/hash/accessors_spec.rb index 305372ed12..dc4e4fa6d5 100644 --- a/spec/unit/mongoid/extensions/hash/accessors_spec.rb +++ b/spec/unit/mongoid/extensions/hash/accessors_spec.rb @@ -21,6 +21,26 @@ } end + describe "#klass" do + + context "when _type exists" do + + it "returns the class" do + { "_type" => "Person" }.klass.should == Person + end + + end + + context "when _type does not exist" do + + it "returns nil" do + {}.klass.should be_nil + end + + end + + end + describe "#remove" do context "when removing from an array" do