I'm still pretty inexperienced with Rails' activerecord and I'm struggling to find a good way to match up multiple records that are associated with multiple other records.
I'm running a league with multiple seasons and teams. I want to find all teams that are in the currently active seasons. So...multiple seasons and multiple teams.
The models:
class Season < ActiveRecord::Base
has_many :team_seasons, :dependent => :delete_all
has_many :teams, :through => :team_seasons
end
class Team < ActiveRecord::Base
has_many :team_seasons, :dependent => :delete_all
has_many :seasons, :through => :team_seasons
end
I am thinking it should be something like #teams = Team.includes(:seasons).where(:active => true) but the sql this returns is SELECTteams.* FROMteamsWHEREteams.active= 1 which is just checking whether the team is active and not checking the seasons at all. Plus that query takes a LONG time to run.
So...is there a good way to do this? I know I could just make a manual sql statement but I really would like to be able to use and learn the activerecord way to do this.
Thanks in advance!
This #teams = Team.includes(:seasons).where("seasons.active = true") did the trick.
Related
I am working with a has_many through for the first time, and despite a lot of reading here and in the guide I am not understanding the correct way to access attributes on the through table. My tables are the same as this example from another post.
class Product < ActiveRecord::Base
has_many :collaborators
has_many :users, :through => :collaborators
end
class User < ActiveRecord::Base
has_many :collaborators
has_many :products, :through => :collaborators
end
class Collaborator < ActiveRecord::Base
belongs_to :product
belongs_to :user
end
Assuming that the collaborators table has additional attributes, say hours_spent, what is the correct way to find the hours_spent from the collaborator table for a particular user and product?
When I have found my users via the product, and am iterating over them as in
#product.users.each do |user|
This seems to work
user.collaborator[0].hours_spent
I get the correct value, but since there should only be one collaborator record for each User/Product pair, the index is throwing me off, making me think I’m doing something wrong.
Thank you for reading!
EDIT
Perhaps I am not getting the has_many through concept. Maybe a MySQL example would help.
What I was thinking is that if I did
SELECT * FROM collaborators where user_id = 1;
I would expect a set (zero or more) as the result. Similarly
SELECT * FROM collaborators where product_id = 1;
would also give me a set, but
SELECT * FROM collaborators where user_id = 1 and product_id = 1;
would give at most 1 row.
If I am understanding properly, all 3 queries return a set. So I guess I need some kind of uniqueness constraint, but that would have to be a compound key of sorts, on both of the belongs to keys. Is that even possible? Is there a structure that better models this?
Thanks so much for the quick and helpful responses!
There may be a single database row per pair, but when considering a single user, that user can be associated to many products, so a user can have many rows in the collaborators table. Similarly, when considering a single product, that product can be associated to many users, so a product can have many rows in the collaborators table.
Also, instead of using user.collaborators[0].hours_spent, use user.collaborators.first.try(:hours_spent) (which may return null), if you only want the first collaborator's hours spent.
If a single user can only have one single product and a single product can only have a single user, then switch the has_many's to has_one's for everything.
Update: The preceding is the answer to the original question which has since been clarified via comments. See comments for detail and see comments on other answer by Peter.
Perhaps you should use has_and_belongs_to_many. If your Collaborator is used only to make link between User and Product without having more fields.
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
The beetween migration would be:
class CreateUsersProducts < ActiveRecord::Migration
def change
create_table "users_products", :id => false do |t|
t.integer :user_id
t.integer :product_id
end
end
end
After implementing this, what I found was that I think I had the correct relationships setup, I had to use the has_many :though as users could have many products, and it needed to be :through because there are additional attributes on the collaborator table. The sticking point was how to get there to only be a single Collaborator record for each user/product pair, and then how do I guarantee I got it. And to this point the answer I've found is it has to be done in code.
To make sure there is only a single record for each pair, I used
class Collaborator < ActiveRecord::Base
validates :product_id, :presence => true, :uniqueness => {:scope => [:user_id], :message => "This is a duplicate join"}
And then to make doubly sure I'm finding the right record, I have a scope
scope :collaboration_instance, lambda {|p_id, u_id| where("collaborations.product_id = ? && collaborations.user_id = ?", p_id, u_id)}
If someone has a more elegant solution, or just wants to improve this one, please post and I will change yours to the selected answer.
I'm new to ORM in general and I'm having trouble getting Rails to generate ok SQL. I have two tables, messages and users. Basically, I want a list of the messages with the user_name of the senders tacked on.
It would be great if:
I only got that one column from the user table (the user table has lots of columns that don't need to get selected)
It used a LEFT JOIN (I want messages even if the users are gone for some reason, and I don't want to slow things down with an INNER JOIN)
I don't need to mention the names of the foriegn keys in this code (I should only have to set those up in the models and "NOT REPEAT MYSELF", right?)
I have these models set up:
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => "User", :foreign_key => "from_user_id"
belongs_to :recipient, :class_name => "User", :foreign_key => "to_user_id"
end
class User < ActiveRecord::Base
has_many :sent_messages, :class_name => "Message", :foreign_key => "from_user_id"
has_many :received_messages, :class_name => "Message", :foreign_key => "to_user_id"
end
I really WANT to like ORM, but it seems like it's so easy to get to the point where you just have to write the queries yourself.
Perhaps something like this will do the trick (in Message model):
default_scope select("messages.*, users.name as user_name").joins(:user).includes(:user)
The thing is, that unless this join works as expected, you will need to specify the joins() with an SQL snippet, and then the includes() will probably NOT utilize this join, and rather do N+1 queries in total.
I have a pretty complicated set of models and I'm trying to figure out how to set it all up so that rails will generate chained queries (though I'm not sure it is possible). So my question ends up being: how do I chain these together OR how do I properly paginate this type of function?
First, the high overview.
It is a "social network" and I have users who can be students or teachers. Students and teachers can both follow each other. Both can also do things on the site that result in activity_items. A students can belong to a teacher. Therefore, if a student is following a teacher, they should also see the activity_items of the students under that teacher.
And that is where it gets complicated. I am trying to figure out how to display an activity feed.
Now the models:
model ActivityItem < ActiveRecord::Base
belongs_to :profile, :polymorphic => :true
attr_accessor :posted_by_user
end
model Following < ActiveRecord::Base
belongs_to :profile, :polymorphic => true
belongs_to :following, :polymorphic => true
end
model Student < ActiveRecord::Base
has_many :following_relationships, :as => :profile, :class_name => "Following"
has_many :follower_relationships, :as => :following, :class_name => "Following"
# getting the actual student/teacher model for each relationship
def following
self.following_relationships.reset
following_relationships.collect(&:following)
end
def activity_feed
all = following
all.inject([]) do |result,profile|
result | profile.activity_items
end
end
end
model Teacher < ActiveRecord::Base
[same as student, really it is in an Extension]
end
So the method I'm most concerned about is activity_feed. Without running the large set of queries every time, I'm not sure how to paginate the data. I have a few more methods that go through each activity_item and set posted_by_user to true or false to let me know if it is an activity_item created by someone they are following directly or indirectly (through the teacher).
How can I modify this so that I'm able to either
(A) get a single (or two or three) queries so that I can do a pagination method on it.
(B) how would I properly cache / paginate a large data set like this?
I have a simple question, but can't seem to find any solution, though I have found things that are similar, but just not exactly what I am looking for.
I have an application where a User has many Assets through the class UserAsset. I want to be able to do current_user.user_assets , but I only want to return records that have an Asset with a specified field value of "active".
This post is similar but I need to use the main model not the join model as a filter.
class UserAsset < ActiveRecord::Base
belongs_to :asset
belongs_to :user
end
class Asset < ActiveRecord::Base
has_many :user_assets
has_many :users, :through => :user_assets
end
class User < ActiveRecord::Base
has_many :user_assets
has_many :assets, :through => :user_assets
end
I tried setting the default scope on Asset, and also some conditions on the has many (user_assets) relationship, but rails is failing to consider the join on the Assets table. ie Unknown column 'asset.live' in 'where clause'. Trying to achieve the following:
#active_user_assets = current_user.user_assets #only where assets.active = true
So how do I use conditions or scopes to achieve this? I need the user_asset object because it contains info about the relationship that is relevant.
Thanks in advance!
You want current_user.assets, then your scopes should work.
Oh, but you want the user_assets. Hmm. I think you need the :include clause to find() but where to put it, I can't be arsed to think of right now.
Perhaps
current_user.user_assets.find(:all, :include => :assets).where('asset.live=?', true)
(I'm not on Rails 3 yet, so that's going to be mangled)
Are you using :through when you really want a HABTM?
Let's say I have a many-to-many relationship between users and group. A user can be a member of a group, or his application can still be pending.
class User < ActiveRecord::Base
has_many :applications
has_many :groups, :through => :applications
end
class Group < ActiveRecord::Base
has_many :applications
has_many :users, :through => :applications
end
class Application < ActiveRecord::Base
belongs_to :user
belongs_to :group
attr_accessible :pending # boolean : is the application still pending or not
end
I want to add a scope to my Group class, to select the groups who have more than 10 non-pending users.
I can get those members like this
Group.joins(:applications).where('applications.pending = ?', false)
But I didn't find enough resources to make the scope which counts the numbers of results of this query, and returns the groups where this number is greater than 10
If you have a solution, or ressources on this topic, this would help me greatly
I didn't spec out your models in my own console, but wouldn't something along these lines work?
Group.joins(:applications).group('groups.id').having('COUNT(*) > 10').where(["applications.pending = ?", false])
Basically, once you include GROUP BY conditions in the underlying SQL, you can use HAVING on the aggregate results. The WHERE simply limits it to those results that you're looking for. You can achieve the same result using straight SQL:
Group.find_by_sql(["SELECT * FROM groups INNER JOIN applications ON applications.group_id = groups.id WHERE applications.pending = ? GROUP BY groups.id HAVING COUNT(*) > ?", false, 10])
Kind of a huge query to include in a named scope. You might want to consider breaking this into pieces - it might save lots of headaches later...