In this third part of a five-part series that delves into Rail's Active Record, you'll learn how to handle many-to-many associations, use an array, and more. This article is excerpted from chapter five of the book Beginning Rails: From Novice to Professional, written by Jeffrey Allan Hardy, Cloves Carneiro Jr. and Hampton Catlin (Apress; ISBN: 1590596862).
Arrays, Associations and the Active Record (Page 1 of 4 )
Creating Many-to-Many Associations
Often, the relationship between two models is many-to-many. This describes a pattern where two tables are connected to multiple rows on both sides. We’ll use this in our events application to add categories to events. If we wanted to allow only one category to be selected for a given event, we could use has_many. But we want to be able to apply multiple categories.
Think about this for a minute: an event can have many categories, and a category can have many events—where would thebelongs_togo in this situation? Neither model belongs to the other in the traditional sense. In Active Record-speak, we refer to this kind of association ashas_and_belongs_to_many(often referred to ashabtmfor short).
Thehas_and_belongs_to_manyassociation works by relying on a join table that keeps a reference to the foreign keys involved in the relationship. The join table sits between the tables we want to join:categoriesandevents. Not surprisingly, then, the join table in this case will be calledcategories_events. Pay particular attention to the table name. It’s formed from the names of each table in alphabetical order, separated by an underscore. In our case, thecin categories comes before thee in events, hence,categories_events. Figure 5-4 illustrates this relationship.
Figure 5-4.The many-to-many relationship between events and categories
Let’s start by adding theCategorymodel. This is a simple matter of generating the model. Since the category schema definition is so simple (consisting of just anamecolumn), we’ll let the generator fill in the migration for us by passing field arguments directly to the generator. Run the following command inside your application root:
./script/generate model Category name:string
You’ll notice that we generated this model a bit differently than the others. We addedname:stringonto the end of thegeneratemethod. This is a shortcut to have your migrations automatically generated with thefield_name:type that you specify. This is a handy trick when you’re generating simple schemas. Let’s look at that migration and see what was generated, as shown in Listing 5-8.
Listing 5-8. The db/migrate/004_create_categories.rb File
class CreateCategories < ActiveRecord::Migration def self.up create_table :categories do |t| t.column :name, :string end end
def self.down drop_table :categories end end
We need another migration to create the join table. Let’s do that now by running the following command:
Listing 5-9. The db/migrate/005_create_categories_events.rb File
class CreateCategoriesEvents < ActiveRecord::Migration def self.up create_table :categories_events, :id => false do |t| t.column :event_id, :integer t.column :category_id, :integer end end
def self.down drop_table :categories_events end end
Remember that when usingcreate_table, you don’t need to specify the primary key, as it will be created automatically. Well, in the case of a join table, we actually don’t want a primary key. This is because the join table isn’t a first-class entity in its own right. Since creating tables without primary keys is the exception and not the rule, we need to explicitly tellcreate_tablethat we don’t want to create anid. Take a close look at the call tocreate_tablein Listing 5-9. We pass in the option:id => false. This preventscreate_tablefrom creating the primary key.
With theCategorymodel and the join table in place, we’re ready to let Active Record in on our association. Open theEventandCategory models and add thehas_and_belongs_to_manydeclarations to them, as shown in Listings 5-10 and 5-11.
Listing 5-10. has_and_belongs_to_many Declaration in app/models/event.rb
class Event < ActiveRecord::Base belongs_to :user has_and_belongs_to_many :categories end
Listing 5-11. has_and_belongs_to_many Declaration in app/models/category.rb
class Category < ActiveRecord::Base has_and_belongs_to_many :events end
We should also create a few categories to work with. We like to do this with fixtures. Fixtures are textual representations of database records. They can be used to populate your database with data, and they’re a great place to keep so-called seed data. Using fixtures, we’ll kill two birds with one stone: fixtures are easy to load into the database any time we wish, and they’ll be useful later on, when we’re testing our application in Chapter 9. The model generator created an emptycategoriesfixture intest/fixtures/categories.yml. Open it and add a few categories so that it looks like Listing 5-12.
Listing 5-12. The test/fixtures/categories.yml File
conferences: id: 1 name: Conferences
parties: id: 2 name: Parties
concerts: id: 3 name: Concerts
readings: id: 4 name: Readings
movies: id: 5 name: Readings
That should do nicely. You can load fixtures using the Rake taskdb:fixtures:load, specifying the fixtures you want to load using theFIXTURESvariable.
$ rake db:fixtures:load FIXTURES=categories
If you need to add more categories later, you can just append them to the fixture file and reload it. We’ll talk more about fixtures in Chapter 9, but this first look should give you an idea of how they work.
Let’s give this a test run now. Get your console ready and run the following commands.
Here, we automatically associate a category with an event using the<<operator. In this case, when you use<<withhas_and_belongs_to_many, it automatically saves the new association. Some things in Active Record don’t happen until you saysave, but this is one of the examples where that part is done automatically,
We can even do this from the category’s side of the association. Try the following:
We just did the opposite of the previous test. We said thathas_and_belongs_to_manyworks in both directions, right? So, we simply found our new category and asked it for its first event, which we can see is now Tiki Party, because that’s what we associated in the other direction, too.
Usinghas_and_belongs_to_manyis a very simple way to approach many-to-many associations. However, it has its limitations. Before you’re tempted to use it for associating users with events they would like to attend (activities), we should point out that it has no way of storing additional information on the join. What if we wanted to know when someone decided to attend an event, or how many people the attendee is going to bring along? This kind of data fits naturally in the join table. Rails includes another type of association calledhas_many :through, which allows us to create rich joins like this.