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?
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"
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
Related
I have a model Match and a model Team, each Match has two teams and each Team can have multiple Matches.
Team: name:string
Match name:string team1:references team2:references
So my models look like this.
class Match < ActiveRecord::Base
belongs_to :team1, :class_name => Team, :foreign_key => "team1_id"
belongs_to :team2, :class_name => Team, :foreign_key => "team2_id"
end
class Team < ActiveRecord::Base
has_many :matches
end
I want to be able to create a new Team through a Match. And I don't want either duplicate Match records nor Team records. I am kinda lost, if this association is the right one between Team and Match.
Here you should use has_and_belongs_to_many relationship.
Match.rb
class Match < ActiveRecord::Base
has_and_belongs_to_many :teams
end
Team.rb
class Team < ActiveRecord::Base
has_and_belongs_to_many :matches
end
And generate a migration to create table to associate teams and matches with each other:
rails g migration create_matches_teams_table
Then in generated migration file:
class CreateMatchTeams < ActiveRecord::Migration
def self.up
create_table :matches_teams, :id => false do |t| # :id => false; is to prevent the creation of primary key
t.integer :match_id
t.integer :team_id
end
end
def self.down
drop_table :matches_teams
end
end
Then run this migration, and you can associate teams and matches with each other via habtm relationship.
try something like this:
class Match < ActiveRecord::Base
#home team
belongs_to :team1, :class_name => Team, :foreign_key => "team1_id"
#away team
belongs_to :team2, :class_name => Team, :foreign_key => "team2_id"
#this should only allow 1 match between each team
validates :team1_id, :uniqueness => { :scope => :team2_id }
end
class Team < ActiveRecord::Base
has_many :home_matches, :class_name => Match, :foreign_key => "team1_id"
has_many :away_matches, :class_name => Match, :foreign_key => "team2_id"
validates :name, :uniqueness => true
def matches
Match.where("team1_id = ? OR team2_id = ?", self.id, self.id)
end
end
I have two models, meetings and attendees which share a habtm relationship. I also have a User model to which a meeting can belong (as a meeting organizer).
class Meeting < ActiveRecord::Base
belongs_to :organizer, :class_name => User, :foreign_key => "organizer_id"
has_and_belongs_to_many :attendees, :class_name => User, :association_foreign_key => "attendee_id"
end
class User < ActiveRecord::Base
has_and_belongs_to_many :meetings, :class_name => Meeting, :association_foreign_key => "meeting_id"
end
and then I have the relationship table..
create_table "attendees_meetings", :id => false, :force => true do |t|
t.integer "attendee_id"
t.integer "meeting_id"
end
When I create a new meeting, and then reference the attendees as meeting.attendees, I get an error. Also the same thing with organizer, meeting.organizer throws an error. Have I not setup the relationships properly?
m = Meeting.create(:subject => "Test", :location => "Neverland", :body => "A test", :organizer_id => 8)
m.organizer
NoMethodError: undefined method `match' for #<Class:0x00000103d8cf08>
The same with attendees (though I have not defined any at the moment but shouldn't be throwing an error)
1.9.2-p318 :014 > m.attendees
(Object doesn't support #inspect)
=>
The class_name option on the has_and_belongs_to_many should be a string which is the name of the class. You have passed the class object itself. So, for example,
has_and_belongs_to_many :attendees,
:class_name => "User",
:association_foreign_key => "attendee_id"
I think you may also need to add a :foreign_key => 'attendee_id' on the has_and_belongs_to_many declaration in your User model and can remove the :association_foreign_key option because it is the default. Actually you can probably lose the :class_name option too as that is also the default. So:
has_and_belongs_to_many :meetings,
:foreign_key => "attendee_id"
I'm having troubles getting a polymorphic many-to-many model working in ruby/rails. The model has three tables that need to be joined, Infection, Drug, and Symptom:
create_table "diseases" do |t|
t.string "name"
end
create_table "drugs" do |t|
t.string "name"
end
create_table "symptoms" do |t|
t.string "name"
end
create_table "to_symptoms" do |t|
t.integer "symptom_id"
t.integer "symptomatic_id"
t.string "symptomatic_type"
end
Where symptoms is linked to both infections and drugs. The tricky part is that the relationship of a symptom to a drug can be either as a side effect or as a contraindication. The way I tried to do this was:
class ToSymptom < ActiveRecord::Base
belongs_to :symptomatic, :polymorphic => true
belongs_to :symptom
end
class Drug < ActiveRecord::Base
has_many :to_symptom, :as => :symptomatic
has_many :contraindications, :class_name => "Symptom",
:through => :to_symptom, :source => :symptomatic,
:source_type => 'Contraindication'
has_many :side_effects, :class_name => "Symptom",
:through => :to_symptom, :source => :symptomatic,
:source_type => 'SideEffect'
end
class Symptom < ActiveRecord::Base
has_many :to_symptom
has_many :diseases, :through => :to_symptom, :source => :symptomatic,
:source_type => 'Disease'
has_many :contraindicated_drugs, :class_name => "Drug",
:through => :to_symptom, :source => :symptomatic,
:source_type => 'Contraindication'
has_many :caused_by, :class_name => "Drug", :through => :to_symptom,
:source => :symptomatic, :source_type => 'SideEffect'
end
class Disease < ActiveRecord::Base
has_many :to_symptom, :as => :symptomatic
has_many :symptoms, :through => :to_symptom
end
The Disease <-> Symptom relationship seems to be working the way I'd expect, but the relationships between Drug and Symptom aren't doing what I'd expect. The relationship in the direction of symptoms-> drugs seems to be working, but the reverse direction generates some weird SQL. If I try something like:
d = Drug.first
d.contraindications
I'll get the following SQL:
SELECT
`symptoms`.*
FROM `symptoms`
INNER JOIN `to_symptoms` ON `symptoms`.`id` = `to_symptoms`.`symptomatic_id`
WHERE `to_symptoms`.`symptomatic_id` = 2
AND `to_symptoms`.`symptomatic_type` = 'Drug'
AND `to_symptoms`.`symptomatic_type` = 'Contraindication'
The to.symptoms.symptomatic_type = drug shouldn't be in there, and the join in on the wrong field of to_symptoms (symptomatic_id vs. symptom_id. I've tried a ton of different combinations, but I can't seem to get this one to work. Is what I'm trying to do even possible in RoR?
It seems that this isn't very widely advertised, but it apparently doesn't work in Rails... (polymorphic mas_many :through) (at least not without insane hacks). I'll try to find some supporting links
I am using Ruby on Rails 3.0.9. I am trying to get the list of all the parents from the relationships table. Here is code.
require 'rubygems'
gem 'activerecord', '3.0.9'
require 'active_record'
ActiveRecord::Base.establish_connection( :adapter => 'sqlite3',
:database => ':memory:')
ActiveRecord::Schema.define do
create_table :people, :force => true do |t|
end
create_table :relationships, :force => true do |t|
t.integer :parent_id
t.integer :child_id
end
end
class Person < ActiveRecord::Base
end
class Relationship < ActiveRecord::Base
belongs_to :parent, :class_name => 'Person'
belongs_to :child, :class_name => 'Person'
end
child = Person.create!
parent = Person.create!
Relationship.create!(:parent_id => parent.id, :child_id => child.id)
# Person.parents_list needs to be implemented and
# I am stuck and do not know how to get to that
assert 1, Person.parents_list.size
assert parent.id, Person.parents_list.first.id
You can use a self-referential many to many relationship:
class Person < ActiveRecord::Base
has_many :relationships,
:foreign_key => 'child_id'
has_many :parents,
:through => :relationships,
:source => :child
end
child.parents.count # -> 1
This blog entry has more details.
Edited to add: Ah, you just want every Person who is a parent. You can query for that:
class Person < ActiveRecord::Base
has_many :parent_relationships,
:class_name => 'Relationship',
:foreign_key => 'parent_id'
end
Person.joins(:parent_relationships).count # -> 1
By joining against the Relationships table you'll get only the Person records with matching Relationships (an inner join). This is covered by the excellent Rails guides.
I have a Comment class with a :foreign_key of post_id in the Post class.
class Comment < ActiveRecord::Base
belongs_to :post, :class_name => "Post", :foreign_key => "post_id", :counter_cache => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
end
But my CreateComments migration does not define a database-level foreign key:
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.column "post_id", :integer, :default => 0, :null => false
t.column "author", :string, :default => "", :limit => 25, :null => false
t.column "author_email", :string, :default => "", :limit => 50, :null => false
t.column "content", :text, :null => false
t.column "status", :string, :default => "", :limit => 25, :null => false
t.timestamps
end
end
def self.down
drop_table :comments
end
end
Instead post_id is a simple Integer column.
So, it seems that this foreign key relationship exists only in the mind of Rails, not at the database level.
Is this correct?
Also, is it necessary for the corresponding Post model to also declare its reciprocal foreign key relationship with Comments using the :foreign_key attribute or could that be omitted?
class Post < ActiveRecord::Base
set_table_name("blog_posts")
belongs_to :author, :class_name => "User", :foreign_key => 'author_id'
has_many :comments, :class_name => "Comment",
:foreign_key => 'post_id', :order => "created_at desc", :dependent => :destroy
has_many :categorizations
has_many :categories, :through => :categorizations
named_scope :recent, :order => "created_at desc", :limit => 5
end
The Rails default behaviour is that the column used to hold the foreign key on a model is the name of the association with the suffix _id added. The :foreign_key option lets you set the name of the foreign key directly. The associations between your Post and Comment model classes should look like this:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
—Note that you don't need :class_name => "Post" in your Comment model. Rails already has that information. You should only be specifying :class_name and :foreign_key when you need to override the Rails' conventions.
You're correct that Rails maintains the foreign key relationships for you. You can enforce them in the database layer if you want by adding foreign key constraints.
I think you would benefit from reading A Guide to ActiveRecord Associations.