Is it possible to upsert documents in mondodb by an array of ids, so that each new created document's _id is the one from the array? - ruby-on-rails

In my Rails project with mongodb, I retrieve user ids from twitter which I want to store in my User model. The plan is to upsert the collection of Users with the retrieved user ids I have in an array, and set every new created document _id to the corresponding user id from the array.
So when I do something like this:
Tweep.collection.find( _id: 1234567 ).modify( { "$set" => {a: true}, "$unset" => {c: ""} }, {upsert: true})enter code here
The result is like expected: <Tweep _id: 1234567, a(active): true, c(candidate_value): nil>
Now I want to do the same, but only passing an array of ids to upsert my collection on Users:
Tweep.collection.find(_id: {"$in" =>[123124,223553,6343643,23423]}, c: { "$exists" => true }).modify( { "$set" => {p: true}, "$inc" => {c: 1} }, {upsert: true})
The result is some newly created documents, but without the desired values as _id e.g. _id: 123124:
<Tweep _id: 5244501325fed0cfd2c1a615, a(active): nil, c(candidate_value): 1>
instead of:
<Tweep _id: 123124, a(active): nil, c(candidate_value): 1>
How can I make mongodb use the user id in my array to be the id for the field _id?
Any help is highly appreciated.

That is not possible. From the documentation:
If upsert is set to true and if no document matches the query
criteria, update() inserts a single document.
Then again, you write:
The result is some newly created documents
I hope that it's really only one document. With a query that contains only _id : {$in : [...]}, it wouldn't even insert a single document on my machine (MongoDB 2.5.0). It only inserts if I add more criteria. Could you check that again?
It's hard to come up with a meaningful definition of how this should behave. Combining $in can be tricky. Let's say you have two documents:
{ _id : 1, name : "John" }
{ _id : 2, name : "Jane" }
and you call
db.update({"_id" : {$in : [1,2]}, "name" : "Max"}, { ... }, { upsert: true});
What is supposed to happen? There is no document that has _id 1 or 2 and name equals "Max", so the upsert would have to insert... well, what? Maybe two documents with the name Max? If the array were larger, we'd create a ton of 'Max's which probably wasn't our intention (the semantics would be: "update one or insert a thousand", which is odd). So let's say we only insert one. But now, which _id to choose? And, of course, there is the problem that _id is a unique key, so in the example, both would fail.

Related

Ruby - Update jsonb array column setup with jsonb_accessor

I have this field called data that was setup with the jsonb_accessor ruby gem like this:
jsonb_accessor :data,
line_items: [:jsonb, array: true, default: []]
This is on my Contract model. And when a Contract is created, its an empty array like this:
data: {"line_items"=>[]},
I want to add multiple hashes to this empty array. For example this:
{user: "Joe"}
Then later I may add the hash:
{user: "Jack"}
I've tried many things, with NO luck including:
new = [{user: "Joe"}].to_json
Contract.last.update_all(["line_items = line_items || ?::jsonb", new])
##
Contract.last.update(["line_items = line_items || ?::jsonb", new])
Then I tried push
Contract.last.line_items.push("line_items" => {user: "Todd"})
Then I tried merge
Contract.last.line_items = Contract.last.line_items.merge(new)
Nothing has worked.
One option would just be to set the attribute with the data you want, then persist it with #save (or #save!)
contract = Contract.create!
=> #<Contract id: 1, data: {"line_items"=>[]}, line_items: []>
contract.line_items = [{foo: "bar"}]
contract.save!
contract
=> #<Contract id: 1, data: {"line_items"=>[{"foo"=>"bar"}]}, line_items: [{"foo"=>"bar"}]>
In my testing, I was able to use the #update method by just passing the object itself (rather than converting it to JSON first):
Contract.update(1, :line_items => [{foo: "bar"}])
=> #<Contract id: 1, data: {"line_items"=>[{"foo"=>"bar"}]}, line_items: [{"foo"=>"bar"}]>
Edit:
It looks like you're trying to append to the array with a single query, which appears to be possible with
Contract.where(id: 1).update_all("data=jsonb_set(data,'{line_items}', data->'line_items' || '{\"foo\": \"bar\"}')")
Though I'm sure there is a better way to achieve the same functionality...
jsonb_accessor means you don't have to manually mess around with JSON for simple accessor operations. If you want to assign to line_items use update! like normal.
contract.update!(line_items: [{user: "Joe"}])
If you want to append to a single Contract's line_items, add to the array in Ruby and update.
contract = Contract.last
line_items = contract.line_items + [{user: "Joe"}]
contract.update!(line_items: line_items)
If you want to do it all in SQL jsonb_accessor offers you no help. You have to write the SQL yourself. To add to an array, use jsonb_insert.
Contract
.where(...)
.update_all(
q[jsonb_insert(data, '{"line_items", -1}', '{user: "Joe"}', true)]
)
'{"line_items", -1}' says to insert at the last element of the key "line_items" and true has to insert after that point.

