Rails Modeling Question - Relationships and Primary Keys - ruby-on-rails

I'm working on a rails site that I've inherited and am trying to troubleshooting some sub-optimal model behavior. I have users, songs, and songs_download, each of which is its own model.
Here's the relevant line from the users model:
has_and_belongs_to_many :downloaded_songs, :class_name => 'Song', :join_table => :song_downloads
From the songs model:
has_and_belongs_to_many :downloaded_users, :class_name => 'User', :join_table => :song_downloads
And from the song_downloads model:
belongs_to :user
belongs_to :song
Here's the code to create a new song_download record when a user downloads a song (in the songs controller):
SongDownload.create( :song_id => #song.id,
:user_id => current_user.id,
:download_date => Date.today )
The problem I'm having is that once a user downloads a song, if I try to invoke the downloaded users from the interactive console, by, say, typing the following:
Song.find(<some id>).downloaded_users
I get back the complete record of the user, but the id in the returned objected is the primary key of the SongDownload, not the primary key of the User. All of the other fields are accurate, but the ID is not.
I didn't come up with this modeling scheme and it seems to me that :has_and_belongs_to_many might be more appropriately used with no explicitly modeled SongDownload object, but I'd rather not overhaul the codebase if I can help it. Are there any ways to get back the right user id given the current modeling scheme?
Thanks very much for your time and consideration!
Justin

Has and belongs to relationships are being phased out in favour of has many :through relationships.
On the upside you won't need to change any of your underlying structure, just the relationship declarations in the Song and User models.
class Song < ActiveRecord::Base
has_many :song_downloads
has_many :users, :through => :song_downloads
...
end
class User < ActiveRecord::Base
has_many :song_downloads
has_many :songs, :through => :song_downloads
...
end
Now
Song.find(<some id>).users
Returns an array of User objects which are joined to the selected song through the song_downloads table.

The has_many :through is recommended when the join table has more columns than simply two foreign keys.
class User < ActiveRecord::Base
has_many :song_downloads
has_many :downloaded_songs,
:through => :song_downloads,
:source => :song
end
class Song < ActiveRecord::Base
has_many :song_downloads
has_many :downloaded_users,
:through => :song_downloads,
:source => :user
end
Now you can get a list of users that have downloaded a particular song like so:
Song.find(1).downloaded_users

Related

Related model can be one of two other models

I currently have essentially 3 models at the moment. I have Project, User and Contact.
I'm trying to assign users or contacts as a sort of 'member' to each project. I initially thought that a linking table here would suffice, for example ProjectMembers but i'm currently hitting a brick wall in my thought process when it comes to a project member only being allowed to be either a Contact or a User and whether to handle this via a relationship or by code in the model or the controller by checking which between user_id or contact_id was not null.
I had a look at polymorphic associations which looked promising, but somehow I ended up with the association backwards (Projects were being entered into the ProjectMember table as the type, rather than User or Contact) and confused myself even more.
The final output I would pretty much like would be simply to have the ability to run something like Project.first.project_members and have those members return with the role in that project. It'd be even nicer if I could run User.first.projects/Contact.first.projects and get those too, but that's something I can figure out down the line.
Any pointers would be greatly appreciated.
Polymorphic association is something that you should go for here.
First: Project has a many to many relationship with User and Contact, hence you need a join table.
Second: Since you need to call something like Project.first.project_members, you should have a polymorphic association.
For this scenario, you should create a polymorphic join table.
Let's say you have the polymorphic table ProjectMember with memberable_id, memberable_type and project_id (well I have a hang of railcasts).
class ProjectMember < ActiveRecord::Base
belongs_to :memberable, :polymorphic => true
belongs_to :project
end
and then in your User model,
class User < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in Contact model
class Member < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in your Project model
class Project < ActiveRecord::Base
has_many :users, :through => :project_members, :source => :memberable, :source_type => 'User'
has_many :contacts, :through => :project_members, :source => :memberable, :source_type => 'Contact'
has_many :project_members
end
Now you can call your desired associations, Project.first.project_members, User.first.projects or Contact.first.projects. Hope this helps.
Thanks

advanced (nexted) has_many :through queries in Ruby on Rails (double-JOIN)

