limit collect in ruby - ruby-on-rails

I currently have the following:
users = User.all
comments = users.collect(&:comments)
However, if there are thousands of comments, i'd like to only collect 10 from each user to limit the number of database queries that are made. Is there a way to do this?

users = User.all
comments = users.collect { |user| user.comments.limit(10) }
Or with another association in you model :
has_many :first_comments, :class_name => "Comment", :limit => 10
Then this will result in only two database queries:
users = User.includes(:first_comments)
comments = users.collect(&:first_comments)

The easiest in terms of query looks a little convoluted:
Comment.where(["user_id IN (?)", users.collect(&id) ]).limit(10)
I presume your sort order is set by a default scope somewhere.
Rails 2:
Comment.all(:conditions => ["user_id IN (?)", users.collect(&id) ], :limit => 10)

Try this
comments = Comment.where(:user_id=>users).limit(10)
or
comments = Comment.all(:conditions => {:user_id=>users}, :limit => 10)
You can use any one that suits you

users = User.all
comments = Comment.order("DESC created_at").limit(10).all
Or, if you only need users for these 10 recent comments, you can try
comments = Comment.includes(:users).order("DESC created_at").limit(10).all
users = comments.map(&:user)

Related

how to paginate records from multiple models? (do I need a polymorphic join?)

After quite a bit of searching, I'm still a bit lost. There are a few other similar questions out there that deal with paginating multiple models, but they are either unanswered or they pagainate each model separately.
I need to paginate all records of an Account at once.
class Account
:has_many :emails
:has_many :tasks
:has_many :notes
end
So, I'd like to find the 30 most recent "things" no matter what they are. Is this even possible with the current pagination solutions out there?
Like using some combination of eager loading and Kaminari or will_paginate?
Or, should I first set up a polymorphic join of all these things, called Items. Then paginate the most recent 30 items, then do a lookup of the associated records of those items.
And if so, I'm not really sure what that code should look like. Any suggestions?
Which way is better? (or even possible)
Rails 3.1, Ruby 1.9.2, app not in production.
with will_paginate :
#records = #do your work and fetch array of records you want to paginate ( various types )
then do the following :
current_page = params[:page] || 1
per_page = 10
#records = WillPaginate::Collection.create(current_page, per_page, records.size) do |pager|
pager.replace(#records)
end
then in your view :
<%=will_paginate #records%>
Good question... I'm not sure of a "good" solution, but you could do a hacky one in ruby:
You'd need to first fetch out the 30 latest of each type of "thing", and put them into an array, indexed by created_at, then sort that array by created_at and take the top 30.
A totally non-refactored start might be something like:
emails = Account.emails.all(:limit => 30, :order => :created_at)
tasks = Account.tasks.all(:limit => 30, :order => :created_at)
notes = Account.notes.all(:limit => 30, :order => :created_at)
thing_array = (emails + tasks + notes).map {|thing| [thing.created_at, thing] }
# sort by the first item of each array (== the date)
thing_array_sorted = thing_array.sort_by {|a,b| a[0] <=> b[0] }
# then just grab the top thirty
things_to_show = thing_array_sorted.slice(0,30)
Note: not tested, could be full of bugs... ;)
emails = account.emails
tasks = account.tasks
notes = account.notes
#records = [emails + tasks + notes].flatten.sort_by(&:updated_at).reverse
#records = WillPaginate::Collection.create(params[:page] || 1, 30, #records.size) do |pager|
pager.replace(#records)
end
Thats it... :)

In Rails, how do I retrieve 10 most recent entries in a model?

Let's say I want to return the last entry in a model, it is easy. The most recent post is found as (assuming descending order)
#post = Post.last
What if I wanted the 10 most recent posts ie
#recentposts = Post.#whatdoIputhere?
How could I most easily and efficiently do this?
Thanks!
An an alternative to James Schorr's answer:
posts = Post.order('created_at DESC').limit(10)
The benefit of this alternative is that it allows you to continue to chain more relational scopes on the end of it:
posts.where(:user_id => 1)
It's not until the object is iterated over or inspected that the SQL query actually runs.
Try this:
#recentposts = Post.all(:order => 'created_at DESC', :limit => 10)
try this all
#recentposts = Post.order("created_at desc").limit(10)
In Rails 4, you can do
Post.order(created_at: :desc).limit(10)
To get the last 10 records in descending order:
Post.last(10).reverse

How do I find the items immediately before and after a specific one in an ordered array, using Ruby on Rails?

