Rails 3 select random follower query efficiency - ruby-on-rails

I have a method that selects 5 random users who are following a certain user, and adds them to an array.
Relationship.find_all_by_followee_id( user.id ).shuffle[0,4].each do |follower|
follower = User.find(follower.user_id)
array.push follower
end
return array
I'm wondering, is this an efficient way of accomplishing this? My main concern is with the find_all_by_followee_id call. This returns a list of all the relationships where the specified user is being followed (this could be in the 100,000s). And then I shuffle that entire list, and then I trim it to the first 5. Is there a more efficient way to do this?

You can try this:
Relationship.find_all_by_followee_id( user.id, :order => 'rand()', :limit => 5 ) do |follower|
follower = User.find(follower.user_id)
array.push follower
end
return array
Btw, this will work with MySql. If you are using PostgreSQL or anything else you may need to change the rand() with any valid random function that your DB supports.
Some minor changes to make it a little more clean:
return Relationship.find_all_by_followee_id( user.id, :order => 'rand()', :limit => 5 ).collect {|follower| User.find(follower.user_id) }
You can also use a join in there in order to prevent the 5 selects but it won't make much difference.
Edit1:
As #mike.surowiec mentioned.
"Just for everyones benefit, translating this to the non-deprecated active record query syntax looks like this:"
Relationship.where(:followee_id => user.id).order( "random()" ).limit( 5 ).collect {|follower| User.find(follower.user_id) }

Related

Help converting Rails 2 Database logic to Rails 3.1/ PostgreSQL

How do I select a single random record for each user, but order the Array by the latest record pr. user.
If Foo uploads a new painting, I would like to select a single random record from foo. This way a user that uploads 10 paintings won't monopolize all the space on the front page, but still get a slot on the top of the page.
This is how I did it with Rails 2.x running on MySQL.
#paintings = Painting.all.reverse
first_paintings = []
#paintings.group_by(&:user_id).each do |user_id, paintings|
first_paintings << paintings[rand(paintings.size-1)]
end
#paintings = (first_paintings + (Painting.all - first_paintings).reverse).paginate(:per_page => 9, :page => params[:page])
The example above generates a lot of SQL query's and is properly badly optimized. How would you pull this off with Rails 3.1 running on PostgreSQL? I have 7000 records..
#paintings = Painting.all.reverse = #paintings = Painting.order("id desc")
If you really want to reverse the order of the the paintings result set I would set up a scope then just use that
Something like
class Painting < ActiveRecord::Base
scope :reversed, order("id desc")
end
Then you can use Painting.reversed anywhere you need it
You have definitely set up a belongs_to association in your Painting model, so I would do:
# painting.rb
default_scope order('id DESC')
# paintings_controller.rb
first_paintings = User.includes(:paintings).collect do |user|
user.paintings.sample
end
#paintings = (first_paintings + Painting.where('id NOT IN (?)', first_paintings)).paginate(:per_page => 9, :page => params[:page])
I think this solution results in the fewest SQL queries, and is very readable. Not tested, but I hope you got the idea.
You could use the dynamic finders:
Painting.order("id desc").find_by_user_id!(user.id)
This is assuming your Paintings table contains a user_id column or some other way to associate users to paintings which it appears you have covered since you're calling user_id in your initial code. This isn't random but using find_all_by_user_id would allow you to call .reverse on the array if you still wanted and find a random painting.

Rails - Find results from two join tables