Rails find all with conditions

I want to search records on a Table based on an array of foreign key ids and trigger error if one of the ids doesn't exist, e.g.
pry(main)> Person.find([1, 2, 2002])
ActiveRecord::RecordNotFound: Couldn't find all Person
with 'id': (1, 2, 2002) [WHERE "persons"."deleted_at" IS
NULL] (found 2 results, but was looking for 3).
The problem is, the array of ids is not id, but parent_id which is a foreign key in Person. I want something like this, but it has deprecated:
Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
If I do like this,
Person.where(friends: ["Bob", "Steve", "Fred"])
it will just return 2 results instead of triggering error if Fred isn't found.
Thanks
You can use find_by method for column specific search.
Person.find_by(friends: ["Bob", "Steve", "Fred"])
Or
Person.find_by_friends(["Bob", "Steve", "Fred"])

Unable to get the values of enum in database

In rails 4, I have created an enum called books which is
enum books_category: {
horror: 1,
mystery: 2,
drama: 3
}
I have a column in the database called Books where I populated it using a csv file. My database attributes are name and books_category. Now when I upgraded to rails 5, books_category attribute of database is directly mapping to the string instead of integer. For example, previously books_category consists of 1 after upgrading it's saving the string "horror". How do I solve this issue?
You can use two ways.
1) create a model which saves business_categories in it.
or
2) when you retrieve record which is storing horror, you can pass it to the model like
my_model = Model.find_by("business_categot = ?","horror")
Model.books_category[my_model.books_category] # Returns the integer value
First of all, you are missing a colon after books_category. The code should look like:
enum books_category: {
horror: 1,
mystery: 2,
drama: 3
}
Please edit your question to include the datatype of books_category attribute.
You can change the value either using strings or ints
book = Book.last
book.books_category = 1
book.save!
=> book.books_category = "horror"
book = Book.last
book.books_category = 'drama'
book.save!
=> book.books_category = "drama"
Please note that Rails always returns the string value even though they are saved in integer datatype.
You can get the hash of all books_categories by calling
Book.book_categories
=> {"horror"=>1, "mystery"=>2, "drama"=>3}
And the keys and values by calling
Book.book_categories.keys
=> ["horror", "mystery", "drama"]
and
Book.book_categories.values
=> [1, 2, 3]
Finally, you can get a list of all the objects(books) having the category of horror with
Book.horror
=> Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."books_category" = ? [["books_category", 1]]
As you can observe from the query, it uses integer to search through the Books.
If you really want the values of your enum, consider using Enumerize gem. It is simpler and offers much more flexibility
Further Reading: https://hackhands.com/ruby-on-enums-queries-and-rails-4-1/

Rails query include? an array

I store an array of Section ids as integers. event.sections #=> ["1","115","130"]
There is no Events has_many Sections relationship. Maybe this is a problem. I only need id's from Section and nothing else, so I have the array of integers stored as a serialized string in Postgres.
I can do something like this, which returns an array of events:
Event.upcoming.select { |m| m.sections.include? #section.id.to_s}
Is there a way to query this to get back an ActiveRecord::Relation?
edit-----
My earlier select query is not correct, because if #section.id = "1" then it will match and select events with these id's "1", "10", "21", "100"
This is the proper select statement:
Event.upcoming.select {|e| ( e.newsletters.split(",").flatten.grep /^#{#section.id.to_s}$/ ).presence }

Compare separate columns in Mongoid

I'm looking for the mongoid equivalent to:
How to select the comparison of two columns as one column in Oracle
I can't seem to find any documentation or examples comparing columns within the same query. Is this just not possible in Mongoid?
Nope, you need to drop down to the mongodb ruby driver to do this and it will potentially be very slow as it is a javascript query that will not use an index:
Model.collection.find_one({"$where" => 'this.name == this.name2'})
Which is equivalent to third shell command here.:
> db.collection.insert({name: "awesome", name2: "awesome"})
> db.collection.insert({name: "awesome", name2: "awesome2"})
> db.collection.find('this.name == this.name2')
{ "_id" : ObjectId("xxx"), "name" : "awesome", "name2" : "awesome" }
> (line shown to signify end of results)
Note: if a document does not have key name and that same document also does not have key name2 that this will return true because null == null.

Resources