Hopefully a simple question. I have several models, two of them, :users and :songs, interact to get data from the database.
A User has_many :songs.
I'm trying to find the users with the most songs in the USER INDEX action, i.e list the 10 users with the most songs, user with the most songs at the top, descending.
So far I have in the users_controller in index;
#users = User.all
And so far all I can do in the view, users/index;
<% #users.each do |user| %>
<%= user.name %><%= user.songs.count %>
<% end %>
Works, it counts the songs but how do I order by users with most songs?
I have been looking at sort_by inside the block and I guess it could be done by listing all songs and grouping them by the user, but I feel that is not efficient enough. -as you can see I'm not an advanced developer.
Please tell me what you guys think and the answer to my solution if possible. The thing that is confusing me is that I am ordering a list generating from a table by data generated by another table. Must be simple but I haven't done it before so I can't get my head around it.
Thanks in Advance.
You should use something like this:
User.includes(:songs).order{|x| x.songs.size}
This does everything in one query so it should be more efficient than for example
User.all.sort{|x| user.songs.size}
Which would perform a query for each user
How about
User.all(:limit => 10, :order => "(select count(user_id) from songs where user_id = users.id) DESC")
Related
I am trying to produce a high score chart, with a list of users who have been given a "thumbs" ordered by the number of thumbs descending. I have it almost working, but cannot think how to get the records to order correctly.
So we have two models, being User and Thumb.
In the controller, we have
def thumbs_highscores
user_ids = current_account.thumbs.from_this_month.map(&:user_id)
#users = current_account.users.where(:id =>emp_ids)
end
In the view I am then displaying the high scores likes this...
%table.center{:style => "max-width: 600px"}
%tr
%th Rank
%th Name
%th.right # Thumbs received
- #users.each_with_index do |user, i|
%tr
%td= i+1
%td= user.name
%td.right
- user.thumbs.from_this_month.each do |thumb|
%i.fa.fa-star.orange
This works nicely, but is not displaying the users in the correct order. I would like to amend the controller code to list the users in the order of how many thumbs they have had in the last month but cannot get my head round it. Can anyone help?
thanks
change
#users.each_with_index do |user, i|
to
#users.sort_by{|u| u.thumbs_from_this_month.size}.reverse.each_with_index do |user, i|
You could eager load the thumbs associations in the controller to make this more efficient, and do the ordering there.
From what I understand from your code you should have a scope named from_this_month so my suggestion is to add order in the query, so you don't need to care about extra code and complexity to order your data.
Check the order documentation
I guess you could use the help of scope here. I'm not very skilled with lambdas, but I guess something like this in the user-model would work:
scope :thumbs_desc_list, lambda { User.all.map {|u| u.thumbs_this_month}.sort { |x,y| y <=> x } }
This ofcourse assumes you have an instance method in your user-model called thumbs_this_month which gives you the amount of thumbs received this month.
After that in the controller:
#users = User.thumbs_desc_list
... and now you should be able to iterate over #users in your view wherever you want, having them ordered by thumbs amount descendingly.
Using scope will allow you to use the properly order list wherever you need afterwards too.
You can change your code into below:
User.select("users.*, thumbs.*, count(thumbs.id) AS thumbs_count").joins(:thumbs).from_this_month.group("users.id").order("thumbs_count DESC")
Let me know when it works.
I'm trying to rank my user's in order of an integer. The integer I'm getting is in my User Model.
def rating_number
Impression.where("impressionable_id = ?", self).count
end
This gives each User on the site a number (in integer form). Now, on the homepage, I want to show an ordered list that places these user's in order with the user with the highest number first and lowest number second. How can I accomplish this in the controller???
#users = User....???
Any help would be appreciated!
UPDATE
Using this in the controller
#users = User.all.map(&:rating_number)
and this for the view
<% #users.each do |user| %>
<li><%= user %></li>
<% end %>
shows the user's count. Unfortunately, the variable user is acting as the integer not the user, so attaching user.name doesn't work. Also, the list isn't in order based on the integer..
The advice here is still all kinds of wrong; all other answers will perform terribly. Trying to do this via a nested select count(*) is almost as bad an idea as using User.all and sorting in memory.
The correct way to do this if you want it to work on a reasonably large data set is to use counter caches and stop trying to order by the count of a related record.
Add a rating_number column to the users table, and make sure it has an index defined on it
Add a counter cache to your belongs_to:
class Impression < ActiveRecord::Base
belongs_to :user, counter_cache: :rating_number
end
Now creating/deleting impressions will modify the associated user's rating_number.
Order your results by rating_number, dead simple:
User.order(:rating_number)
The advice here is just all kinds of wrong. First of model your associations correctly. Secondly you dont ever want to do User.all and then sort it in-memory based on anything. How do you think it will perform with lots of records?
What you want to do is query your user rows and sort them based on a subquery that counts impressions for that user.
User.order("(SELECT COUNT(impressions.id) FROM impressions WHERE impressionable_id = users.id) DESC")
While this is not terribly efficient, it is still much more efficient than operating with data sets in memory. The next step is to cache the impressions count on the user itself (a la counter cache), and then use that for sorting.
It just pains me that doing User.all is the first suggestion...
If impressions is a column in your users table, you can do
User.order('impressions desc')
Edit
Since it's not a column in your users table, you can do this:
User.all.each(&:rating_number).sort {|x,y| y <=> x }
Edit
Sorry, you want this:
User.all.sort { |x, y| y.rating_number <=> x.rating_number }
I'm trying to get a count of how many subcontacts every contact has.
Class Contacts
has_many :subcontacts, class_name: "Contact",foreign_key: "supercontact_id"
belongs_to :supercontact, class_name:"Contact"
And here's the activerecord part i have so far that's roughly what i'm trying to do.
Contact.joins{subcontacts.outer}.select(subcontacts.count as subcontact_count)
I think the problem is that the joins portion is looking for a association name and the select part is looking for a table name. The trouble is that the table name is the same table... What's the best way to do this so that it stays as a relation or using SQL so that we can minimize the number of queries so that it isn't an N+1 problem?
Try using
results = Contact.joins(:subcontacts).select("count(subcontacts.id) as count, contacts.id").group("contacts.id")
and count can be fetched as
results.map do |result|
"Contact ID: #{result.id} - Subcontacts Count: #{result['count']}"
end
Contacts.all.each do |contact|
puts contact.name => contact.subcontacts.count
end
OR
Contacts.all.map{|contact| [contact.name => contact.subcontacts.count]}
The above will provide you the hash like answer{contact_name => subcontacts.count}
I don't know what this feature should be called.... so I can just describe the basic scenario:
topic has_many tags through :tagging
tag has_many topics through :tagging
so, in the #topic show page, I want to display all topics which have the tags that belong to #topic( Not with the same tags, just one common tag)
One possible approach is
tags.each do |tag|
tag.topics.each do |topic|
topic
end
end
but this would result dulipications in topic, since a topic may belongs to different tags
I find that it's possible to use ids.uniq to remove the dulicates in an array. So would this be a solution? And how can I get the topic_ids? Maybe topic_ids= topic_ids + topic.id?
This will give you all topics that have tag_id's matching those associated with #topic. Note that this will also include the original #topic:
topics = Topic.joins(:taggings).
where(:taggings => {:tag_id => #topic.taggings.pluck(:tag_id) }).
uniq
If you want to exclude the original, just add an additional where to the chain:
where("taggings.topic_id != ?", #topic.id)
There is a slightly more efficient way to do this using a subquery instead of the initial database call to get the associated tag_ids, but this will probably suffice.
I prefer acts_as_taggable_on then I use
<%= raw #topic.find_related_tags.map { |t| link_to(t.title, topic_path(t)}.join(" ") %>
I have a following SQL QUERY:
SELECT articles.name, articles.price, users.zipcode FROM articles INNER JOIN users ON users.id = articles.user_id WHERE vectors ## to_tsquery('crime') ORDER BY articles.price ASC
And I Would like to write it inside of a find method from an ActiveRecord Class named Articles (Articles belongs_to user). Basically i wanna search for Articles and access the zipcode propertie from the User (user has_many Articles)
I wrote the following version, but i'm not sure that its working because in the response i dont receive any information about the user zipcode.
a = Article.find(:all,:conditions=>"vectors ## to_tsquery('crime')",:joins= >:user,:order=>:price,:include=>:user)
But i have no idea how to access the zipcode information. how can I access this information ? this is the right approach ?
Regards,
Victor
If you've coupled Articles and Users like you say above, this should be pretty easy:
#articles = Article.find(:all, :conditions => "…", :include => :user)
Then, in your view, you can do:
<ul>
<% for each article in #articles do %>
<li><%= article.user.zipcode %></li>
</ul>
<% end %>
This works because Rails creates a property for the parent object (User) in the model (Article) - you can read more about that in the API docs. This even works without the "include" key above, but leaving it out would mean a database query in every step of the loop.