I have have 3 Tables of data and 2 Join Tables connecting everything. I'm trying to figure out a way to query the results based on the condition that the join table data is the same.
To explain, I have User, Interest, and Event Tables. These tables are linked through an HABTM relationship (which is fine for my needs since I dont need to store any other fields) and joined through two join tables. So i also have a UsersInterests table with (user_id, interest_id) and a EventsInterests table with (event_id, interest_id).
The problem comes when trying to query all the Events where the users interests match the events interests.
I thought it would look something like this...
#events= Event.find(:all, :conditions => [#user.interests = #event.interests])
but I get the error
"undefined method `interests' for nil:NilClass", Is there something wrong with my syntax or my logic?
You're problem is that either #user or #event is undefined. Even if you define them, before executing this statement, the conditions option supplied is invalid, [#user.interests = #event.interests].
This named scope on events should do the trick
class Event < ActiveRecord::Base
...
named_scope :shares_interest_with_user, lambda {|user|
{ :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
"LEFT JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
:conditions => ["ui.user_id = ?", user], :group_by => "events.id"
}
end
#events = Event.shares_interest_with_user(#user)
Given Event <-> Interest <-> User query all the Events where the users interests match the events interests (so the following will find all such Events that this event's interest are also interests of at least one user).
First try, the simplest thing that could work:
#events = []
Interest.all.each do |i|
i.events.each do |e|
#events << e if i.users.any?
end
end
#events.uniq!
Highly inefficient, very resource hungry and cpu intensive. Generates lots of sql queries. But gets the job done.
Second try should incorporate some complicated join, but the more I think about it the more I see how vague your problem is. Be more precise.
Not sure I completely follow what you are trying to do. If you have one user and you want all events that that user also has interest in then something like:
Event.find(:all, :include => [:events_interests], :conditions => ['events_interests.interest_id in (?)', #user.interests.collect(&:id)])
should probably work.

rails - activerecord ... grab first result

I want to grab the most recent entry from a table. If I was just using sql, you could do
Select top 1 * from table ORDER BY EntryDate DESC
I'd like to know if there is a good active record way of doing this.
I could do something like:
table.find(:order => 'EntryDate DESC').first
But it seems like that would grab the entire result set, and then use ruby to select the first result. I'd like ActiveRecord to create sql that only brings across one result.
You need something like:
Model.first(:order => 'EntryDate DESC')
which is shorthand for
Model.find(:first, :order => 'EntryDate DESC')
Take a look at the documentation for first and find for details.
The Rails documentation seems to be pretty subjective in this instance. Note that .first is the same as find(:first, blah...)
From:http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263
"Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args)."
Digging into the ActiveRecord code, at line 1533 of base.rb (as of 9/5/2009), we find:
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
This calls find_every which has the following definition:
def find_every(options)
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
records = find_by_sql(construct_finder_sql(options))
if include_associations.any?
preload_associations(records, include_associations)
end
end
records.each { |record| record.readonly! } if options[:readonly]
records
end
Since it's doing a records.each, I'm not sure if the :limit is just limiting how many records it's returning after the query is run, but it sure looks that way (without digging any further on my own). Seems you should probably just use raw SQL if you're worried about the performance hit on this.
Could just use find_by_sql http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002267
table.find_by_sql "Select top 1 * from table ORDER BY EntryDate DESC"

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

Better Performance on Associations

Right now I have a table called Campaigns that has many Hits, if I call say:
Campaign.find(30).hits
Which takes 4 seconds, or 4213 ms.
If I call this instead:
campaign = Campaign.find(30)
campaign.hits.count
Does it still load all of the hits, then count? Or does it see I am counting and avoids loading all of the hits? (Which is currently 300,000+ rows).
I am trying to figure out a smart way to load/count my hits. I am thinking about adding a method to my Campaign.rb model, like:
def self.total_hits
find :first, :select => 'COUNT(id) as hits', :conditions => ["campaign_id = ?", self.id]
end
I know that query won't load from the hits table, but that is just an example of counting it from a self made query, apposed to Ruby on Rails doing this for me.
Would this memcache query be more effecient? (I have it running, but doesn't seem to be any better/faster/slower, just the same speed.)
def self.hits
Rails.cache.fetch("Campaign_Hits_#{self.campaign_id}", :expires_in => 40) {
find(:first, :select => 'COUNT(id) as hits', :conditions => ["campaign_id = ?", self.campaign_id]).hits
}
end
Any suggestions would be great!
How about:
Campaign.find(30).hits.count
You might also consider adding the following in hit.rb (assuming a one-to-many relationship between campaigns and hits).
belongs_to :campaign, :counter_cache => true
You then need a column in the campaigns table called hits_count. This will avoid hitting hits altogether if you're only getting the count.
You can check the API for the full rundown.
My ActiveRecord might be a little rusty, so forgive me if so, but IIRC Campaign.find(30).hits is at least two separate queries. How does Campaign.find(30, :include => [ :hits ]).hits do? That should perform a single query.

Resources