I am new to mongodb and mongoid. I am used to ActiveRecord/mysql in Ruby
on Rails so pardon my ignorance.
In ActiveRecord world if I want to search for all records meeting a certain
criteria (students from a specific zipcode), I could use
students = Student.where(zipcode: "12345")
and this would return an array of students.
Using Mongoid, if I query
Student.all.where(zipcode: "12345")
it just returns a criteria and I have to use an iterator like
students = []
Student.all.where(zipcode: "12345").each { |s| students << s }
Is there a better way to do a Mongoid /Mongo query to get all the documents
meeting the search criteria without using the ruby iterators (.each) ?
I have been referring to the mongoid document from
https://docs.mongodb.org/ecosystem/tutorial/mongoid-queries/
and couldn't find an example to get all the documents in one query.
You're being fooled by the console if you think:
students = Student.where(zipcode: "12345")
gives you an array of Students in students. That actually gives you a relation object in students but the relation's inspect (which is called by the console) will load the records from the database behind your back; similarly, as soon as you try to do anything array-ish with a relation, it will load records from the database.
Mongoid's where behaves the same but it won't necessary resolve to model instances in all the same cases that an ActiveRecord relation will.
The easiest thing to do (with both ActiveRecord and Mongoid) is to call to_a on the relation/criteria if you really want an array:
# ActiveRecord
students = Student.where(zipcode: "12345") # students is a relation
students = Student.where(zipcode: "12345").all # students is still a relation
students = Student.where(zipcode: "12345").to_a # students is now a real array
# Mongoid
students = Student.where(zipcode: "12345") # students is a criteria
students = Student.where(zipcode: "12345").all # students is still a criteria
students = Student.where(zipcode: "12345").to_a # students is now a real array
In both cases, if you want an array then just say so using to_a.
Related
I have two models in my Rails 6 / PostgreSQL application which have a many to many relation; Car and Driver. The goal is to get all records (Cars) which have no association at all (Drivers). At the moment this is done using Ruby (2.6.3) like this:
result = []
Car.all.each do |car|
result << car.id if car.drivers.empty?
end
Is there an ActiveRecord expression which avoids instantiating each record?
Try:
Car.left_joins(:drivers).where(drivers: { id: nil }).pluck(:id)
you first left join cars with drivers to have all the cars and drives associated with them, then you filter cars without drivers in where and finally select only one column (id) with pluck.
I have a schema where product has_many articles
I am retrieving a mongoid criteria based on scopes I created on the article model :
criteria = Article.published.with_image
From this criteria, I would like now to find all articles for which their products have a certain subject_id (or a subset of subject_ids).
I tried to writte :
criteria = criteria.in('product.subject_ids': data[:subjects])
where data[:subjects] is an array of subject_ids but this doesn't work.
Is there a clean way to do this with mongoid without having to loop over all articles from the first criteria or pluck all product_ids from the first criteria ?
How about any of these?
Project.where(:subject_id.in => data[:subject_id], :article_ids.in => criteria.pluck(:id))
criteria = Article.eagerload(:products).published.with_image
criterial.map {|art| return art.product if data[:subjects].any? {|subjects| art.product.subject_ids.include?(id) }
My title might be confusing, I wasn't sure what to write.
In rails I understand how to fetch Many Objects for One parent object
#first_user = User.first
#first_user_posts = #first_user.posts
But how can I fetch Many Objects for Many parent objects and select its attributes in one query?. I am trying to do something like that:
#many_posts = Post.all
#posts_by_user_gender = #many_posts.joins(:user).map(&:gender)
hoping it would give me an array that could look something like this:
#posts_by_user_gender => ["male", nil, "female", nil]
#I know I can do this map technique if I fetch it directly from the User model
# User.all.map(&:gender),
# but I want to start with those that posted in a specific category
# Post.where(:category_id => 1)
and then to count the males I could use the Ruby Array method .count
#males_count_who_posted = #posts_by_user_gender.count("male")
=> 1
I could always do 3 separate queries
#males_count_who_posted = #many_posts.select(:user_id).joins(:user)
.where("gender = ?", "male").count
#females_count_who_posted = ...
but I find that extremely inefficient, especially if I do the same for something like "industry" where you could have more than 3 options.
you can join model via SQL syntax
#posts_by_user_gender = #many_posts.joins("LEFT JOIN Users where users.id=posts.user_id").joins("LEFT JOIN Genders where genders.id=user.gender_id")
I have a Trainer model that has a has_many relationship with Pokemon. How do I find all Trainers who do not have any Pokemon of a certain type or group of types(type is a column in Pokemon model)?
The code I have tried, but it returns a trainer if any of his Pokemon's types is not in the group (e.g. if a trainer has pokemon of type fire and electric, he will be returned because electric isn't in the array. I don't want him returned because he has a fire pokemon.)
Trainer.joins(:pokemons).where("pokemons.type NOT IN (?)", ["fire","grass","water"])
The same problem occurs when I'm just comparing to one type.
Trainer.joins(:pokemons).where("pokemons.type != ?", "fire")
Again, the example trainer will get returned because he has a pokemon with a type, electric, that is not equal to fire.
I am using Rails 3.2.13 and Ruby 1.9.3.
I don't think there is a way to write this with Rails in one query, but you can do the following:
Trainer.where('trainers.id NOT IN (?)', Pokemon.where(type: ['grass', 'fire', 'water']).pluck(:trainer_id).uniq )
Explained version:
# selects the IDs of Trainer having a Pokemon of type Grass||Fire||Water
trainer_ids = Pokemon.where(type: ['grass', 'fire', 'water']).pluck(:trainer_id)
# returns the trainers
Trainer.where('trainers.id NOT IN (?)', trainer_ids)
In Rails 4, you can do:
Trainer.where.not(id: Pokemons.select(:trainer_id).where("pokemons.type IN (?)", ["fire","grass","water"])
This will grab all the trainers where their id does not show up in the list of trainer_ids on pokemons with fire, grass, or water.
An alternative is doing the query on the SQL layer through ActiveRecord's find_by_sql:
Trainer.find_by_sql [
"SELECT * FROM trainers
WHERE id NOT IN
(SELECT DISTINCT(trainers.id) FROM
trainers JOIN pokemons
ON trainers.id = pokemons.trainer_id
WHERE pokemons.type IN ('grass', 'fire','water'))"
]
I'm trying to find a record by associated username which is included in a belongs_to relation, but it's not working.
Articles belong to Users
Users have many articles
Article.where(user_id: someid) works fine, but I'd like to use the username as reference which is stored in the Users table.
Article.includes(:user).where(:username => "erebus")
Article.includes(:user).where("user.username" => "erebus")
I also have identity_map_enabled: true
Article.includes(:user).inclusions returns the relation details
Doesn't work, what am I not understanding?
You have to keep in mind that there are no joins in mongodb. In relational dbs, includes forms a join query and you can use columns from both the tables in query. However due to absence of joins in mongodb, same is not possible.
In mongoid, includes just saves a bunch of db calls. It fetches and stores the associated records in identity map for fast retrieval, but still while querying, one query can only deal with one collection.
If you need articles based on user names, I would suggest following work around:
user_ids = User.where(username: 'erebus').only(:_id).map(&:_id)
articles = Article.where(:user_id.in => user_ids)
You can make it little shorter from what rubish suggested:
user_ids = User.where(username: 'erebus').pluck(:id)
articles = Article.where(:user_id.in => user_ids)
Or one liner:
articles = Article.where(:user_id.in => User.where(username: 'erebus').pluck(:id))