views:

62

answers:

2

I'm trying to create a model for a comic book which has 1 writer and 1 artist. These are both instances of a Person. However how do I show this in my migration?

class CreateComics < ActiveRecord::Migration
   def self.up
     create_table :comics do |t|
       t.column :name, :string      
       t.column :writer_id, :integer, :null => false
       t.column :artist_id, :integer, :null => false
     end
   end
...

How do I say :writer_id should map to :person_id or is this kind of renaming frowned upon?

+3  A: 

You create the mapping in your model class using the class_name and foreign_key option.

belongs_to :writer, :class_name => "Person", :foreign_key => "writer_id"
belongs_to :artist, :class_name => "Person", :foreign_key => "artist_id"
MattMcKnight
Thank you. Doesn't this mean my mySQL will never get the knowledge of the foreign key?
Stickboy
You can add the foreign key constraints in the migration. I created a migration helper to do this in SQL Server, you should be able to do something similar for MySQL or use a plugin like this one: http://agilewebdevelopment.com/plugins/foreign_key_migrations
MattMcKnight
A: 

The migration has no knowledge of how you intend to use the tables defined within it. All that information goes in the models.

In short your migration is fine for what you've described will be done. But you need to flesh out the models to define the associations you're thinking of.

class Comic < ActiveRecord::Base
  belongs_to :writer, :class_name => "Person"
  belongs_to :artist, :class_name => "Person"
end

This allows you to reference the Writer and Artist from a Comic. However, you will probably want to reciprocate the association so that you can easily fetch a comic from a person based on their role in it's production.

class Person < ActiveRecord::Base
  has_many :comics_as_writer, :class_name => "Comic", :foreign_key => :writer_id
  has_many :comics_as_artist, :class_name => "Comic", :foreign_key => :artist_id

  # some times you don't care what a person did for a comic,
  # you just want to know what they worked on.
  has_many :comics, :finder_sql => "SELECT comics.* FROM comics, people WHERE " +
     "`comics`.`writer_id` = `people`.`id` OR " +
     " `comics`.`artist_id` = `people`.`id`"
end

With these relationships defined the following is possible:

@comic.artist # => Person with id matching comics.artist_id
@comic.writer # => Person with id matching comics.writer_id

@person.comics_as_writer # => Array of comics where @person.id matches comics.writer_id
@person.comics_as_artist # => Array of comics where @person.id matches comics.artist_id

@person.comics # => Array of comics where @person.id matches comics.writer_id or comics.artist_id
EmFi
Thanks for the thorough reply. This is where I will be heading. Is that not meant to be in the Person class tho?
Stickboy
Good catch. I've corrected the mistake.
EmFi
Sorry for the late comment, I've just tried implementing this. It all works fine until I call <code> <% @person.comics.each do |comic| %> <%= comic.name %> <% end %> </code> This prints out the name of ther Person, not the comic. Have I missed something? When i do comic.class it corrcectly reports Comic.Sorry I've forgotten how to format things on here...
Stickboy
Doesn't matter, I fixed it. I had to do "SELECT comics.* "
Stickboy
You're entirely correct about `SELECT comics.*`. I don't think I had time to test the code as I wrote. The solution has been updated for future visitors.
EmFi