Semantic vs. Functional Interpretation of has_many/belongs_to in Rails - ruby-on-rails

I'm a Rails beginner and I'm having trouble wrapping my head around how I should design my database schema. Here's a quick breakdown of some of my models:
Submission: Contains multiple Guesses.
Guess: Contains one Song.
Song: Contains song-related info, has a many-to-many relationship with Shows.
Show: Contains show-related info, including Songs that were played.
The relationship between the last two models are managed through the Song_Show relationship model. Things get a little trickier when I try and relate the Guess model to the Song model. Semantically, I think of it like each Guess has_one Song and each Song belongs_to many Guesses; however, there is no belongs_to_many relation.
In the classic example that uses the Customer and Order models, the semantic relationship matches the Rails relationship - each Order belongs_to a Customer and a Customer has_many Orders; however, trying to apply that logic to my example doesn't quite work if the proper way to do things is a Song has_many Guesses and each Guess belongs_to a Song. Is the way I'm thinking about these relationships semantically getting in my way of understanding Rails associations?
On a related topic, creating all of these relationships seems to be a hassle, at least for the initial version of this application where both the Guess and Song models seem rather useless, as they each essentially store a single song_name property. One of the critical features of the application is to compare a Submission to a Song and see if any of the song_names from the Submission match those in the Show. Is it a bad idea to simply serialize a list of song_names in both the Submission and Show instances and compare those directly, rather than complicating things with the Song and Guess models?

