I'm having trouble updating an embedded document in a Ruby / Sinatra app. I'm trying to use the positional operator "$" in an update statement in order to select the correct document from the embedded array. But this is throwing an "ArgumentError - wrong number of arguments (2 for 1)" error.
A simple update statement with a hard-coded array index works fine. So maybe Mongoid/Moped doesn't support the position operator?... Although from what I could see, it looks like it should.
Does anyone know what the best approach is? Is there some other way to determine sub-document index without iterating through them all using Ruby in my controller - which is plan B, but seems really flaky!...
Here is my basic set up: I have "Customers"...
class Customer
include Mongoid::Document
field :customer_name, type: String
embeds_many :contacts
attr_accessible :customer_name
end
... with embedded "Contacts"...
class Contact
include Mongoid::Document
field :first_name, type: String
attr_accessible :first_name
embedded_in :customer
end
And in my controller I get the ._ids of both customer (pk), and the specific embedded document to update (contact_pk):
Customer.update(
{
"_id" => Moped::BSON::ObjectId(pk),"contacts._id" => Moped::BSON::ObjectId(contact_pk)
},
{
$set => {"contacts.$.first_name" => "Bob" }
}
)
The update
class method is actually Customer.with_default_scope.update
in disguise, that means that update
is actually the update
method from Mongoid::Criteria
and that looks like this:
# Update the first matching document atomically.
#
# @example Update the first matching document.
# context.update({ "$set" => { name: "Smiths" }})
#
# @param [ Hash ] attributes The new attributes for the document.
#
# @return [ nil, false ] False if no attributes were provided.
#
# @since 3.0.0
def update(attributes = nil)
update_documents(attributes)
end
Notice that it only takes a single attributes
parameter? That explains the error message. Presumably you're expecting update
to work the same way as it does in the MongoDB shell or the JavaScript interface.
First you need to find the document of interest and then call update
on that:
Customer.where('_id' => Moped::BSON::ObjectId(pk), 'contacts._id' => Moped::BSON::ObjectId(contact_pk))
.update($set => { 'contacts.$.first_name' => 'Bob' })