How to do atomic update in Mongo/Mongomatic? - ruby

I'm having an awfully difficult time figuring out how to update a MongoDB document, using the atomic '$set' operator with Mongomatic. I'm pretty sure it's Mongo's criteria/update language I'm having troubles with, not Mongomatic, but I'm willing to be proven wrong.
The link to a gist with a standalone, runnable script is here: https://gist.github.com/3835672
I'm starting out by creating a document that looks like this:
{"videos":[{"video_id":"video1"},{"video_id":"video2"}],"_id":{"$oid": "506ddd53a114604ce3000001"}}
I can get that document using a model instantiated using Mongomatic:
video_group = VideoGroup.find_one('videos.video_id' => 'video1')
Then I'm trying to set a 'views' field, by doing this:
video_group.update!({ 'videos.video_id' => 'video1' }, '$set' => { 'videos.$.views' => 123 })
That's where Mongo blows up, with the following error:
can't append to array using string field name [$]
I know this is a very common question on StackOverflow. I understand generally that the problem is that the positional operator isn't getting any matches. But even reading through dozens of responses, I still can't figure out how to express this statement in a way that works.
Am I just starting out with the wrong data structure?

It is, in fact, a mongomatic problem. You need to pass the underlying mongo ruby driver the option {:multi => true}, as well as including your criteria with the specific _id for the update sent to mongodb instead of as part of the optional parameters. Looks like a bug in mongomatic. Here is the ruby debugger transcript that I used to find it: https://gist.github.com/3836797
Note that I made a change to the file you posted, adding the line debugger before line #41, and changing line #42-44 to this:
video_group.update!({ 'videos.video_id' => 'video1', :multi => true }, '$set' => {
'videos.$.views' => 123,
})

Related

What options does the asana api expect in tasks.find_by_id

I'm using the asana gem to access the asana api.
The client documentation for the class method find_by_id exposed on the tasks resource (i.e. Asana::Task) says that it will take a hash of options. As far as I can tell looking at the little code snippet, it should be the same options as are listed on https://asana.com/developers/documentation/getting-started/input-output-options#paths
However, when I do client.tasks.find_by_id(123456, :fields => "this.assignee.email"), for example, I get an ArgumentError: unknown keyword: fields.
What am I doing wrong? How should this work?
Also: it's unclear to me from the above page when I should be using this in my field specifications and when it is unnecessary.
EDIT: SOLVED!
The correct syntax is client.tasks.find_by_id(123456, :options => { :fields => "this.assignee.email" })
Both :fields and "fields" work.
Judging from the code in the ruby client library: https://github.com/Asana/ruby-asana/blob/423f76c14792bd4712c099161a14a10ce941b2d9/lib/asana/http_client.rb#L42
Perhaps something like client.tasks.find_by_id(123456, {"fields" => "this.assignee.email"}) might work. Could you try that?

How to do an upsert / push with mongoid / moped

I'm using Mongoid (v3) to access MongoDB, and want to perform this action:
db.sessionlogs.update(
{sessionid: '12345'}, /* selection criteria */
{'$push':{rows: "new set of data"}}, /* modification */
true /* upsert */
);
This works fine in the mongo shell. It's also exactly what I want since it's a single atomic operation which is important to me as I'm going to be calling it a lot. I don't want to have to do two operations -- a fetch and then an update. I've tried a bunch of things through mongoid, but can't get it to work.
How can I get MongoID out of the way and just send this command to MongoDB? I'm guessing there's some way to do this at the Moped level, but the documentation of that library is basically non-existent.
[Answer found while writing the question...]
criteria = Sessionlogs.collection.find(:sessionid => sessionid)
criteria.upsert("$push" => {"rows" => datarow})
Here is one way to do it:
session_log = SessionLog.new(session_id: '12345')
session_log.upsert
session_log.push(:rows, "new set of data")
Or another:
SessionLog.find_or_create_by(session_id: '12345').
push(:rows, "new set of data")
#push performs an atomic $push on the field. It is explained on the
Atomic Persistence page.
(Note: the examples use UpperCamelCase and snake_case as is Ruby convention.)
Don't go down to moped just yet, you can use find and modify operation to achieve the same thing (with all the default scope and inheritance goodies)
Sample to save an edge in a graph if not existed
edge = {source_id: session[:user_id],dest_id:product._id, name: edge_name}
ProductEdge.where(edge).find_and_modify(ProductEdge.new(edge).as_document,{upsert:true})

Embedding documents in existing documents with the Ruby Driver for MongoDB

