In my last post we defined our application, and created some Rails migrations to the database tables. That's a good start, but now we need to tell Rails how our tables are related. This might seem a little redundant seeing as the database can do this, but with the migration approach we haven't defined the relationships and Rails makes our life easier if we spend a little time defining them.
This process is both simple, and sometimes hard to understand. So we'll talk about the different possibilities. We'll focus on the relationships for this post, and cover the other things you can do with models in a future post.
When we created our migration files, we did so by asking for a model (script/generate model location). This created both our migration and model files. We know where the migration files are, but the models are in a different location.
Rails uses the Model View Controller approach. This is reflected under the app directory where we find controllers, models, and views directories. Get familiar with this structure, you'll spend a lot of time in the app directory. For now, we're going to focus on the app/models directory. Here we find three files reflecting the models we asked to be created. The generator creates these for us, though we could write them ourselves if we'd like.
Lets start with the Location model. Open the app/models/location.rb. You'll see the following:
class Location < ActiveRecord::Base end
There is not much there, but even this small bit is plenty enough to provide some great capabilities. The model inherits from the ActiveRecord::Base class. This gives us all we need to perform the standard CRUD (Create, Read, Update, Delete) functionality. But, this is NOT enough to tell Rails how a Location is related to an Item.
A single location may have many items. This is a standard one to many relationship. To reflect this, we need to tell the Location model that it can have many items. In Rails, this is done with the has_many command. (rather anit-clamtic after the build up, eh?) Here's how we change our model file:
class Location < ActiveRecord::Base has_many :items end
When dealing with the relationships, think about how you would describe the relationship. "A location has many items". Notice that "items" is plural. So, we transpose this to say "has_many :items" (remembering that the : notation is just a symbol - an alternate way to represent a string in some cases). The has_many "what" will be the name of the table the relationship is for. A model can have many relationship declarations. If we were to assume a location could also have many names, then we might see "has_many :location_names" right below the first has_many line.
Now, for the flip side of that relationship. Our item model can have one location. This is reflected by the location_id field we created in the items table. There are two methods available to us here - has_one and belongs_to. To choose which one to use, consider where your foreign key field is. If we were to say that a location could only have one item, then instead of the has_many we used, we would have used has_one. But we still have the location_id in the items table - for that side of the relationship, we use belongs_to.
class Item < ActiveRecord::Base belongs_to :location end
The trick with belongs_to is that it will always go to the model that has the foreign key id field (the location_id in this case). This is one of those things that might seem backwards at first, but does make sense once you have worked with it for a bit. Notice that the location symbol is not plural. This would read as "an item belongs to a location", which reflects that an item can only be linked to a single location record.
Phew, one relationship down, one to go. Er, actually, it's not that simple...
Our items table has a many to many relationship with categories. From a database perspective this is normally handled with a join table (aka a cross reference table). Our categories migration file created this table for us. A quick recap though is that the join table name must be comprised of the names of the two tables involved, in alphabetical order, and that table will contain the foreign key fields to our two tables (item_id, and category_id for our table). So our join table is called "categories_items".
To reflect this join we have two choices - we can use a "has_and_belongs_to_many" approach (which is short-handed to habtm for dicussions), or a "has_many :through" approach. I'll describe both, but we only need the habtm approach here.
When using the habtm approach, both models get an habtm entry. So our models and categories models would like like so:
class Item < ActiveRecord::Base belongs_to :location has_and_belongs_to_many :categories end class Category < ActiveRecord::Base has_and_belongs_to_many :items end
Use a habtm approach when your join table ONLY joins your tables. If your join table provides other properties, you need a separate model for the join table, and you would use the "has_many :through" approach.
Imagine for a moment that we wanted to track when an item was linked to it's various categories. We could just add a "linked_at" field to our join table. But now we have no clear way to get the linked_at data. To do so, we need to create a new model for our categories_items table (and it'll likely need an ID now, whereas we didn't create one for the current table). We would then change the three models to look like this:
class Item < ActiveRecord::Base belongs_to :location has_many :categories, :through => :categories_items end class Category < ActiveRecord::Base has_many :items, :through => :categories_items end class CategoryItem < ActiveRecord::Base has_many :categories has_many :items end
So our categories and items table become simple many to many relationships, with an extra instruction stating how to get to the foreign table data (the :through). We point :through to the model that contains the join fields.
So what does all that do for us. Take a look at some of this Rails loveliness:
myitem = Item.find(1) puts myitem.location.name puts myitem.categories.name
The first line creates a variable called "myitem", and sets it to the item record represented by ID 1. The second line is using "puts" (which outputs something to the command line) to show us the name of the location that item is stored at. The second line then shows us the name of the first category the item has.
All this talk about defining the relationships may seem dull and boring, but as you can see it saves us a large amount of coding with regards to SQL. The categories become a nice array property of the item. (Or, in converse the items would become a nice array property of a category.) And we get that for almost free.
We have only just scratched the surface of what can be done with our models and their relationships. In the Rails API, these are called the Class Methods of the ActiveRecord object. You can find all the details you need at http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethod..., including more details when to use the belongs_to or has_one approach.
In the next post, we'll discuss the processing flow of a request, and how that applies to our application. We'll also see the first functional pages of the app.