Skip to content

Commit

Permalink
MONGOID-5331 Document hash assignment to associations (#5494)
Browse files Browse the repository at this point in the history
* MONGOID-5331 change buildable docs

* MONGOID-5331 add proxy tests and fix embedded_in proxy

* MONGOID-5331 update models

* Update spec/mongoid/association/embedded/embedded_in/proxy_spec.rb

* MONGOID-5331 update doc strings in batchable

* MONGOID-5331 add docs

* MONGOID-5331 add docs/release notes

* Apply suggestions from code review

* Update spec/mongoid/association/embedded/embedded_in/proxy_spec.rb
  • Loading branch information
Neilshweky authored Oct 7, 2022
1 parent 0ffd05d commit 4d7dcfc
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 12 deletions.
32 changes: 32 additions & 0 deletions docs/reference/associations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,38 @@ documents that exist in the application:
band.tours.destroy_all


.. _hash-assignment:

Hash Assignment
---------------

Embedded associations allow the user to assign a ``Hash`` instead of a document
to an association. On assignment, this hash is coerced into a document of the
class of the association that it's being assigned to. Take the following
example:

.. code:: ruby

class Band
include Mongoid::Document
embeds_many :albums
end

class Album
include Mongoid::Document
field :name, type: String
embedded_in :band
end

band = Band.create!
band.albums = [ { name: "Narrow Stairs" }, { name: "Transatlanticism" } ]
p band.albums
# => [ #<Album _id: 633c71e93282a4357bb608e5, name: "Narrow Stairs">, #<Album _id: 633c71e93282a4357bb608e6, name: "Transatlanticism"> ]

This works for ``embeds_one``, ``embeds_many``, and ``embedded_in`` associations.
Note that you cannot assign hashes to referenced associations.


Common Behavior
===============

Expand Down
31 changes: 31 additions & 0 deletions docs/release-notes/mongoid-8.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,34 @@ This time, the value for the ``:age`` field is maintained.
The default for the ``:replace`` option will be changed to ``false`` in
Mongoid 9.0, therefore it is recommended to explicitly specify this option
while using ``#upsert`` in 8.1 for easier upgradability.


Allow Hash Assignment to ``embedded_in``
----------------------------------------

Mongoid 8.1 allows the assignment of a hash to an ``embedded_in`` association.
On assignment, the hash will be coerced into a document of the class of the
association that it is being assigned to. This functionality already exists
for ``embeds_one`` and ``embeds_many`` associations. Consider the following
example:

.. code:: ruby

class Band
include Mongoid::Document
field :name, type: String
embeds_many :albums
end

class Album
include Mongoid::Document
embedded_in :band
end

album = Album.new
album.band = { name: "Death Cab For Cutie" }
p album.band
# => <Band _id: 633c74113282a438a15d2b56, name: "Death Cab For Cutie">

See the section on :ref:`Hash Assignment on Embedded Associations <hash-assignment>`
for more details
4 changes: 2 additions & 2 deletions lib/mongoid/association/embedded/batchable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def batch_remove(docs, method = :delete)
# @example Batch replace the documents.
# batchable.batch_replace([ doc_one, doc_two ])
#
# @param [ Array<Document> ] docs The docs to replace with.
# @param [ Array<Document> | Array<Hash> ] docs The docs to replace with.
#
# @return [ Array<Hash> ] The inserts.
def batch_replace(docs)
Expand Down Expand Up @@ -235,7 +235,7 @@ def inserts_valid=(value)
# @example Normalize the docs.
# batchable.normalize_docs(docs)
#
# @param [ Array<Hash | Document> ] docs The docs to normalize.
# @param [ Array<Document> | Array<Hash> ] docs The docs to normalize.
#
# @return [ Array<Document> ] The docs.
def normalize_docs(docs)
Expand Down
4 changes: 2 additions & 2 deletions lib/mongoid/association/embedded/embedded_in/buildable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ module Buildable
# @example Build the document.
# Builder.new(meta, attrs).build
#
# @param [ Object ] base The object.
# @param [ Object ] object The parent hash or document.
# @param [ Document ] base The object.
# @param [ Document | Hash ] object The parent hash or document.
# @param [ String ] type Not used in this context.
# @param [ Hash ] selected_fields Fields which were retrieved via
# #only. If selected_fields are specified, fields not listed in it
Expand Down
3 changes: 2 additions & 1 deletion lib/mongoid/association/embedded/embedded_in/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(base, target, association)
# @example Substitute the new document.
# person.name.substitute(new_name)
#
# @param [ Document ] replacement A document to replace the target.
# @param [ Document | Hash ] replacement A document to replace the target.
#
# @return [ Document | nil ] The association or nil.
def substitute(replacement)
Expand All @@ -40,6 +40,7 @@ def substitute(replacement)
return nil
end
_base.new_record = true
replacement = Factory.build(klass, replacement) if replacement.is_a?(::Hash)
self._target = replacement
bind_one
self
Expand Down
5 changes: 3 additions & 2 deletions lib/mongoid/association/embedded/embeds_many/buildable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ module Buildable
# @example Build the documents.
# Builder.new(meta, attrs).build
#
# @param [ Object ] base The base object.
# @param [ Object ] object The object to use to build the association.
# @param [ Document ] base The base object.
# @param [ Array<Document> | Array<Hash> ] object The object to use
# to build the association.
# @param [ String ] type Not used in this context.
# @param [ Hash ] selected_fields Fields which were retrieved via
# #only. If selected_fields are specified, fields not listed in it
Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/association/embedded/embeds_many/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ def shift(count = nil)
# @example Substitute the association's target.
# person.addresses.substitute([ address ])
#
# @param [ Array<Document> ] docs The replacement docs.
# @param [ Array<Document> | Array<Hash> ] docs The replacement docs.
#
# @return [ Many ] The proxied association.
def substitute(docs)
Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/association/embedded/embeds_one/buildable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module Buildable
# Builder.new(meta, attrs).build
#
# @param [ Document ] base The document this association hangs off of.
# @param [ Document ] object The related document.
# @param [ Document | Hash ] object The related document.
# @param [ String ] _type Not used in this context.
# @param [ Hash ] selected_fields Fields which were retrieved via
# #only. If selected_fields are specified, fields not listed in it
Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/association/embedded/embeds_one/proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def initialize(base, target, association)
# @example Substitute the new document.
# person.name.substitute(new_name)
#
# @param [ Document ] replacement A document to replace the target.
# @param [ Document | Hash ] replacement A document to replace the target.
#
# @return [ Document | nil ] The association or nil.
def substitute(replacement)
Expand Down
27 changes: 27 additions & 0 deletions spec/mongoid/association/embedded/embedded_in/proxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -616,4 +616,31 @@
end
end
end

context "when assigning a hash" do
let(:building_address) { BuildingAddress.new }

before do
building_address.building = { name: "Chrysler" }
end

it "creates the objects correctly" do
expect(building_address.building).to be_a(Building)
expect(building_address.building.name).to eq("Chrysler")
end
end

context "when replacing an association with a hash" do
let(:building_address) { BuildingAddress.new }

before do
building_address.building = { name: "Chrysler" }
building_address.building = { name: "Empire State" }
end

it "creates the objects correctly" do
expect(building_address.building).to be_a(Building)
expect(building_address.building.name).to eq("Empire State")
end
end
end
15 changes: 15 additions & 0 deletions spec/mongoid/association/embedded/embeds_many/proxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4840,4 +4840,19 @@ class DNS::Record
expect(from_db.company_tags.size).to eq(2)
end
end

context "when assigning hashes" do
let(:user) { EmmUser.create! }

before do
user.orders = [ { sku: 1 }, { sku: 2 } ]
end

it "creates the objects correctly" do
expect(user.orders.first).to be_a(EmmOrder)
expect(user.orders.last).to be_a(EmmOrder)

expect(user.orders.map(&:sku).sort).to eq([ 1, 2 ])
end
end
end
15 changes: 14 additions & 1 deletion spec/mongoid/association/embedded/embeds_one/proxy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class << person
end
end
end

context 'when the original document does not need to be unset because it will be replaced by the $set' do

let!(:pet_owner) do
Expand Down Expand Up @@ -1022,4 +1022,17 @@ class << person
expect(building.building_address).to be_a(BuildingAddress)
end
end

context "when assigning a hash" do
let(:building) { Building.create! }

before do
building.building_address = { city: "NYC" }
end

it "creates the objects correctly" do
expect(building.building_address).to be_a(BuildingAddress)
expect(building.building_address.city).to eq("NYC")
end
end
end
1 change: 0 additions & 1 deletion spec/support/models/artist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,4 @@ def before_remove_album(album)
def after_remove_album(album)
@after_remove_referenced_called = true
end

end
2 changes: 2 additions & 0 deletions spec/support/models/building.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
class Building
include Mongoid::Document

field :name, type: String

embeds_one :building_address, validate: false
embeds_many :contractors
end

0 comments on commit 4d7dcfc

Please sign in to comment.