I am having a somewhat too nested database layout, however, I seem to need it. That is, Our website visitors may each have a single account with maintaining multiple users (think of identities) within.
Now they may create tickets, which are grouped by ticket sections, and we have ticket manager (operator) to process the incoming tickets.
Not every ticket manager may see every ticket but only those this manager is a member of the given ticket section for.
Now, I am totally fine in querying via raw SQL statements, but I failed to verbalizing those two special queries the Rails way.
Here is the (abstract) model:
# account system
class Account < ActiveRecord::Base
has_many :users
has_many :tickets, :through => :users
has_many :managing_ticket_sections, ... # TicketSection-collection this account (any of its users) is operate for
has_many :managing_tickets, ... # Ticket-collection this account (any of its users) may operate on
end
class User < ActiveRecord::Base
belongs_to :account
has_many :tickets
has_many :managing_ticket_sections, ... # TicketSection-collection this user is operate for
has_many :managing_tickets, ... # Ticket-collection this user may operate on
end
# ticket system
class Ticket < ActiveRecord::Base
belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User"
belongs_to :section, :class_name => "TicketSection"
end
class TicketSection < ActiveRecord::Base
has_many :tickets
has_many :operators
end
class TicketSectionManager < ActiveRecord::Base
belongs_to :manager, :class_name => "User"
belongs_to :section
end
I am aware of basic has_many :through-constructs, however, here, I am accessing more than three tables to get the tickets.
Something that actually works for in the User's model is:
class User < ActiveRecord::Base
has_many :managing_relations, :class_name => "TicketSectionManager" # XXX some kind of helper, for the two below
has_many :managing_sections, :class_name => "TicketSection", :through => :managing_relations, :source => :section
has_many :managing_tickets, :class_name => "Ticket", :through => :managing_relations, :source => :section
end
Here I am using a helper relation (managing_relations), which is absolutely never used except by the two has_many relations below.
I were not able to describe a User.managing_sections nor User.managing_tickets relation without this helper, which is, where I need an advice for.
Secondly, the customer is to have a look at all of the tickets he can manage on any User (think of an identity) he has logged in, so what I need, is a way to collect all tickets (/sections) this Account is permitted to manage (identified by being a member of the corresponding TicketSection)
Here I even were not able to express this relation the ruby way, and I had to work around it by the following:
class Account
def managing_tickets
Ticket.find_by_sql("SELECT t.* FROM tickets AS t
INNER JOIN ticket_section_managers AS m ON m.section_id = t.section_id
INNER JOIN users AS u ON u.id = m.user_id
WHERE u.account_id = #{id}")
end
end
I'd appreciate any kind of advice, and
many thanks in advance,
Christian Parpart.

Rails model advice

I need some suggests for the model relationship that I'm going to develop:
I want to create a research table that keep track of all the reasearches performed inside the platform.
In the platform I'm developing, an user can search for other users.
In order to do it, I thought to create 3 fields in the research table: Performer (user_id that submit the research) Research_string (The string that the performer submit) and Results (that is one or more user).
Of course the relationship among user and research tables will be many to many, but note that the research table has 2 different column that involves the user_id (performer and results) so how can I specify to rails ? I thought something like that :
class User < ActiveRecord::Base
has_many :performed_research, :through => :research_table
class Research < ActiveRecord::Base
has_many :users
But how can I specify that the has_many users in the Research tables implies two different relations depending by the column ?
Tnx
EDITED: my solution
Your solution is not correct, because the user has only the research performed and not the research where he is resulted. I made another solution, that it's not the best of clear but it works, I would like to have your judge:
class Research < ActiveRecord::Base
belongs_to :searcher, :class_name => 'User', :foreign_key=> 'submitter_id'
has_many :found_users, :through=>:user_researches,:source=>:user
#It is necessary to let the association work without class_name it would look for
userResearch class
has_many :user_researches, :class_name =>'User_research'
end
class User_research < ActiveRecord::Base
belongs_to :user
belongs_to :research
end
class User < ActiveRecord::Base
# It returns a list of research performed by the user
has_many :researches, :foreign_key => 'submitter_id'
# It is necessary to let the searcher relationship works
has_many :user_researches, :class_name =>'User_research'
#==> Searcher will return an Array of research where the user's skill has been researched
has_many :follower_researches, :through => :user_researches, :source=>:research
end
I say that it;s not the best because the follower_research of the user model, show an array of research when he has been results ... and not an Array of submitter that searching him, so to obtain them, I have to scroll the array of research and then take the searcher field ... Are you able to perform an improvement (hopefully less complex than this)
Do you mean something like this?
class User < ActiveRecord::Base
has_many :research_entries
class ResearchEntry < ActiveRecord::Base
has_one :performer, :class_name => 'User'
has_and_belongs_to_many :resulting_users, :class_name => 'User'
Update: I changed this to use the has_and_belongs_to_many relationship which will allow you to use a join table to connect many users into "resulting_users" field of the ResearchEntry
Something like this:
class User < ActiveRecord::Base
has_many :submitted_researches, :class_name => 'Research', :foreign_key => 'submitter_id'
has_manu :researches, :through => :performed_researches
class Research < ActiveRecord::Base
belongs_to :submitter, :class_name => 'User'
has_many :researchers, :through => :performed_researches, :class_name => 'User'
class PerformedResearch < ActiveRecord::Base
belongs_to :user
belongs_to :research
See: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Update: I think I misread something. What is results? and how is it related to users?
Update 2: Still not sure I understand but here's another stab
I originally thought you were doing something related to academic research and the Research model was for research papers with the performer being scientists that did the research.
From re-reading your question I'm now thinking you have a rails application with some users and a search feature that lets a user search for other users and for each search you are trying to keep track of which user did the search and which users they found.
Let me know if any of these assumptions are wrong.
Based on that:
class User < ActiveRecord::Base
has_many :searches #all the searches this user performed
class Search < ActiveRecord::Base
belongs_to :searcher, :class_name => 'User' # the user who performed the search
has_many :found_users, :though => :user_search_results, :foreign_key => 'user_id' # using foreign_key because I want to change the name to 'found_users'
#alternatively if you don't want to use foreign_key use the line bellow instead
#has_many :users, :thorough => :user_search_results
class UserSearchResult < ActiveRecord::Base
belongs_to :user
belongs_to :search

question about using the polymorphic association in rails

How guys
I'm new to rails, here's my code:
class Video < ActiveRecord::Base
belongs_to :videoable, :polymorphic => true
end
class Drummer < ActiveRecord::Base
has_many :videos,:as => :videoable
end
class Cymbal < ActiveRecord::Base
has_many :videos, :as => :videoable
end
From this point I can use the drummer.videos to get all the video that belong to drummer,
But I can't use video.drummer to get who is the video belongs to.
of course I can use video.where(:videoable_id => '1', :videoable_type => 'drummer') to get the to find the exact drummer record, but I think there must be a elegant to do that in rails, right?
and one more question, I want to improve this association, video and drummer, video and cymbal should be many-to-many, sometime there are more than 1 drummer or 1 cymbal in one video, so it makes sense do it this way. how can I do this?
If it's always a one-to-many relationship that you want (a video can at most have one drummer), you can add these lines to your Video model:
belongs_to :drummer, :class_name => "Drummer", :foreign_key => "videoable_id"
belongs_to :cymbal, :class_name => "Cymbal", :foreign_key => "videoable_id"
Rails will figure out what class the foreign key maps to and fetch the correct entry.
You might be looking for video.videoable and many-to-many join tables.
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/154-polymorphic-association
If your Drummer and Cymbal models are similar, you might consider using STI instead of polymorphism. Define a new model with at type column to act as a parent, and add the has_many association, then add the child models:
class Subject < ActiveRecord::Base
has_many :subject_videos, :dependent => :destroy
has_many :videos, :through => :subject_videos
end
class Drummer < Subject
end
class Cymbal < Subject
end
Add a SubjectVideo model with foreign keys subject_id and video_id. Then make your Video model associate through it:
class Video < ActiveRecord::Base
has_many :subject_videos, :dependent => :destroy
has_many :subjects, :through => :subject_videos
end
Now you have a many-to-many association.
d = Drummer.create
d.videos # []
d.videos.create(:name=>"Stompin' at the Savoy")
v = Video.find_by_name("Stompin' at the Savoy")
v.subjects # [Drummer]
The primary drawback of this approach is that Drummer and Cymbal are now stored in the same table, which can be undesirable if they share few columns.
If still need a many-to-many relationship using polymorphism, also take a look at has_many_polymorphs.

has_many association with non-traditional data model

I'm struggling with a has_many association. I have a diary application. The model players are as follows:
User
UserFriend
UserFoodProfile
I want to be able to get at all the foods that a user's friends have eaten. So, I want to be able to get: current_user.friends.profiles
I've setup the associations properly so far so that I'm able to access current_user.friends, but now I want to be able to get all the friend's entries as well over the last 30 days.
Here are my models
class User < ActiveRecord::Base
cattr_reader :per_page
##per_page = 20
has_many :user_food_profiles
has_many :preferred_profiles
has_many :food_profiles, :through => :user_food_profiles
has_many :weight_entries
has_one :notification
has_many :user_friends
has_many :friendships, :class_name => "UserFriend", :foreign_key => "friend_id"
has_many :friends, :through => :user_friends
class UserFriend < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
class UserFoodProfile < ActiveRecord::Base
belongs_to :user
belongs_to :food_profile
belongs_to :post
The UserFriend model is setup the following way:
id
user_id
friend_id
friend_name
I want to connect to user_food_profiles from friend so that I can get a user's friend's current user_food_profiles as "entries" but everything I've tried hasn't worked. How would I setup this association?
Tried to do:
UserFriend: has_many :user_food_profiles, :as => 'entries'
UserFoodProfile: belongs_to :friend, :foreign_key => 'friend_id'
Any ideas on how to make this work? Tempted to create a custom finder_sql but I'm sure this can work with associations.
Isn't a "friend" just another user that's in the database?
Let your UserFriend be a many_to_many relationship (either with "has_and_belongs_to_many" or "has_many :through"): each user can have several users as friends.
You can then link those user_ids (which could be in the many_to_many table called 'friend_id' if you like) to their foodprofile without a problem, since it is using the same link as user.foodprofile .
This is the line I see being the problem:
class User < ActiveRecord::Base
# <snip/>
has_many :friendships,
:class_name => "UserFriend",
:foreign_key => "friend_id"
I'm assuming that you're using a join table here called user_friend. That would mean that the foreign key there should be "user_id".
Now, unless you're going to store extra metadata in that UserFriend model, it's not required — you can get away with a self-referential has_and_belongs_to_many relationship like so:
has_and_belongs_to_many :friends,
:class_name => "User",
:join_table => "user_friends",
:foreign_key => "user_id",
:association_foreign_key => "friend_id"
Doing this, all you have to do is user.friends.profiles quite easily.
Now, if the relationship is bi-directional it gets a bit more complex, but I feel like this should at least get you started along the way.

Resources