I have three models: User, RaceWeek, Race.
Current associations:
class User < ActiveRecord::Base
has_many :race_weeks
end
class RaceWeek < ActiveRecord::Base
belongs_to :user
has_many :races
end
class Race < ActiveRecord::Base
belongs_to :race_week
end
So the user_id is a foreign key in RaceWeek and race_week_id is a foreign key in Race.
fastest_time is an attribute of the Race model.
QUESTION: What's the optimal way to retrieve a list of users who have the top X fastest race times?
You can do it like this:
users = User.all(:limit => X, :joins => {:race_weeks => :races}, :order => "reces.fastest_time DESC").uniq
If you have correctly specified has_many :through association, then you could even do it like this:
users = User.all(:limit => X, :joins => :races, :order => "reces.fastest_time DESC").uniq
In this solution, you get what you want with one query, but two joins. And this uniq method is not very good unless you would use small X.
Something like:
races = Race.all(:order => "fastest_time desc", :limit => X, :include => {:race_week => :user})
users = races.map{|race| race.race_week.user}.uniq
Note: didn't test this.
Given your current model the following should work.
race_weeks = RaceWeek.find_by_sql(["SELECT user_id FROM race_weeks JOIN races ON races.race_week_id = race_weeks.id ORDER BY races.fastest_time desc LIMIT ?", X)
users = User.find(race_weeks.collect(&:user_id).uniq)
I know that it requires two look ups but the second lookup should be very fast since you are only looking up X records by their primary key.
Related
I need to collect objects that are all connected through multiple layers of associations, and I don't know who to do so.
I need to get a collection of CustomText based on a string query param.
Basically I need to do a query that will pull a collection of CustomText by name:
#searched_content = params[:search].downcase
#query = CustomText.where("lower(name) like ?", "%#{#searched_content}%")
but then also filters the #query to only search LineItems that have been a part of an approved order. I am using Spree, where Spree::Order has_many Spree::LineItem. Basically, doing something like this (doesn't work at all, but hopefully you'll be able to see what I'm trying to do):
#query = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"}).joins(:custom_texts).where("lower(name) like ?", "%#{#searched_content}%"))
Models:
class CustomText < ActiveRecord::Base
belongs_to :custom_set, :inverse_of => :custom_texts
end
class CustomSet < ActiveRecord::Base
belongs_to :spree_line_item, :class_name => Spree::LineItem, :foreign_key => :spree_line_item_id
has_may :custom_texts, :dependent => :destroy, :inverse_of => :custom_set
end
class LineItem < ActiveRecord::Base
has_many :custom_texts, :through => :custom_sets
has_many :custom_sets, :dependent => :destroy, :foreign_key => :spree_line_item_id
end
Any help would be very appreciated.
Well, this was just a simple syntax error with one too many end parenthesis at the end...stupid spelling error.
#query = Spree::LineItem.joins(:order).where(spree_orders: {state: "complete"}).joins(:custom_texts).where("lower(name) like ?", "%#{#searched_content}%")
I'm having a little bit of a brain problem with what I think would be a simple call:
I've got:
class Channel < ActiveRecord::Base
has_and_belongs_to_many :shows, :join_table => :channels_shows
end
class Show < ActiveRecord::Base
has_and_belongs_to_many :channels, :join_table => :channels_shows
end
A channel has a :position and :hidden in the database (:hidden can be false, or nil if not saved as I had forgotten about defaulting to 0).
A show has :approved (same as :hidden) and of course :created_at.
I want to be able to get Channels that are (:hidden => [nil, false] ) with each channels included Shows where a show is :approved and by created_at, newest first.
I can't figure out if this is a join or an include. The closest I've gotten is this, but this doesn't sort the included shows in the right order:
Channel.order('channels.position').where(:hidden => [nil, false] ).includes(:shows).where(shows:{approved: true})
Still looking at docs and trying things in the irb; feel like it's crazy simple but I'm just not getting it.
To sort the join records, just include that sort in the order clause after your primary sort. Your channels will still have the primary sort order, but when they are equal (ie when comparing the same channel but a different show), it will fall back to sorting by the second order (effectively sorting your included table):
Channel.order('channels.position, shows.created_at').includes(:shows)...
I think you should be able to do something like this:
class Channel < ActiveRecord::Base
has_and_belongs_to_many :shows, :join_table => :channels_shows
has_and_belongs_to_many :active_shows, :class_name => 'Show', :join_table => :channels_shows, :conditions => ["approved = ?", true], :order => "created_at desc"
end
To allow you to go
Channel.order('channels.position').where(:hidden => [nil, false] ).includes(:active_shows)
This is rails 3 syntax by the way.
I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])
I have the following models:
class Person < ActiveRecord::Base
has_many :images
has_one :preference
end
class Image < ActiveRecord::Base
belongs_to :person
end
class Preference < ActiveRecord::Base
belongs_to :person
end
I am trying to fetch all images that are public and at the same time eager load the people who own those images:
Image.find(:all, :conditions => ["images.person_id = ? AND preferences.image_privacy = ?", user.id, PRIVACY_PUBLIC],
:joins => [:person => :user_preference], :include => :person)
It appears Rails does not like the :include (I believe because :person is referenced in 2 models). This is the error I get (which disappears when I drop the :include option):
"ActiveRecord::StatementInvalid: Mysql::Error: Not unique table/alias: 'people'"
I can get around this by writing out the actual JOIN command as a string and passing it into the :include option, but this not Rails-y so I was hoping there's a cleaner way to do this.
Any help would be much appreciated.
Thanks!
It looks like you call it "preferences", and not user_preferences. So you join should be:
:joins => [:person => :preference])
By using JOIN you are actively including the People table, so you shoudn't need to add "include" again. This should work:
Image.find(:all, :conditions => ["images.person_id = ? AND preferences.image_privacy = ?", user.id, PRIVACY_PUBLIC],
:joins => [:person => :user_preference])
It might be the issue with Table Aliasing, rails doc has great details in Table Aliasing
also, post SQL here will be useful too.
You wrote :conditions => ["images.person_id", user.id] and you said that you want to load images and people who owns those images. But it looks like you are loading images that belongs to one person (not to group of people), because you specify only one user.id.
I would do it this way:
Person.find(user.id, :include => [:images, :preference], :conditions => ["preferences.image_privacy = ?", PRIVACY_PUBLIC])
It will load person and his/her images.
Probably I don't understand your problem correctly, because what I think you want to do doesn't seem logic to me.
Instead of using conditions you can try named_scope
I need to do something like this
class User < ActiveRecord::Base
has_many :abuse_reports
end
class AbuseReport < ActiveRecord::Base
belongs_to :abuser, :class_name => 'User', :foreign_key => 'abuser_id'
belongs_to :game
end
class Game < ActiveRecord::Base
has_many :abuse_reports
end
#top_abusers = User.page(params[:page],
:joins => [
'JOIN abuse_reports ON users.id = abuse_reports.abuser_id',
'JOIN games ON games.id = abuse_reports.game_id'
],
:group => 'users.id',
:select => 'users.*, count(distinct games.id) AS game_count, count(abuse_reports.id) as abuse_report_count',
:order => 'game_count DESC, abuse_report_count DESC'
)
This works, but doesn't create objects for AbuseReports or Games - it just returns a pile of rows. When I reference these objects from my view it loads them again. Is there a way to fix this? Or some way to get this behavior without using :joins?
Firstly, you should really use :include instead of :joins
User.find(:all, :include => { :abuse_reports => [ :game ] }, :order => )
or, in your case, try
User.page(params[:page], :include => { :abuse_reports => [ :game ] })
This will perform the join for you and retrieve the records in one shot.
Now, this may retrieve a given game record for you multiple times (if the same game is tied to a user by multiple reports.) If your game record is large, you can reduce the amount of data exchanged between your app and the RDBMS as follows:
class User < ActiveRecord::Base
has_many :abuse_reports
has_many :abused_games, :through => :abuse_reports
end
...
User.find(:all, :include => [ :abuse_reports, :abused_games ])
Finally, you also want to retrieve the counts and sort accordingly. Check out http://railscasts.com/episodes/23 for how to add counter caches into the actual active records (counter caches simplify the SQL and make the RDBMS' life easier and your queries run faster). After you set up the counter caches, you can finally alter the above to do:
User.find(:all, :include => [ :abuse_reports, :abused_games ], :order => 'users.abused_games_count DESC, users.abuse_reports_count DESC')
This will ultimately retrieve your ActiveRecords in one single, simple SQL statement.
The problem you are having is that you use ActiveRecord in way its not "supposed" to be used. By that I mean that you are writing your own sql, which makes AR give up all of its control to you.
If you want AR to handle everything you should try to use it with less of your own SQL in there. It looks like you want to know which user has the highest amount of AbuseReports. Try something like this:
some_user.abuse_reports.count
to get the count of abuse_reports