I started simple this time.
I wanted a PERSON class and a GIFT class.
A Person has been given gifts and may give gifts (and a few other common attributes).
class CreatePeople < ActiveRecord::Migration def change create_table :people do |t| t.string :first_name t.string :last_name t.date :date_of_birth t.string :email_address t.timestamps end end end
One of the things that I can’t decide if I like is the automatic pluralization of words, especially People/Person. I would have been content with a Persons table, but when creating a model, by default (as I’m aware it can be overridden), a Person is mapped to a table called “People.”
The second table, Gifts is very simple:
class CreateGifts < ActiveRecord::Migration def change create_table :gifts do |t| t.string :description t.integer :from_person_id t.integer :to_person_id t.timestamps end end end
As I thought I might want a richer structure for the Gift class in the future, I did not use the more standard “person_id” name for the foreign key column that maps a gift to a Person. I wanted the column name to be more obvious what it was. Additionally, I needed two columns that both mapped to a “Person", so I couldn’t have both be called “person_id” anyway.
By deviating from the normal pattern, there are a few expectations when defining the ActiveRecord class. It was these expectations that weren’t clear to me (especially with examples).
The Ruby class for Gift is defined like so:
class Gift < ActiveRecord::Base belongs_to :from_person, :class_name => "Person", :foreign_key => "from_person_id" belongs_to :to_person, :class_name => "Person", :foreign_key => "to_person_id" end
and the Person:
class Person < ActiveRecord::Base has_many :gifts_given , :class_name => "Gift", :foreign_key => "from_person_id" has_many :gifts, :foreign_key => "to_person_id" end
The key (and the ‘ah ha’ moment for me) was the use of the foreign_key parameter to the on the has_many and belongs_to associations.
In the Gift class, I included the belongs_to association macro. In this case, :from_person is the name of the rich accessor method (which looks like a property in other languages) that will be added to the Gift class. Using the symbol :class_name is like a class finding assistant. Without it, the Rails framework assumes that there would be a class named “FromPerson.” Of course, that would fail. By specifying “Person,” I’ve indicated to Rails that the class it should map to is called “Person” which I defined earlier. The :foreign_key symbol and value indicates which column in the backing table has the value which will map to an instance of a Person. In the SQL table, I added a “from_person_id” column and this points at that as the “from_person_id” column is the foreign key to the People table. (The same is true for :to_person.)
Looking at the Person class, it is using another common association macro, :has_many. :Has_many when used here, is indicating that a Person may have zero or more “gifts.” The new accessor method is named gifts (by using :gifts). Here, too, you’ll specify the name of the foreign_key. Again, repeat this for the :gifts_given automatically added accessor method. One interesting thing is that only :gifts_given requires the :class_name to be specified. The reason is that Rails automatically maps :gifts to the Gifts class (by way of naming). The :gifts_given cannot be automatically mapped, so you need to give (sigh) it a little help.
Here’s a little test:
>> jason = Person.find(1) Person Load (18.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = ? LIMIT 1 [["id", 1]] #<Person id: 1, first_name: "Jason", last_name: "Bourne", date_of_birth: nil, email_address: nil, created_at: "2012-01-06 13:47:40", updated_at: "2012-01-07 03:10:29"> >> jason.gifts_given Gift Load (0.0ms) SELECT "gifts".* FROM "gifts" WHERE "gifts"."from_person_id" = 1 [#<Gift id: 1, description: "Machine Gun", from_person_id: 1, to_person_id: 4, created_at: "2012-01-07 14:39:17", updated_at: "2012-01-07 14:39:17">] >> jason.gifts_given.to_person.first_name "Magnum" Person Load (0.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = 4 LIMIT 1 >> jason.gifts_given.to_person.gifts [#<Gift id: Gift Load (1.0ms)1 SELECT "gifts".* FROM "gifts" WHERE "gifts"."to_person_id" = 4 , description: "Machine Gun", from_person_id: 1, to_person_id: 4, created_at: "2012-01-07 14:39:17", updated_at: "2012-01-07 14:39:17">] >> jason.gifts_given.to_person.gifts.from_person.first_name Person Load (0.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = 1 LIMIT 1 "Jason"
I added two people: Jason, and Magnum, and one gift before executing the code above. Jason, as you should be able to follow, gave a wonderful gift to Magnum. As you can see, by using the automatically added accessor methods by way of the association macros described above, I was able to traverse the database structure very easily when mapped to a few simple objects.
One plus of experimenting and testing with the console while using Rails/Ruby in this case is that the output includes the SQL commands that are executed when the various method calls are made. Here’s an example where I rolled multiple calls into one chained call:
>> jason = Person.find(1).gifts_given.to_person.first_name Person Load (19.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = ? LIMIT 1 [["id", 1]] Gift Load (0.0ms) SELECT "gifts".* FROM "gifts" WHERE "gifts"."from_person_id" = 1 "Magnum" Person Load (0.0ms) SELECT "people".* FROM "people" WHERE "people"."id" = 4 LIMIT 1