I am fairly new to Rails and understand the basics of joins and associations but I am having a challenge with how to best use rails for some general queries. There is a lot of documentation I have reviewed but still not clear on best approach for this query. The models are as follows
User:
has_many :accounts
Account:
belongs_to :user
belongs_to :address, :class_name => "Location", :foreign_key => 'address_id'
Address:
belongs_to :account (This is really a many to one relationship where an address,
can belong to more than one account)
The query is to find all account addresses for the user. A standard sql query would look something like this.
SELECT Users.ID AS Users_ID, Users.Username, Account.acct_name, Address.*
FROM Address INNER JOIN (Users INNER JOIN Account ON Users.ID = Account.user_id)
ON Address.ID = Account.address_id
WHERE (((Users.ID)=2));
There seems to be a lot of power in rails to do this without a direct sql query. What is the best approach to doing this and are the models correct? Is the foreign key representation in the correct model.
Thank you in advance for your assistance.
You might be looking for a has_many :through association.
class User < AR::B
has_many :accounts
has_many :addresses, through: :accounts
end
You can then query all addresses of the user with #user.addresses.
If you want to take a look at the SQL query that ActiveRecord generates for your query, you may call .to_sql on a query: #user.addresses.to_sql.
Edit: Regarding the rest of your associations setup, you'll need to rework how Address and Account are joined. One approach might be this one.
class Account < AR::B
belongs_to :user
belongs_to :address # the accounts table should have an address_id integer column
end
class Address < AR::B
has_many :accounts
end
For more info on the options you can pass to the associations, see the Rails API Docs.
First, based on what you've described, the Address class should probably have the relationship has_many :accounts rather than belongs_to :account. The general rule of thumb is to use belongs_to when there's a foreign key in that model's table for the belongs_to table. But for this query that really doesn't matter, as you're starting from users.
Something like this should be close to what you want:
User.where(:id => 2).includes(:accounts => :addresses)
See the Rails guide for more information. The squeel gem is also useful for even more advanced querying capabilities.
Related
I'm trying to list the model instances that do not have the association with another model created yet.
Here is how my models are related:
Ticket.rb:
has_one :purchase
has_one :user, through: :purchase
User.rb:
has_many :purchases
has_many :tickets, through: :purchases
Purchase.rb:
belongs_to :ticket
belongs_to :user
I have an SQL query but have troubles when translating it to rails:
SELECT id FROM tickets
EXCEPT
SELECT ticket_id FROM purchases;
It works great as it returns all ids of the tickets that are not purchased yet.
I've tried this:
Ticket.joins('LEFT JOIN ON tickets.id = purchases.ticket_id').where(purchases: {ticket_id: nil})
but it seems not to be the right direction.
If you're just trying to get the list of Ticket records with no associated purchases, use .includes instead. In my experience a join will fail with no associated records, and this will keep you from needing to write any actual SQL.
Ticket.includes(:purchase).where(purchases: { ticket_id: nil} )
The generated SQL query is a bit more difficult to read as a human, but I've used it several times and not seen any real difference in performance.
I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.
lets say I have the Users table, and the Team table.
In Rails, I know how to link the user_id column in the Team table to the Users table. But what if I have a second column I also want to link to the user's table, such as user_id2 (this essentially creates an order in the team table)?
Is there a solution, or something I don't know about to do what I'm trying? I also don't think the "has_many" is what I'm looking for, because user_id might be the team manager, and user_id2 might be the team captain, i.e. they have different roles affiliated with them, and order is important.
Thanks!
Edit: for my purposes, I also wouldn't need more than these two user relations. (i.e. cases for three wont be relevant)
You may want to look into STI (look for Single Table Inheritance on that page) or Polymorphic Associations. Either would allow you to express your intent a bit more clearly, although there isn't enough information in your question for me to puzzle out which would fit best.
Give those a read and see whether they accomplish what you want.
First here is a way to do this in one direction (Team -> User), but it wouldn't work for the reverse direction, and there's a better option I'll get into afterwards. The first one assumes you have columns named manager_id and captain_id on the teams table.
class User < ActiveRecord::Base
end
class Team < ActiveRecord::Base
belongs_to :manager, :class_name => ::User
belongs_to :captain, :class_name => ::User
end
However, I'd be surprised if a Team only consisted of two Users (the manager and captain) - it's more likely that you'd want a join table to track all of the users' team memberships. That join table (called team_memberships in this example) could have a role column that holds the manager/captain info, as well as any other data you have. This way is a lot more flexible, and offers additional benefits, like being able to track historical team data if team members change over time, which they will.
class Team < ActiveRecord::Base
has_many :team_memberships
has_many :users, :through => :team_memberships
def captain
team_memberships.captain.first
end
def manager
team_memberships.manager.first
end
end
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
# You'll need some database-level UNIQUE INDEXes here to make sure
# you don't get multiple captains / managers per team, and also
# some validations to help with error messages.
named_scope :captain, :conditions => {:role => "captain"}
named_scope :manager, :conditions => {:role => "manager"}
end
class User < ActiveRecord::Base
# depending on the rules about teams, maybe these should be has_many...
has_one :team_membership
has_one :team, :through => :team_memberships
end
Check out http://guides.rubyonrails.org/association_basics.html for more details.
I have this:
class User < ActiveRecord::Base
has_many :serials
has_many :sites, :through => :series
end
class Serial < ActiveRecord::Base
belongs_to :user
belongs_to :site
has_many :episodes
end
class Site < ActiveRecord::Base
has_many :serials
has_many :users, :through => :serials
end
class Episode < ActiveRecord::Base
belongs_to :serial
end
I would like to do some operations on User.serials.episodes but I know this would mean all sorts of clever tricks. I could in theory just put all the episode data into serial (denormalize) and then group_by Site when needed.
If I have a lot of episodes that I need to query on would this be a bad idea?
thanks
I wouldn't bother denormalizing.
If you need to look at counts, you can check out counter_cache on the relationship to save querying for that.
Do you have proper indexes on your foreign keys? If so, pulling the data from one extra join shouldn't be that big of a deal, but you might need to drop down to SQL to get all the results in one query without iterating over .serials:
User.serials.collect { |s| s.episodes }.uniq # ack! this could be bad
It really depends on the scale you are needing out of this application. If the app isn't going to need to serve tons and tons of people then go for it. If you are getting a lot of benefit from the active record associations then go ahead and use them. As your application scales you may find yourself replacing specific instances of the association use with a more direct approach to handle your traffic load though.
I have a many-to-many relationship set up through a join model. Essentially, I allow people to express interests in activities.
class Activity < ActiveRecord::Base
has_many :personal_interests
has_many :people, :through => :personal_interests
end
class Person < ActiveRecord::Base
has_many :personal_interests
has_many :activities, :through => :personal_interests
end
class PersonalInterest < ActiveRecord::Base
belongs_to :person
belongs_to :activity
end
I now want to find out: in which activities has a particular user not expressed interest? This must include activities that have other people interested as well as activities with exactly zero people interested.
A successful (but inefficent) method were two separate queries:
(Activity.all - this_person.interests).first
How can I neatly express this query in ActiveRecord? Is there a (reliable, well-kept) plugin that abstracts the queries?
I think the easiest way will be to just use an SQL where clause fragment via the :conditions parameter.
For example:
Activity.all(:conditions => ['not exists (select 1 from personal_interests where person_id = ? and activity_id = activities.id)', this_person.id])
Totally untested, and probably doesn't work exactly right, but you get the idea.