Don't get too bogged down by the meaning of has_many and belongs_to. At some point in your Rails career, you'll start to see them for what they really mean: which model keeps track of the relationship.
In a has_many relationship, it's the other model that keeps track of the relationship. If a Song has_many Guesses, then the Guess would have the song_id.
In a belongs_to relationship, the class that you're in is the thing that keeps the id. If a Guess belongs_to a Song, then the Guess has the song_id.
In a has_many...through model, it's the joining class that keeps track of everything.
As for your last question, I think you have to figure out how you're going to compare Guesses to Songs. Seems like a Guess might not merit it's own class—could be serialized—but a Song could probably merit its own class. Doing it like this might make it easier to search too:
Song.where(song_name: #submission.guesses) # where guesses is an array.

Related

Accessing relationship tables data in rails (using has_many :through)

It is my first question, but I have yet to find an answer, so I hope it doesn't violate any rule.
I have a problem with a seemingly simple rails issue. I have taken the time to read about relationship models in rails (has_many :through) and came upon this example:
Exemplary model relations
In my model, I have Anthology (phyisicians), Poem(patients), and an anthology_poem relationship model (appointments). In may relationship table, I have a column, order, that indicates the position of a specific poem in a specific anthology.
The question is - How do I address said "order" column? How do I update it/read it? I imagine something like:
book.poems.first.order
which obviously doesn't work.
I'd like to be able to do it without too much hacking, because I fell in love with how simply rails handled the rest of the stuff.
Thanks in advance!
If you want to access your relationship model attribute you should call it on that model:
Appointment.where(physician: physician).pluck(:order)

Creating a has_one association with an object which doesn't belong to anything

I'll admit it right off, I'm new to Rails, but not to programming in general or anything.
I followed Michael Hartl's tutorial and have a decent understanding of how things work in his safe and controlled space, but now that I'm out in the real world I'm having some problems.
The little project I'm working on to learn this stuff is a very simple rails app where a user can add a song to the database and mark it as their "favorite" track. While songs are uploaded by a user, they are not owned by a user, because any other user can pick a previously uploaded song to mark as their "favorite" as well.
So what makes sense to me is for the user model to have
has_one :song
validates :song_id, presence: true
And for the song model to have no association rules, because a song doesn't "belong" to a user, it belongs to the entire app for any user to choose as their favorite.
However when I try this in the rails console, and attempt to do user.song an error is thrown:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: songs.user_id:
SELECT "songs".* FROM "songs" WHERE "songs"."user_id" =
? ORDER BY "songs"."id" ASC LIMIT 1
I can see what it is trying to do, it's interpreting what I say as meaning the songs model has a user_id column to join the users table, but what I want is the opposite. I read the documentation and found that the belongs_to association does the opposite, so I threw that in for the user model instead like this:
belongs_to :song
validates :song_id, presence: true
And this actually works! But... it doesn't make any sense. The user shouldn't belong to anyone, it should have a favorite song. Furthermore, it also doesn't make sense to say the song object has_many users, because users, like songs, aren't really "owned" by anyone, the way a tweet would be owned by a user, lets say.
I mean, if this is the way to do it (making users belong to songs, and having songs have many users) I'm ok with that, it just doesn't really make sense to me, and since I'm new to Rails I figured there's probably a better way of doing this which makes more sense in the code of the models.
Thanks for the help!
The short answer is that this is the Rails way to do it.
The :belongs_to and :has_one predicates are both 1-1 relations, and the distinction is mostly just indications as to on which object the primary key reference resides to link the two objects. The natural choice of which to use often falls out of whether the relation might actually be more like 1-to-many or many-to-1.
:belongs_to means the key reference is local. This is often used when using composition; say if you had shelves in a cabinet, each shelf 'belongs to' a cabinet, and the cabinet would 'has_many' the shelf object. The key is on the shelf, because that is the object of which there are many.
:has_one means the key reference is on the opposing object. It is often used if it might just as well :has_many the opposing object.
In your case, you imagine describing the relationship as the song :has_many admirers, each of which :belongs_to the song (as an admirer, via the 'favorite' relation).
http://guides.rubyonrails.org/association_basics.html gives a much better overview with many examples, including many-many relations.

How to design/model a has many relationship that has a meaningful join table?

I wasn't able to put well into words (in Question title) what I'm trying to do, so in honor of the saying that an image is worth a thousand words; In a nutshell what I'm trying to do is..
Basically, what I have is A Teacher has many Appointments and A Student has many Appointments which roughly translates to:
I'm trying to stay away from using the has_and_belongs_to_many macro, because my appointments model has some meaning(operations), for instance it has a Boolean field: confirmed.
So, I was thinking about using the has_many :through macro, and perhaps using an "Appointable" join table model? What do you guys think?
The Scenario I'm trying to code is simple;
A Student requests an Appointment with a Teacher at certain Date/Time
If Teacher is available (and wants to give lesson at that Date/Time), She confirms the Appointment.
I hope you can tell me how would you approach this problem? Is my assumption of using the has_many :through macro correct?
Thank you!
Both teachers and students could inherit from a single class e.g. Person. Then create an association between Person and Appointments. This way you keep the architecture open so that if in the future you want to add 'Parents' then they could easily be integrated and may participate in appointments.
It may not be completely straightforward how you do the joins with the children classes (Students, Parents, Teachers). It may involve polymorphic relationships which I don't particularly like. You should though get away with a single join table.
In any case, you want to design so that your system can be extended. Some extra work early on will save you a lot of work later.

Rails Order by Contained Objects 2 Levels Deep?

How can I include ordering in an 'order' ActiveRelation call that's more than one level deep?
That is, I understand the answer when it's only one level deep (asked and answered at Rails order by associated data). However, I have a case where the data on which I want to sort is two levels deep.
Specifically, in my schema a SongbookEntry contains a Recording, which contains an Artist and a Song. I want to be able to sort SongbookEntry lists by song title.
I can go one level deep and sort Recordings by song title:
#recordings = Recording.includes(:song).order('songs.title')
...but don't know how to go two levels deep. In addition, it would be great if I could sort on the recording (that is, the song title and the artist name) -- is this possible without descending into SQL?
Thanks for any help,
Keith
If you model the association between SongbookEntry and Song as such:
class SongbookEntry < ActiveRecord::Base
# ...
has_one :song, through: :recording
end
you will be able to access #songbookentry.song and SongbookEntry.joins(:song) using your existing schema.
Edit:
Applying the same idea for Artist, a possible query would be:
SongbookEntry.joins(:song,:artist).order('songs.title','artists.name')
Note that this may not be the most efficient operation (multiple joins involved) even though it looks Rails-ish, so later on you may want to denormalize the tables as Ryan suggested, or find another way to model the data.
I would advise storing the artist name (and possibly the song title too) on the recording itself, so you don't have to "descend into SQL".
Try this
SongbookEntry.includes(:recording=>[:artist,:song]).order('songs.title, artists.name')
You can use joins in place of includes if you don't want to use associated tables fields in views

Objects and Relations in Ruby on Rails

I'm attempting to create a simple web application for some personal development, but I've run into an obstacle, which I'm hoping others will find trivial. I have working classes created, but I'm not sure how define their relationships, or whether their models are even appropriate.
For the sake of argument, I have the following classes already in existence: User, Team, and Athlete
The functionality I'm striving for is for a user to create a team by adding one athlete to the team object. Other users can either add players to the team, or they can add alternatives to an existing athlete on the team roster. Essentially, I want to create some sort of class to wrap an array of athlete objects, we'll call it AthleteArray.
If my understanding is correct, these are the relationships that would be appropriate:
So a Team would have-many AthleteArrays
An AthleteArray would have-many Athletes and would belong-to a Team
Athletes would belong-to a User, and would belong-to a AthleteArray.
Users would have-many athletes, but that would be the extent of their involvement.
Since the AthleteArray class wouldn't have any attributes, is it wise to create it as an ActiveRecord object(would it merely have an ID)? Is there another way to implement this idea(can you define the team class, to have an array of arrays of athlete objects)? I have very little knowledge of RoR, but I thought it would be a good place to start with web development. Any help would be appreciated. Thanks in advance!
Edit: The names of Athletes, Teams, and AthleteArrays are unimportant. Assume that an Athlete is basically a comment, validated against a list of athletes. Duplication is acceptable. If I'm understanding the answers posted, I should basically create an intermediate class which takes the IDs of their parents?
Is response to Omar:
Eventually, I'd like to add a voting system. So after basic team is created, users can individual suggest replacements for a given player, and users can vote up the best choices. For instance, If I have a team of chess_players created:
Bobby Fischer
Garry Kasparov
Vladimir Kramnik
someone might click on Kasparov, and decide that a better athlete would be Deep Blue, so this option would appear nested under Kasparov until it received more votes. This same process would occur for every character, but unlike a comment system, you wouldn't be able to respond to "Deep Blue" and substitute another player, you would simply respond to the position number 2, and suggest another player.
Right, so if I understand the question properly, there will be many possible combinations of athletes for a team, which may or may not use the same athletes?
Something sounds off here, possibly in the naming of your models. Don't quite like the smell of AthleteArrays.
has_many_through might be what you need to access the athletes from your team e.g.
has_many :athletes, :through => :team_permuations # your AthleteArray model
Josh Susser has an old roundup of many to many associations which i guess is what you need, since according to your specification, theoretically it can be possible for an athlete to belong to any number of teams. I suppose the best thing is that you can have automagically populated audit columns (created/updated_at/on) on your many to many associations, which is a nice thing to have.
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
Please comment if you think I have understood the question wrong.
If I understand your need, this is something that can be solved through has_many :through.
basically you can represent this in the database like
athletes:
id
other_fields
teams:
id
other_fields
roster_items:
id
team_id
athlete_id
position
is_active #is currently on the team or just a suggestion
adding_user_id #the user who added this roster item
class Athlete < ActiveRecord::Base
has_many :roster_items
end
class Team < ActiveRecord::Base
has_many :roster_items
has_many :athletes, :through => :roster_items
end
class RosterItem < ActiveRecord::Base
belongs_to :team
belongs_to :athlete
belongs_to :adding_user, :class_name => 'User'
end

Resources