views:

771

answers:

4

I have a legacy database that I'm working on getting ActiveRecord to work with. I've run into a problem with join tables. I have the following:

class TvShow < ActiveRecord::Base

  set_table_name "tvshow"
  set_primary_key "idShow"

end

class Episode < ActiveRecord::Base
  set_table_name "episode"
  set_primary_key "idEpisode"
end

And then I have a table called tvshowlinkepisode that has 2 fields: idShow, idEpisode So I have 2 tables and a join between them (so a many to many relationship), however the join uses non-standard foreign keys. My first thought was to create a model called TvShowEpisodeLink but there isn't a primary key. The idea was that since the foreign keys are non-standard I could use the set_foreign_key and have some control. Ultimately I want to say something like TvShow.find(:last).episodes or Episode.find(:last).tv_show. How do I get there?

A: 

The relationship is one-to-many so you need to use the belongs_to/has__many relationship in the tables. If your database has views, you could mask the non-standard foreign keys by a view for the tables.

Not sure if that fits 100% what you need, but I hope it at least gives you an idea.

txwikinger
Interesting idea. I haven't worked with database views before. I don't have the ability to change anything about the database, so I'm not sure if database views would work here. On the subject of the has_and_belongs_to_many relationship, doesn't that fall apart if I have non-standard primary keys? I thought that by using a third model I could do a has_and_belongs_to_many_through relationship.
nixterrimus
Er make that non-standard foreign in key in the above response: On the subject of the has_and_belongs_to_many relationship, doesn't that fall apart if I have non-standard *foreign* keys?
nixterrimus
@nixterrimus, right, thanks
txwikinger
Well, the idea is that you use the standard foreign key, the view translates basically your standard foreign key to the non-standard one in the table. A view can be as simple as re-naming the fields (i.e. CREATE VIEW TvShowView AS SELECT tvshow, idShow AS Show_id FROM TvShow). As long as the view has a one-to-one relationship to the table you can also use it for inserts and updates.
txwikinger
+2  A: 

This work for you...

class TvShow < ActiveRecord::Base
  set_table_name "tvshow"
  set_primary_key "idShow"

  has_many :tv_show_link_episode, :foreign_key => 'idShow'
  has_many :episodes, :through => :tv_show_link_episode
end


class Episode < ActiveRecord::Base
  set_table_name "episode"
  set_primary_key "idEpisode"

  has_many :tv_show_link_episode, :foreign_key => 'idEpisode'
  has_many :tv_shows, :through => :tv_show_link_episode

end

class TvShowLinkEpisode  < ActiveRecord::Base
  set_table_name "tvshowlinkepisode"

    # the foreign key is named by the TvShowLinkEpisode field, 
    # the primary key name is for the primary key of the associated class
    belongs_to :tv_show, :foreign_key => 'idShow'
    belongs_to :episode, :foreign_key => 'idEpisode'
end
Alvaro Talavera
+1, this looks fine to me
marcgg
A: 

With this, you don't need to set table views, actually, tables views it's not "The Rails Way",

Try this:

>> TvShow.find(1).episodes 
#=> returns an array with [#<Episode idEpisode: 1, test: "Episode 1">]

>> Episode.find(1). tv_shows 
#=> returns an array with [#<TvShow idShow: 1, test: "tvshow 1">]

Then you can do some stuff like:

e = Episode.find(1)
TvShow.find(1). episodes << e
#=> this make the proper association
Alvaro Talavera
+4  A: 

I believe you can be slightly more elegant than Alvaro's answer using options to has_and_belongs_to_many, though his answer is perfectly fine and will result in fairly identical functionality for any clients of your class.

class TvShow < ActiveRecord::Base

  set_table_name "tvshow"
  set_primary_key "idShow"
  has_and_belong_to_many :episodes, 
                         :join_table => "tvshowlinkepisode", 
                         :foreign_key => "idShow",
                         :association_foreign_key => "idEpisode"

end

class Episode < ActiveRecord::Base
  set_table_name "episode"
  set_primary_key "idEpisode"
  has_and_belongs_to_many :tv_shows,
                          :join_table => "tvshowlinkepisode",
                          :foreign_key => "idEpisode",
                          :association_foreign_key => "idShow"
end

Note that the :foreign_key option specifies which column is the id for the class on "this side" of the link, while :association_foreign_key specifies the column that is the id for the class on the "other side" of the link.

Contrast this with Alvaro's answer, this pattern should avoid instantiation of any unnecessary objects to represent the link.

animal
I would think an episode can only belong to one particular tv show, not to many.
txwikinger
I would think that too, @txwikinger, but I guess the example is that if a show has a spin-off series that at sometime during both of their respective series they may have an episode where the two come together. At any rate, it's interesting getting someone else's database to make logical sense through activerecord.
nixterrimus