Let's say I have defined my model Person with a couple of indexes:
class Person
include Mongoid::Document
field :email
field :ssn
index({ email: 1 }, { unique: true })
index({ ssn: 1 }, { unique: true })
end
However, only the email index already exists in the database, so when I call
Person.collection.indexes.each {|i| puts i.inspect}
I get the following response:
{"v"=>1, "key"=>{"_id"=>1}, "name"=>"_id_", "ns"=>"x.person"}
{"v"=>1, "unique"=>true, "key"=>{"email"=>1}, "name"=>"email_1", "ns"=>"x.person"}
The question is, how can I get the list of defined indexes in the model, even if they are not already created in mongo ?
In my case, such list should include the definition for the field "ssn"
In other words...How to get those indexes that haven't been created yet ?
Person.index_specifications
shows the indexes defined in the model regardless of its existence in the database.
And
Person.collection.indexes
only shows the index that actually exists in the database.
So there is something else that is worth paying attention to:
rake db:mongoid:create_indexes
will create the indexes defined in the model in the database, and it uses the method 'index_specifications' in deed.
While this removes all the indexes other than the index of the primary key:
rake db:mongoid:remove_indexes
So when you want to only remove the indexes that exists in the database but no longer defined in the database, you should use this:
rake db:mongoid:remove_undefined_indexes
which use the method 'undefined_indexes' in deed.
I hope this can be helpful.
The docs are here:
https://mongoid.github.io/en/mongoid/docs/indexing.html
http://www.rubydoc.info/github/mongoid/mongoid/Mongoid/Tasks/Database#create_indexes-instance_method
Just found it...
We can get the list of all index definitions into the model as follows:
Person.index_specifications
This is an array populated when the application is loaded and is used by the "create_indexes" method as can be seen here:
https://github.com/mongodb/mongoid/blob/master/lib/mongoid/indexable.rb
Related
class Parent < ApplicationRecord
has_many :children
enum status: {
status1: 0,
status2: 1
}
end
class Child < ApplicationRecord
belongs_to :parent
end
# error
# "Relation passed to #or must be structurally compatible. Incompatible values: [:references]"
combination = Parent.status1.or(Parent.status2.includes(:children).where(children: {name: 'ABC'}))
I want to get the data "status1" or "status2 has children named 'ABC'", but error occurs.
The or method takes another relation that has a similar filter pattern, and combines it with the already-existing filters on the object being called.
For example, Parent.status1.or(Parent.status2) would give you a set of records that have either status: 1 or status: 2.
(In case someone is not familiar with it, the example in the question also uses enum, which allows filtering the enum's attribute value using the name of the value. #status1 and #status2 in this case correspond to { status: 0 } and {status: 1} respectively.)
In order to call more relation methods to modify the final result, you must call them on the result of calling #or, like this:
Parent.status1.or(Parent.status2).includes(:children).where(children: {name: 'ABC'})
Based on your comment I see now that you want records that either (have status1) or (have status2 and have a matching children record).
Note that in order to use a relation in a where (like where(children: { name: value }) you must join with the related table (joins(:children).where(children: { name: value }). It seems that ActiveRecord will infer the join if you use only includes, but that's not documented as far as I can tell. This is why or sees the two relations as incompatible: one has children in the references list, while the other does not.
If you write the where clause by hand as a string, it does not change the references list, so or does not see the relation as incompatible. When you write a where clause by hand, you must explicitly use joins:
Parent.status1.joins(:children).or(Parent.status2.joins(:children).where("children.name = 'ABC'"))
You are not calling "includes" on the final or result.
parent = Parent.status1.or(Parent.status2)
parent.includes(:chilren).where(children: {name: "ABC"})
I am using Mongoid v4.0.2, and I'm running into an interesting issue using .includes(). I have a record that represents invoices, who has a list of charges.
I want to query for a single invoice and have the charges be populated after I run the query. According to the docs (search for "Eager Loading"), I should be able to do something like this to have Mongoid populate the charges:
Invoice.includes(:charges).find_by({ _id: <objectId> })
When I get the record back the charges are still showing up as a list of ObjectId's, and removing the .includes() seems to have no effect one way or another. I've verified each charge exists in the record I'm querying for, so I'm confused why they aren't populating.
I believe I have the data models set up correctly, but I'll include them here for completeness.
class Invoice
include Mongoid::Document
has_many :charges
field :status, type: String
field :created, type: Time, default: -> { Time.now }
end
class Charge
include Mongoid::Document
field :created, type: Time, default: -> { Time.now }
field :transactionId, type: String
field :category, type: String
field :amount, type: Float
field :notes, type: String
belongs_to :invoices
end
There is no reason to use includes if you are only finding one document. Just find the document and then access the relation. Either way, 2 database requests will be issued.
The only time includes provides a performance increase is when you are loading multiple relations for multiple documents, because what Mongoid will do is load the queried documents, go through and gather all of the ids that should be queried for all of those documents and then query for all relations as one database call using the :id.in => ids feature. In your case, there is no point to do this.
When I try to run the following code, DataMapper calls for 3 queries in just these two lines. Can anyone explain why it would do this?
#u = User.first(:uid => 1, :fields => [:uid, :name])
json #u
This calls the following queries:
SELECT "uid", "name" FROM "users" WHERE "uid" = 1 ORDER BY "uid" LIMIT 1
SELECT "uid", "email" FROM "users" WHERE "uid" = 1 ORDER BY "uid"
SELECT "uid", "accesstoken" FROM "users" WHERE "uid" = 1 ORDER BY "uid"
It is worth noting that datamapper has a validation on name for being => unique
Also, the accesstoken is lazily loaded so it should only be queried when asked for specifically, which must be happening when serializing it to a json object.
EDIT:
I have added my model class for clarification. I just want one query made for the uid and name without having to extract them individually from the object. Maybe this is the only way?
property :uid, Serial
property :name, String
property :email, String
property :accesstoken, Text
ANSWER:
Use the dm-serializer gem that has this support built-in
https://github.com/datamapper/dm-serializer
The first query is invoked by your User.first... call. Notice the fields it's selecting are what you requested - uid and name
The second and third queries are getting run in the json serialization, as it's lazy loading each property you didn't already load.
So you either need to do a custom serialization to only output uid and name for your users, or you should just remove the field selection from your initial query so it all gets loaded at once.
Update:
To do a custom serialization with datamapper, you can use the dm-serializer gem https://github.com/datamapper/dm-serializer and call #u.to_json(only: [:uid, :name])
Alternatively in this simple case you could just build the serialized object you want yourself, for which there are many examples: Rails3: Take controll over generated JSON (to_json with datamapper ORM)
I can get some data with where() method, but if some records were deleted with Paranoia delete() method (the deleted_at field is set with the date of deletion) they are not returned in the results.
I can get those records using collection.deleted.entries.find() with Moped, but I need it as usual Mongoid criteria data.
The paranoia plugin sets a default_scope on the model.
included do
field :deleted_at, type: Time
class_attribute :paranoid
self.paranoid = true
default_scope where(deleted_at: nil)
scope :deleted, ne(deleted_at: nil)
define_model_callbacks :restore
end
You can tell Mongoid not to apply the default scope by using unscoped, which can be inline or take a block.
Band.unscoped.where(name: "Depeche Mode")
Band.unscoped do
Band.where(name: "Depeche Mode")
end
I'm using the mongoid gem in Ruby. Each time I upsert, save or insert the same unique document in a collection, the Ruby instance shows a different id. For example, I have a script like so:
class User
include Mongoid::Document
field :email, type: String
field :name, type: String
index({ email: 1}, { unique: true })
create_indexes
end
u=User.new(email: 'test#testers.edu', name: "Mr. Testy")
u.upsert
puts u.to_json
The first time I run it against an empty or non-existent collection, I get this output
{"_id":"52097dee5feea8384a000001","email":"test#testers.edu","name":"Mr. Testy"}
If I run it again, I get this:
{"_id":"52097e805feea8575a000001","email":"test#testers.edu","name":"Mr. Testy"}
But the document in MongoDB still shows the first id (52097dee5feea8384a000001), so I know we are operating on the same record. If I always follow the upsert with a find_by operation, I get the right id consistently, but it feels inefficient to have to run an upsert followed by a query.
Am I doing something wrong? I'm concerned that I will be getting the wrong id back in an operation where someone is, say, updating his profile repeatedly.