I'm trying to embed a document inside an existing document using the Ruby Driver.
Here's what my primary document looks like:
db = Mongo::Connection.new.db("Portfolios")
project_collection = db.collection("Projects")
new_Project = { :url => 'http://www.tekfolio.me/billy/portfolio/focus', :author => 'Billy'}
project_collection.insert(new_Project)
After I've created my new_project and added it to my project_collection I may or may not add another collection to the same document later called assets. This is where I'm stuck. The following code doesn't seem to do anything:
new_asset = { :image_url => 'http://assets.tekfolio.me/portfolios/68fbb25a-8353-41a8-a779-4bd9762b00f2/projects/13/assets/20/focus2.PNG'}
new_Project.assest.insert(new_asset)
I'm certain I've butchered my understanding of Mongodb and the Ruby driver and the embeded document concept and would appreciate your help getting me out of this wet paper bag I can't seem to get out of ;)
Have you tried just setting the value of asset without insert and instead using update?
new_Project["asset"] = new_asset
project_collection.update({"_id" => new_Project["_id"]}, new_Project)
I think , are you trying to "update" the new_project record with the asset
it doesn't work because then you are only updating the hash in ruby, not in mongo, you have to first get the reference to the object in mongo, update it, and then save it, check this info:
http://www.mongodb.org/display/DOCS/Updating+Data+in+Mongo
(if you can, you can assign the asset before inserting, and it should work)

Where did this Ruby parameter convention come from?

There's a piece of Ruby middleware used by Rails and other frameworks to the parse the parameters you've sent to the server into a nested Hash object.
If you send these parameters to the server:
person[id] = 1
person[name] = Joe Blow
person[email] = joe.blow#test.com
person[address][street_address] = 123 Somewhere St.
person[address][city] = Chicago
person[address][zip] = 12345
person[other_field][] = 1
person[other_field][] = 2
person[other_field][] = 3
They get parsed to this:
{
:person => {
:id => "1",
:name => "Joe Blow",
:email => "joe.blow#test.com",
:address => {
:street_address => "123 Somewhere St.",
:city => "Chicago",
:state => "IL",
:zip => "12345"
},
:other_field => [ 1, 2, 3 ]
}
}
I believe this is supported by PHP as well. Can anybody tell me what this convention is called, where it came from, and what other languages support it? (Perl, Python, etc.)
Field research
I'm trying to find out if there's a name for this convention, but I can't find it yet.
Ruby
For what it's worth, the piece of middleware that does this in Ruby is Rack::Utils. See the source on Github.
There is some more information on the subject in the Ruby on Rails Guides.
And here is an interesting ticket about the code being moved from Rails to the Rack middleware.
PHP
I've done some digging in the PHP source, and it seems that all the magic there happens in the main/php_variables.c source file. The SAPI code calls the php_std_post_handler method defined here. This eventually calls the php_register_variable_ex method, which is 185 lines of complex string-parsing in C (I must admit that C isn't my forte).
The only name I could find here was the php_std_post_handler, the PHP standard POST handler.
Python
In Python, this format isn't supported by default. See this question here on stackoverflow.com on the subject.
Perl
The CGI library in Perl doesn't support this format either. It does give easy access to single or multiple values, but not nested values as in your example. See the documentation on feching the value or values of a single named parameter and fetching the parameter list as a hash.
Java
Check out the heated debate on the subject of query parameter parsing in this question. Java doesn't parse this 'nested format' of POST parameters into a data structure by default.
Conclusion
I've looked into this, and haven't found a single name for this way of parameter parsing. Of the languages that I've looked into, only Ruby and PHP support this format natively.
It's not called anything, AFAIK, other than "sending parameters". If anything, it's called "parameter [type] conversion", where Rails just "converts" it into a hash.
Other frameworks go further, using the parameter names as expressions used to create typed objects initialized with type-converted parameter values.
All parameters are is a string value with a name. Any/all structure is imposed by the language/framework in use on the server side; what it gets transformed to is 100% dependent on that language/framework, and what that conversion consists of would determine what it would be (reasonable) called.
that would be a JSON object, which is quite standard and supported by most languages/libraries these days.

Count operation with parameters with mongodb ruby driver

I was trying to use the count() Mongodb feature (db.collection_name.count({data:value}) using the Ruby Driver. I tried using de collection.count method, but it don't accept any parameters.
I checked the collection.count() method docs, it only returns the total amount of objects in the collection, where is no way you can pass a "filter" parameter to this method.
Is it possible use the count() Mongodb feature with filter parameters in some other way?
Is it possible use the count() Mongodb feature with filter parameters in some other way?
From the shell (command-line), you can do the following:
db.collection.find({ data : value}).count()
Obviously, you'll have to do something similar with Ruby, but it should be pretty straightforward.
For those who just want the answer using the ruby driver:
# actual number of records
DB["posts"].find({"author" => "john"}).to_a.length
=> 48
# *total* number of documents in the collection (the query matching is ignored)
DB["posts"].count({"author" => "john"})
=> 1431
# more efficient way (using the count server-side command)
DB["posts"].find({"author" => "john"}).count
=> 1431
It's actually possible to use .count() as you do on the command line, it's just poorly documented.
Mongo command line:
db.User.find({"emails.5": {"$exists": true}}).count()
Mongo Ruby driver:
db.collection('User').count({:query => {"emails.5" => {"$exists" => true}}})
This and other .count() options are document here.

Resources