I have two models
class User
include Mongoid::Document
include Mongoid::Timestamps
field :username
embeds_many :user_tags
end
class UserTag
include Mongoid::Document
field :name
field :like_count, :type => Integer, :default => 0
embedded_in :user
end
I want to query all the users that have the user_tag named "nyc" and where the user_tag "nyc" has a like_count > 10. I've tried the following:
users = User.where('user_tags.name' => "nyc").and('user_tags.like_count' => {'$gte' => 10 })
Logically this does what it's supposed to do, but not what I need it to do. It returns users that have the user_tag "nyc" and have any user_tag with a like_count >= 10. I need users that have the user_tag "nyc" and where the user_tag "nyc"'s like_count is >= 10.
How do I do that? I'm running mongoid 4.0.2.
Actually your query is not correct for the purpose you are trying to achieve. It translates to the following MongoDB query:
db.users.find({'user_tags.name': 'nyc' }, {'user_tags.like_count': {$gte: 10}})
It means that MongoDB will find all documents with both criteria. Mongoid is returning you the same data, as MongoDB.
What you need instead is the following MongoDB query:
db.users.find({ user_tags: {
$elemMatch: {
name: 'nyc',
like_count: { $gte: 10 }
}
}})
With Mongoid you can write:
User.where(user_tags: {
'$elemMatch' => {
name: 'nyc',
like_count: { '$gte' => 10 }
}
}).count
Maybe you should write something like this:
users = User.where('user_tags.name' => "nyc", 'user_tags.like_count' => {'$gte' => 10 })
Mongoid will try to find Documents which satisfies both conditions.
You can try this
users = User.where('user_tags.name' => "nyc").where('user_tags.like_count' => {'$gte' => 10 }).all
or
users = User.where('user_tags.name' => "nyc", 'user_tags.like_count' => {'$gte' => 10 }).all
Related
I would like to know how to add an extra field to the response of collection.aggregate?
The query below groups activities by user_id. And I would like to know how to also include the user_name in the response.
DB Models
class Activity
include Mongoid::Document
field :hd_race_id, type: Float
field :metric_elapsed_time, type: Float
field :metric_distance, type: Float
field :user_name, type: String
belongs_to :user
...
class User
include Mongoid::Document
field :user_name, type: String
has_many :activities
...
Query
Activity.collection.aggregate([
{
"$group" => {
"_id" => "$user_id",
"distance" => { "$sum" => "$metric_distance" },
"time" => { "$sum" => "$metric_elapsed_time" },
},
},
{ "$sort" => { "distance" => -1 } },
])
Thank you in advance
Use the operator $first (aggregation accumulator) inside the $group stage.
For example:
"user_name": {"$first": "$user_name"}
or for the programming language you are using (not sure what it is), try something like:
"user_name" => {"$first" => "$user_name"},
For an example, see the "Group & Total" chapter in my Practical MongoDB Aggregations book
I have a class model, a student model and an attendance model. Attendance is embedded in Student to improve the performance.
I want to show number of all students in Class, number of present students, number of absent student & percentage of attendance. I am a newbie in Mongodb and i would appreciate any help. Thanks you for your time.
class Klass
include Mongoid::Document
include Mongoid::Timestamps
has_and_belongs_to_many :students
field :name, type: String
end
class Student
include Mongoid::Document
include Mongoid::Timestamps
has_and_belongs_to_many :klasses
embeds_many :attendances
field :name, type: String
end
class Attendance
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :student
field :status, type: Integer # 1 = Present, 2 = Absent
field :klass_id, type: BSON::ObjectId
end
I have solved my problem by following technique.
#students_present_today = #class.students.where({ attendances: { '$elemMatch' => {status: 1, :created_at.gte => Date.today} } }).count
#students_absent_today = #class.students.where({ attendances: { '$elemMatch' => {status: 2, :created_at.gte => Date.today} } }).count
You can try these:
#class = Klass.where(name: 'something').first
#total_students = #class.students.count
#present_students = #class.students.where('attendances.status' => '1').count
#absent_students = #class.students.where('attendances.status' => '2').count
#p_s_today = #class.students.where('attendances.status' => '1', 'attendances.created_at' => {'$gte' => Date.today} ).count
#a_s_today = #class.students.where('attendances.status' => '2', 'attendances.created_at' => {'$gte' => Date.today} ).count
I'm trying to create a json array (string actually) based on my db structure. I have the following relationship:
Country > State > City
The way I'm doing it now is very innefficient (N+1):
data = "[" + Country.all.map{ |country|
{
name: country.name,
states: country.states_data
}.to_json
}.join(",") + "]"
Then on the Country model:
def states_data
ret_states = []
states.all.each do |state|
ret_states.push name: state.name, cities: state.cities_data
end
ret_states
end
Then on the State model:
def cities_data
ret_cities = []
cities.all.each do |city|
ret_cities.push name: city.name, population: city.population
end
ret_cities
end
How can I do this more efficiently?
Eager load the states and cities. Just be careful because this could take up a lot of memory for large datasets. See documentation here http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations. Whenever possible I like using joins in addition to includes to fetch all data at once.
#to_json will also serialize Arrays for you, so you don't need to manually add bits of JSON.
Your code from above could be altered like so:
data = Country.joins(:states => :cities).includes(:states => :cities).all.map{ |country|
{
name: country.name,
states: country.states_data
}
}.to_json
But you could also remove the need for the _data methods.
data = Country.joins(:states => :cities).includes(:states => :cities).to_json(
:only => :name,
:include => {
:states => {
:only => :name,
:include => {
:cities => {
:only => [:name, :population]
}
}
}
}
)
That is pretty ugly, so you may want to look into overriding #as_json for each of your models. There is a lot of information about that available on the web.
u can provide the model to be included when converting to json.
country.to_json(:include => {:states => {:include => :cities}})
check http://apidock.com/rails/ActiveRecord/Serialization/to_json for
I am trying to optimize my code with eager loading, but when ever where function is called, a query is executed in logs.
#votes_list = Vote.joins(:user => :profile).where(:post_id => post.id)
#male_votes = #votes_list.where(:profiles => { :gender => 1 }).count
#female_votes = #votes_list.where(:profiles => { :gender => 2 }).count
I am trying to make few queries after the first one, without need to fetch from database, how to do it?
You want to eagerly load the Users and their Profile for each vote. Then you can select the sub-set of votes in-memory broken down by gender on the profile.
#votes_list = Vote.where(:post_id => post.id, :include => { :user => :profile })
#male_votes = #votes_list.select {|v| v.user.profile.gender == 1}
#female_votes = #votes_list.select {|v| v.user.profile.gender == 2}
I have a donations table where I'm trying to calculate the total amount for each month. For months without without any donations, I'd like the result to return 0.
Here's my current query:
Donation.calculate(:sum, :amount, :conditions => {
:created_at => (Time.now.prev_year.all_year) },
:order => "EXTRACT(month FROM created_at)",
:group => ["EXTRACT(month FROM created_at)"])
which returns:
{7=>220392, 8=>334210, 9=>475188, 10=>323661, 11=>307689, 12=>439889}
Any ideas how to grab the empty months?
Normally you'd left join to a calendar table (or generate_series in PostgreSQL) to get the missing months but the easiest thing with Rails would be to merge your results into a Hash of zeroes; something like this:
class Donation
def self.by_month
h = Donation.calculate(:sum, :amount, :conditions => {
:created_at => (Time.now.prev_year.all_year) },
:order => "EXTRACT(month FROM created_at)",
:group => ["EXTRACT(month FROM created_at)"])
Hash[(1..12).map { |month| [ month, 0 ] }].merge(h)
end
end
then just call the class method, h = Donation.by_month, to get your results.
In addition to mu is too short answer, in Rails 3.2.12 did not work for me, ActiveRecord returns the keys as strings:
h = Donation.calculate(:sum, :amount, :conditions => {
:created_at => (Time.now.prev_year.all_year) },
:order => "EXTRACT(month FROM created_at)",
:group => ["EXTRACT(month FROM created_at)"])
Which returns:
{"7"=>220392, "8"=>334210, "9"=>475188, "10"=>323661, "11"=>307689, "12"=>439889}
So when I merge the hash with zeros:
{1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0, 7=>0, 8=>0, 9=>0, 10=>0, 11=>0, 12=>0, "7"=>220392, "8"=>334210, "9"=>475188, "10"=>323661, "11"=>307689, "12"=>439889}
The little fix (to_s):
Hash[(1..12).map { |month| [ month.to_s, 0 ] }].merge(h)