I have an array of 'questions' ordered according to their number of votes, and want to show the question immediately before and immediately following the currently-selected question.
Let's say the currently-selected question is stored in the variable #question, and I'm showing a list of other questions associated with the same user. This code orders that list according to number of votes:
questions = Question.find(:all, :conditions => {:user => #question.user}).sort { |q1,q2| q2.votes.length <=> q1.votes.length}
Now how do I pick out just the question before and the question after #question in that list?
Updated my answer. I figured you guys would get the gist from the first post but here is a more elaborated example.
Can't you do something simple like:
#questions = user.questions.sort_by{ |q| -q.votes.length }
current_question_index = #questions.index(#question)
#prev_question = #questions[current_question_index-1]
#next_question = #questions[current_question_index+1]
It's more lines but just uses simple array manipulation
I would refer to time stamps.
prev = Question.first(:conditions => ["created_at < ?", #question.created_at], :order => "created_at DESC")
prev = Question.first(:conditions => ["created_at > ?", #question.created_at], :order => "created_at ASC")

Fastest way to search two models connected via join table in rails given large data set

I have a user model and a cd model connected through a join table 'cds_users'. I'm trying to return a hash of users plus each cd they have in common with the original user.
#user.users_with_similar_cds(1,4,5)
# => {:bob => [4], :tim => [1,5]}
Is there a better/faster way of doing this without looping so much? Maybe a more direct way?
def users_with_similar_cds(*args)
similar_users = {}
Cd.find(:all, :conditions => ["cds.id IN (?)", args]).each do |cd|
cd.users.find(:all, :conditions => ["users.id != ?", self.id]).each do |user|
if similar_users[user.name]
similar_users[user.name] << cd.id
else
similar_users[user.name] = [cd.id]
end
end
end
similar_users
end
[addition]
Taking the join model idea, I could do something like this. I'll call the model 'joined'.
def users_with_similar_cds(*args)
similar_users = {}
Joined.find(:all, :conditions => ["user_id != ? AND cd_id IN (?)", self.id, args]).each do |joined|
if similar_users[joined.user_id]
similar_users[joined.user_id] << cd_id
else
similar_users[joined.user_id] = [cd_id]
end
end
similar_users
end
Would this be the fastest way on large data sets?
You could use find_by_sql on the Users model, and Active Record will dynamically add methods for any extra fields returned by the query. For example:
similar_cds = Hash.new
peeps = Users.find_by_sql("SELECT Users.*, group_concat(Cds_Users.cd_id) as cd_ids FROM Users, Cds_Users GROUP BY Users.id")
peeps.each { |p| similar_cds[p.name] = p.cd_ids.split(',') }
I haven't tested this code, and this particular query will only work if your database supports group_concat (eg, MySQL, recent versions of Oracle, etc), but you should be able to do something similar with whatever database you use.
Yap, you can, with only 2 selects:
Make a join table model named CdUser (use has_many.. through)
# first select
cd_users = CdUser.find(:all, :conditions => ["cd_id IN (?)", args])
cd_users_by_cd_id = cd_users.group_by{|cd_user| cd_user.cd_id }
users_ids = cd_users.collect{|cd_user| cd_user.user_id }.uniq
#second select
users_by_id = User.find_all_by_id(users_ids).group_by{|user| user.id}
cd_users_by_cd_id.each{|cd_id, cd_user_hash|
result_hash[:cd_id] = cd_users_hash.collect{|cd_user| users_by_id[cd_user.user_id]}
}
This is just an ideea, haven't tested :)
FYI: http://railscasts.com/episodes/47-two-many-to-many

Rails, Get a random record when using :group

How do I get a random record when using :group?
#paintings = Painting.all(:group => "user_id", :order => "created_at DESC")
This gives me the latest painting for each user. Now I would like to select a random painting from each user instead of the latest. The order of the paintings should still be the same, so that the user that have been the most active will get his/her random painting displayed first.
painting150 (user1)
painting200 (user2)
painting231 (user3)
Is this possible?
Best regards.
Asbjørn Morell.
This answer is specific to Rails, but since you are using ActiveRecord, I am assuming it should be fine.
unique_paintings = []
#paintings.group_by(&:user_id).each do |user_id, paintings|
unique_paintings << paintings[rand(paintings.size-1)]
end
unique_paintings.sort_by(&:created_at)
The group_by most certainly messes up the created_at sort you did in the query, so I did a sort_by as the last step. You might want to get rid of it in the query since you'll have to do it anyway here.
#painting = #paintings[rand(#paintings.size-1)]
(or paintings.count, dont know the right method yet)
Assuming you have MySQL, you can try:
#paintings = Painting.all(:group => "user_id", :order => "RAND()")
you could do something like this but it will suffer as your number of records grow
#paintings = Painting.find(:all, :order => 'RAND()').map{ |i| i.user_id }.uniq

Resources