I'm about to start building an application in Rails (just started learning Rails, so figured this would be a good, basic exercise) and wanted to solicit some feedback on how to architect my models and associations. The application is designed to host a "game" in which users guess what songs they think will be played at a given concert each show the band plays. So far, I have the following models (with approximate attributes):
User: keep track of active users (user_name, user_password, user_email, etc.)
Submission: keep track of a given user's guess for a given show (date_of_show, guess_1, guess_2, guess_3, user_id)
Show: keep track of each show the band plays (date_of_show, songs_played)
Song: keep track of band's current catalog (song_name)
The relationship between User and Submission is pretty straightforward:
class User < ActiveRecord::Base
has_many :submissions
end
class Submission < ActiveRecord::Base
belongs_to :user
end
When it comes to the relationships between the other pieces though, things become a bit more unclear, so I have the following questions:
Each Submission corresponds to one Show. Does it make sense to say that each Submission belongs_to a Show and each Show has_many Submissions using date_of_show as the key?
Is it necessary to have a Song model at all? In the initial version of the application, it will simply be used as a list to validate guesses (i.e.; populate a select drop down).
If I should keep the Song model, how do I associate that with the other models? A User doesn't have a song, so that one's easy, but each Submission has three Songs and each Show can have a varying number of Songs.
On the point of each Show having a varying number of Songs, is the best way to approach this to store them in an array, ala this post (http://amberonrails.com/storing-arrays-in-a-database-field-w-rails-activerecord/) or is there a better way? Ultimately, I will need to implement some method that loops through each Submission, check guess_1, guess_2, and guess_3 against the songs_played array (or whatever other alternative I use) in the Show model, and produce a score.
For the first steps, design your application like the real life. When you have more experience, you can drop some models, simulate others for some optimization.
In real life, how to design a show ? It's a band playing songs. How to design a band ? It's a set of people (not used here) having songs. How to design a submission ? It's a user thinking a song is played at a time.
Then, you can have theses models :
User (name...)
Song (name, band_id) : belongs_to a band and have many show_songs
Band (name) : has_many songs and has_many shows
Show (band_id) : belongs_to band
ShowSong (show_id, song_id, datetime) : belongs_to show and song
Submission (user_id, song_id, show_song_id) : belongs_to song and show_song
It's not a unique solution, you can design what you want.
I design theses models from some Rails practices:
Don't use date_of_show as a key, use an _id (that's why I insert show_song)
It's necessary to have a model when model have specific methods or must be registered in database
don't use guess_1, guess_2, ... Use an have_many guesses
Related
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.
I'm not sure whether I've accurately reflected my aim in the title, but I'll explain more here.
In my app I have Companies, and Companies has_many Key_Contacts.
Companies also has_many Sales_Opportunities.
I would like the user to be able to select some of the Key_Contacts that belong_to the Company and associate them with a specific Sales_Opportunity. I would also like the user to be able to add a Key_Contact that is not associated with any Sales_Opportunity.
The aim for this is that I can show the specific Key_Contacts that are involved in one Sales_Opportunity view on the Sales_Opportunity page, but not all of them.
Is it as simple as adding a sales_opportunity_id to the Key_Contacts model, but not setting up the "belongs_to" and "has_many" relationships? Or is there a more "official Rails" method to achieve my goal?
If I am reading this right, then all you need to do is add another has_many :key_contacts relation to your SalesOpportunity model (and belongs_to :sales_opportunity in your KeyContacts model). Then relate all contacts belonging to a specific sales opportunity.
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.
In a Rails app I want to combine several models into a single activity feed.
For example, I have Post, Comment, Photo, Status models. Each one belongs to a User, an Institution, and a City. On a User/Institution/City show page I want to display a chronological list of the latest activity.
From reading around and from other questions on SO, there appear to be two schools of thought on this.
Create a polymorphic ActivityFeed model where e.g. a User has_many :posts as: feedable. Then render the ActivityFeed collection on the respective show pages.
Create and render a combined array e.g. #user_feed = #user_photos + #user_comment +..
But I haven't seen much discussing the pros and cons of each approach.
Has any one else attempted something similar? What issues should I be thinking about or planning for in choosing an approach? How well do each perform (especially as the database gets larger)?
My requirements are:
To display all items that belong to the parent model.
To have several different types of parent model (User, Institution, City, etc).
To render a different view partial for each item in the activity feed, depending on its class.
Grateful for any thoughts, personal experiences, or pointers to further information on this. Should I be looking for a third option? I'm particularly interested in the performance of creating a combined array from several queries.
Thanks
Let me add a few more issues to your requirements
ease of display all items belonging to parent model
ease of allowing for different types of parent model
ease of class-dependent template rendering
ease of refactoring shared activity functionality
ease of integrating potential activity control functionality (mark as read, subscribe, follow, share etc)
ease of calculating polymorphic feed, say in reverse chronological order of activities of various types.
ease of calculating aggregated activity count on a parent
For me 5 and 6 and 7 defo tips it in favour of the first option, my code would be:
class Activity
belongs_to :activity, polymorphic: true
belongs_to :user, counter_cache: true
belongs_to :institution, counter_cache: true
belongs_to :city, counter_cache: true
end
your scope for city newsfeed (demonstrating 1,2,6)
city.activities.order('updated_at DESC').limit(20)
note that this is a cheap search on one table only that can already give you enough for a quick list since association loading (would be .includes(:activities)) is not necessary to display something like.
Joe from NYU, New York has posted a comment
Mel from UCL, London has added a new photo
4 is trivial if activity is a separate class and 5 also smooth with standard activity controller. for shared rendering partials like the quicklist views/activities will be a natural place.
7 is easily done with the counter_cache.
as a bonus, by allowing has_many :activities on each type, you could distinguish types of activities, like create, update, read, etc.
hope this helps
I have the app, where user can have one of several different profiles. Some of profile data are always the same (like first and last name, gender etc). Other fields may vary (for example, doctor can have license number and text about himself, while patient can have phone number etc).
I found approach, that fits pretty well, but still have some doubts. The point of my approach looks like this:
User model contains a lot of system-specific data, controlled by Devise and has_one :person
Person model contains common profile data and belongs_to :profile, :polymorphic => true
Doctor/Patient/Admin/etc contains more specific profile data and has_one :person, :as => :profile
With this approach i can simply check in Person model:
def doctor?
self.profile_type == 'Doctor'
end
But there is the few things doesn't give me a rest.
First one is performance. This approach requires a lot of additional joins. For example, for reading doctor's license number, first/last name and email at the same time it will generate 2 additional joins.
Second one is different ids for profile-specific model (i.e. Doctor) and for Person/User models. There will be situations, when user with ID=1 will have Patient relation with different ID, but it would be logical to have same ID for all this associated models.
Maybe you guys will see any more pitfalls in this approach? Is there any better solution for my situation?
You've got four basic patterns you can use here that might work.
Omnirecord
In this model you have all the possible fields in a single record and then use STI to differentiate between profile types. This is the simplest to implement but looks the most messy as few people will have all fields populated. Keep in mind that NULL string fields don't take up a lot of database space, typically one bit per column, so having a lot of them isn't a big deal.
Optional Joins
In this model you create a number of possible linkages to different profile types, like doctor_profile_id linking to a DoctorProfile, patient_profile_id linking to a PatientProfile, and so forth. Since each relationship is spelled out in a specific field, you can even enforce foreign key constraints if you prefer, and indexing is easy. This can come in handy when a single record requires multiple different profiles to be associated with it, as in the case of a patient that's also a doctor.
Polymorphic Join
In this model you link to a specific profile type and profile id using the :polymorphic option, much like you've suggested. Indexing is more complicated and foreign keys are impossible. You're also limited to having one and only one profile. These tend to work as a starting point but may prove to be trouble down the road when you get a doctor + patient requirement.
Key/Value Store
In this model you abandon all efforts to organize things into singular records and instead build an associated ProfileField and ProfileValue table. The ProfileField identifies what fields are available on what kinds of profiles, such as label, allowed values, data type and so forth, while the ProfileValue is used to store specific values for specific profiles.
class User < ActiveRecord::Base
has_many :profile_fields
end
class ProfileField < ActiveRecord::Base
has_many :profile_values
end
class ProfileValue < ActiveRecord::Base
belongs_to :user
belongs_to :profile_field
end
Since this one is wide open you can allow the site administrator to redefine what field are required, add new fields, and so on, without having to make a schema change.