Part 4 – Setting Up a Polymorphic Association Among Users, Folders, Tasks
If you recall the small discussion on polymorphic associations in Part 1a, you’ll remember that we wanted the ability to create a task and have the option to not associate it with a specific folder. So we needed to add polymorphic assocations to our rails application.
Sidebar: Using Git
I’ll be using the git subversion control system throughout this tutorial, but you don’t have to. Michael Hartl talks uses it throughout the Rails tutorial (available for free online) or you can search for it.
git checkout -b part-4-polymorphic-models
Creating a Single Task Model
To think about how to setup the polymorphic association, we ask “what do the other models that the task model relates to have in common?” The answer is that they can all have tasks. So we create a task model with 2 extra fields needed for the polymorphic association to work — namely, taskableid and taskabletype.
rails g model task description:text duedate:string taskable_id:integer taskable_type:string
Since taskableid and taskabletype will be queried together frequently, we add an index on them as follows.
add_index :tasks, [:taskable_id, :taskable_type]
This index will help us query our database more efficiently and get the information we need returned to us in less time.
Next we need to modify the Task model with a belongs_to line as follows:
belongs_to :taskable, polymorphic: true
So your task database migration file should look something like this:
class CreateTasks < ActiveRecord::Migration def change create_table :tasks do |t| t.text :description t.string :duedate t.integer :taskable_id t.string :taskable_type</code> t.timestamps end add_index :tasks, [:taskable_id, :taskable_type] end end
And your task model file should look like this:
class Task < ActiveRecord::Base attr_accessible :description, :duedate belongs_to :taskable, polymorphic: true end
Please note I’ve removed the :taskableid and :taskabletype attributes as we don’t need them to be accessible through mass assignment.
Modifying the Todouser Model for Polymorphic Associations
We add the following line to the Todouser model:
has_many :tasks, as: :commentable
This helps set the other side of the polymorphic association. We can now use this just like any other has_many association in Rails.
Creating the Folder Model
Finally, we create the folder model as follows.
rails g model folder name:string todouser_id:integer
Note that when generating the model included a “todouser_id” attribute. This will be a foreign key that tells Rails that a folder belongs to a user (in our specific case, an instance of the Todouser model).
Sidebar: what is a foreign key?
A foreign key is a field in a database relational table that matches a candidate key in another table. For example, in our case a todouser will be able to create many folders. The intention is that each folder that the todouser creates must be associated with a todouser that is in our todouser database table. To accomplish this, we create a foreign key in the folders table and have it relate to the primary key of the todouser table. In this way, we know which folder belongs to which todouser – those that have the same matching numerical keys.
Setting Up the Polymorphic Association With the Folder Model
Setting up the polymorphic association via the “taskable” interface is the same as the Todouser model. We add the following line to the Folder model:
has_many :tasks, as: :commentable
This helps set the other side of the polymorphic association. We can now use this just like any other has_many association in Rails. The upshot is that now folders can have many tasks, and so can users.
But there’s another relationship with a Todouser that Folder Has
A Todouser can have many folders, and a folder belongs to a specific todouser (recall the diagram from part 1a of this tutorial). Think of this as a relationship that is separate from the relationship between folders and tasks and todousers and tasks.
So we need to add the following to our Todouser model:
has_many :folders, dependent: :destroy
The dependent: :destroy command tells Rails to destroy any associated folders when we delete a Todouser.
Finally, we add the following to our Folder model:
At this point, your Todouser model file should look something like this:
class Todouser < ActiveRecord::Base # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable # Setup accessible (or protected) attributes for your model attr_accessible :email, :password, :password_confirmation, :remember_me # attr_accessible :title, :body has_many :tasks, as: :commentable has_many :folders, dependent: :destroy end
And your Folder model file should look something like this:
class Folder < ActiveRecord::Base attr_accessible :name, :todouser_id has_many :tasks, as: :commentable belongs_to :todouser end
Sidebar: Using Git
Finally, we commit our work to git and merge it into the master branch.
git add .
git commit -m “added polymorphic associations”
git checkout master
git merge part-4-polymorphic-models
So far we’ve added authentication to our application and created the associations and models we need. Next, it’s time to start laying the groundwork to create and edit tasks.
I’m keeping a public repo of this application at my github account. You can download the source code there.
- Agile Web Development with Rails by Dave Thomas