Create a Model Scope using hash attribute - ruby

I am using mongoid-history gem and mongoid in Ruby. I am actually linking the mongo history to a model called SocialPost, so i can make something like.
history = current_user.social_posts.history_tracks
Now i need to filter this 'history' with a scope or a method that filter attribute 'association_chain' from history tracker model, but 'history_tracks' attributes are made in this way:
<HistoryTracker _id: 57bdc1cb65e59325ae000001, created_at: 2016-08-24 15:48:27 UTC, updated_at: 2016-08-24 15:48:27 UTC, association_chain: [{"name"=>"SocialPost", "id"=>BSON::ObjectId('57ac8b0f65e5930944000000')}], modified: {"facebook_likes_count"=>2594213, "tweeter_followers_count"=>0}, original: {}, version: 1, action: "update", scope: "social_post", modifier_id: nil>
So, how i can create a search in my HistoryTracker model that allow me to search a specific group of ids inside association_chain, something like this:
HistoryTracker.where(:association_chain.in => {"name"=>"SocialPost", "id"=>[GROUPS OF IDS TO SEARCH]}
UPDATE using $elemMatch
#test case multiple social_id: empty result
HistoryTracker.any_in({:association_chain.elem_match => [{name: 'SocialPost', :id.in => social_ids}] })
#test case 1 social_id: match result
HistoryTracker.any_in({:association_chain.elem_match => [{name: 'SocialPost', :id => social_ids.first}] })

I found already a posible solution. The elemMatch should be constructed grouped with ids you need to collect, so this is what i did:
social_ids = [BSON::ObjectId('...1'), BSON::ObjectId('...2'), BSON::ObjectId('...3')]
#reorder the ids with the extra hash elements
a = []
social_ids.each do |id_bson|
a << {name: 'SocialPost', id: id_bson}
end
And now you create the 'QUERY'
HistoryTracker.any_in({:association_chain.elem_match => a})

Related

How to update embedded object of an array inside mongodb document using mongoid with ruby?

I want to update an embedded object of an array inside of mongodb collection using ruby and mongoid. I have an schema like this
{
_id: 23234342,
age: 23,
customer_name: 'alex',
email: 'alex#gmail.com',
orders: [
{
reference_number: '10',
amount: 2300,
ordered_at: '01-01-2023 11:12:00'
},
{
reference_number: '12',
amount: 2300,
ordered_at: '01-01-2023 11:12:00'
},
]
}
Now i want to update the value of amount of orders object with reference_number = 12
I know i can do like this
Customer.where(_id: "23234342").elem_match(orders: { reference_number: 12 }).update("$set" => {"orders.$.amount" => 4000})
But i want the correct syntax in ruby mongoid for this query.
How can i achieve using ruby mongoid?

Updating in MongoDB without first having to pull a Mongoid object?

When performing queries I can go through Mongoid:
product_obj = Product.where(
_id: "58f876f683c336eec88e9db5"
).first # => #<Product _id: 58f876f683c336eec88e9db5, created_at: nil, updated_at: nil, sku: "123", name: "Some text", ...)
​
or I can circumvent it:
product_hsh = Product.collection.find( {
_id: BSON::ObjectId.from_string("58f876f683c336eec88e9db5")
}, {
projection: {
_id: 1,
name: 1
}
} ).first # => {"_id"=>BSON::ObjectId('58f876f683c336eec88e9db5'), "name"=>"Some text"}
I prefer to circumvent, because it performs better; I can limit which fields to get in the response.
My problem, however, is how to further work with the returned product. The response is a hash, not an object, so if I have to update it I need to pull it through Mongoid anyway, and thereby the performance gains are gone:
Product.find(product_hsh["_id"]).update_attribute(:name, "Some other text")
My question is: how do I update without first having to pull a Mongoid object?
You don't need to pull/fetch at all. You can just send the $set commands directly:
Product.where(id: product_hsh["_id"]).update_all(name: "Some other text")

Upsert hash in mongodb. Remove unused keys

From the Mongo documentation:
If you specify multiple field-value pairs, $set will update or create
each field.
I have a mongoid document like this:
class MyCounter
include Mongoid::Document
field :date, type: Date
field :properties, type: Hash
end
and when I try to change the properties like this:
hash = {properties.0 => 50, properties.1 => 100 }
MyCounter.where(something).find_and_modify(
{ '$set' => hash, {'upsert' => 'true', new: true}
)
it keeps the old keys on the property hash.
What's the correct way to completely replace (and create a new document if it doesn't exist) an hash in a document?
EDIT
I'm currently doing this which is dumb:
MyCounter.where(
date: date
).find_and_modify(
{ '$unset' => { properties: nil} }, {'upsert' => 'true', new: true}
)
MyCounter.where(
date: date
).find_and_modify(
{ '$set' => hash }, {'upsert' => 'true', new: true}
)
Just don't use $set. By only passing field: value pairs all the fields are replaced (except the _id field)
This should work fine.
MyCounter.where(
date: date
).find_and_modify(
hash, {'upsert' => 'true', new: true}
)
http://docs.mongodb.org/manual/reference/method/db.collection.update/#example-update-replace-fields

Batch insert multiple records with Mongoid?

I am reading through this Stackoverflow answer about how to insert multiple documents in Mongoid in one query. From the answer I read:
batch = [{:name => "mongodb"}, {:name => "mongoid"}]
Article.collection.insert(batch)
I need an example to understand how this works. Say we have the Article class:
class Article
include Mongoid::Document
include Mongoid::Timestamps
field :subject, type: String
field :body, type: String
field :remote_id, type: String
validates_uniqueness_of :remote_id
belongs_to :news_paper, :inverse_of => :articles
end
And the I e.g. create an array of articles:
[ {subject: "Mongoid rocks", body: "It really does", remote_id: "1234", news_paper_id: "abc"},
{subject: "Ruby rocks", body: "It really does", remote_id: "1234", news_paper_id: "abc"},
{subject: "Rails rocks", body: "It really does", remote_id: "5678", news_paper_id: "abc"} ]
How do I create them, and at the same time make sure the validation catches that I have 2 remote_id's that are the same?
If you add a unique indexing for remote_id field, MongoDB will take care the uniqueness of this field
index({ remote_id: 1 }, { unique: true })
Don't forget to run create_indexes: rake db:mongoid:create_indexes
After that, you are free to use Article.collection.insert(batch).

Mongoid: ActiveModel Numericality Validation, allow_nil does not work

I've defined a Mongoid model with an Integer field for which i validate numericality like this
# source.rb
class Source
field :code, type: Integer
validates_numericality_of :code, allow_nil: true
The purpose of allow_nil is to validate fields which are present & ignore nil values.
But here, allow_nil completely bypasses the numericality check
object = Source.new
object.code = "ABC"
object.valid?
=> true
object
=> #<Source _id: 50d00b2d81ee9eae46000001, _type: nil, code: 0>
In activerecord, this works correctly
object = Source.new
object.code = "ABC"
object.valid?
=> false
object
=> #<Source id: nil, code: 0, created_at: nil, updated_at: nil>
object.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false
Mongoid behaves slightly different to Active Record when using #valid? on already persisted data. Active Record's #valid? will run all validations whereas Mongoid's #valid? will only run validations on fields where data has changed as an optimization. - see mongoid validation
so this could be your problem.
you could try
validates_numericality_of :code, :allow_nil => true
and
validates :code, :numericality => true ,:allow_nil => true

Resources