I am working with Ruby on Rails (specifically the ActiveRecord) and I am trying to decide whether or not it is a good idea to link my models using multiple levels.
class Student < ActiveRecord::Base
has_many :student_sections
has_many :sections, :through => :student_sections
has_many :courses, :through => :sections
end
It seems like this would work, but I don't have a lot of experience in ActiveRecord. Is there any reason not to do this?
This is fine but you should bear in mind that the courses association is effectively only a 'get' association (as opposed to 'get and set'). What i mean by that is that you can say
#student.courses
(after doing neo's fix) to get a list of courses, but you can't do
#student.courses << #course
as rails doesn't have the section info required to make the necessary joins between the student and the course.
you need to add :source attribute
has_many :sections, :through => :student_sections, :source => 'your_source'
Related
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
I've read the guide on associations but I feel like I'm still not completely comprehending so I want to ask a couple of questions just to be sure. Let's say I am making an app that will, among other things, list large cities all over the world. I would plan on having a view that starts at continent level and can be filtered down. So I would start with a Continent model. And then a Country model. Now, within the Continent model I would define an association as has_many :countries. And in the Country model I would use belongs_to :continents. That much I grasp. So my next model would be a model for states / province. Let's just call it Province since that is more common throughout the world. So now I have my Province model, and I would use belongs_to :country. And likewise Countries would have has_many :provinces. My first question is, how do I describe the association between Province and Continent? Has_many through describes associations where both models have many. A Province only has one Continent. Has_one through describes a relationship between objects that have a one to one relationship via a third object. Again, this isn't the case because a Continent will have many Provinces. So that is my primary question.. how to describe relationships that exist in a one to many through context. My second question would be just asking for tips on writing the migrations for this in a situation where I add another layer, say County, later on. But the main problem is just understand how to express the relationships I described. Or if they even need to be expressed.
ETA: If I were to use the has_many_through association, do I necessarily need to create a join table ( continent_province ), or can I simply use the countries table ie has_many :provinces -> through :countries?
Don't get too wound up around a couple of small examples in some doc somewhere. The relationship support is wonderfully flexible. In the end, just give it a try -- I have a Tester application that has all sorts of proof of concepts in it -- that's its purpose.
class Project
# one-to-many
has_many :scenarios
# linking through scenarios
has_many :unittests, :through => :scenarios
# polymorphic relationship, everything can be relation to one or more appls
has_many :appllinks, :as => :applinkable, :dependent => :destroy
has_many :appls, :through => :appllinks, :order => 'name'
blah blah blah
end
class Scenario
# many-to-one
belongs_to :project
# many-to-many
has_many :scenariotests
has_many :unittests, :through => :scenariotests
# polymorphic relationship, everything can be relation to one or more appls
has_many :appllinks, :as => :applinkable, :dependent => :destroy
has_many :appls, :through => :appllinks, :order => 'name'
blah blah blah
end
class Unittest
# many-to-many
has_many :scenariotests
has_many :scenarios, :through => :scenariotests
# polymorphic relationship, everything can be relation to one or more appls
has_many :appllinks, :as => :applinkable, :dependent => :destroy
has_many :appls, :through => :appllinks, :order => 'name'
blah blah blah
end
I have a 'has many through' relationship between two Models:
Task:
has_many :placements
has_many :games, :through => :placements
Game:
has_many :placements
has_many :tasks, :through => :placements
Placements:
belongs_to :task
belongs_to :game
In my controller's index method I want to list only those tasks that have a particular game id.
The solution I came up with uses an array of id's, but I am thinking there must be an easier, however less obvious to me, way of doing this!
#tasks = Task.find(Placement.where(:game_id => current_user.selectedgame).collect(&:task_id))
Any suggestions most welcome please.
Maybe I'm missing something in your question, but...
#tasks = Game.find(current_user.selectedgame).tasks
You probably should use has_and_belongs_to_many.
You can start from the Games object:
Games.find(current_user.selectedgame).tasks
You can use has_and_belongs_to_many relationships instead of has_many :through, but it's not necessary. The Ruby on Rails Guide about Active Record Associations has a section about this.
I set up a public github app (see: https://github.com/greenplastik/testapp to download) to work through a problem I'm having with specifying a type on one side of a many-to-many association between two models, via a join model.
Given Person and Book models and a Book_Person join model, I want to be able to do the following:
#book = Book.first
#book.people # lists people for book
#book.authors # lists author-type people for book
#book.editors # lists editor-type people for book
and
#person = Person.first
#person.books # lists books for people
This app was set up in part using the instructions found through Google. There's a link to those instructions in the README of my testapp.
I tried, as best I could, to remove the inconsistencies and typos. I can't get it to work.
Any help would be appreciated. I've included the sqlite database for easier testing on your end.
I'm sorry i haven't got time to post neither a full solution or a tested one, but this should at least put you on the right track..
The best way to achieve what you're looking for is with Single Table Inheritance used with a polymorphic relationship.
I'd suggest redefining Author and Editor to be subclasses of Person
As in
class Person < ActiveRecord::Base
has_many :book_people
has_many :books, :through => :book_people
end
class Author < Person
end
class Editor < Person
end
class BookPerson < ActiveRecord::Base
belongs_to :person, :polymorphic => true
belongs_to :book
end
class Book < ActiveRecord::Base
has_many :book_people
has_many :people, :through => :book_people
has_many :authors, :through => :book_people, :source => :person, :source_type => "Author"
has_many :editors, :through => :book_people, :source => :person, :source_type => "Editor"
end
However this would be suboptimal if a single person could fill all three roles. There's probably a way around that by using the same STI name for the three of them. But then you'd make it harder to query for all authors.
When building a rails app that allows a User to login and create data, is it best to setup a belongs_to :user association on every single model? For example, let's say a user can create Favorites, Colors and Tags.
And let's say Favorites has_many :tags and Colors also has_many :tags. Is it still important for Tags to belong_to :user assuming the User is the only person who has authority to edit those tags?
And a similar question along the same lines: When updating data in FavoritesController, I've come to the conclusion that you perform CRUD operations by always doing something like current_user.favorites.find(param[:id].update_attributes(param[:favorite]) so that they can definitely only update models that belong to them. Right?
Update Wasn't too happy with any of the answers, as no one really answered my question but instead went after the for-example-only Tags model suggesting better ways to do that. I'm assuming I was right, and models should belong_to :user. I also discovered some great security tips that address my questions here: http://asciicasts.com/episodes/178-seven-security-tips
As you describe the tags it seems that they are more of an aspect, so you can implement them as a polymorphic association. But you should do it many-to-many, as tags can be reused among users and taggable objects. Let's call the join model Tagging, which will be the one that belongs to user if you want to remember who created the tagging.
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :colors, :through => :taggings, :source => :taggable, :source_type => "Color"
has_many :favorites, :through => :taggings, :source => :taggable, :source_type => "Favorite"
end
class Tagging < ActiveRecord::Base
belongs_to :user
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Color < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class Favorite < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class User < ActiveRecord::Base
has_many :favorites
has_many :colors
has_many :taggings
has_many :tags, :through => :taggings
end
As for the Favorite updating, I agree with you: you will mostly work within the scope of a user (most likely the currently logged in user).
It depends on your model. Both cases are valid but I'd discorage making a circular relationships like that. Having a hierarchy is more flexible. For example: User->Favorites->Tags (unless you want to tag users as well)
User.favorites.find(params[:id]).update_attributes(param[:favorite])
is what you mean I guess (syntax). Whoever calls the URL will perform that action. Dont rely on the fact that that URL is visible to one user only (owner of the favorite). You should have checks in place that the currently logged in user is the only one performing actions on the objects that belong to him.
The proposed mechanism sounds a bit too complex for me. I prefer the current_user way. Assume there is a current_user (following the authlogic way) in your authentication system, then simple add a user references (user_id) in every relevant table. Update the current_user for new or update record via a controller filter.
In the models, put relevant belongs_to :users accordingly, put enough has_many in users model if needed.
:has_many and :belongs_to in AR will explains the relationship between models, but not necessarily you have to use them in your models, the associaton between them will be already present in the tables as a foreign key.
But adding :has_many or :belongs_to to your models will give you extra methods to your model
ex:
class User < ActiveRecord::Base
has_many :favorites
#def favorites
# Favorite.find_all_by_user_id(self.id)
# end
end
If you mention has_many it will give a new method in your model called favorites, that method will be invisible (will be present in the AR).
Similarly for any association, if you are planning to use this kind of methods you should use associations in your models.