Connecting a row of an unknown table to another row of an un

Hello all,

I’m currently designing a web app in Rails that lets me keep a directory
of free/opensource computer games. Each game has attributes like name,
website, review, etc., but each game can also be associated with a set
of genres and gameplay elements. The main three tables I’m working with
are “games”, “genres”, “elements”, “game_genres”, and “game_elements”
(the last two being join tables, obviously). The models look like

class Game < ActiveRecord::Base
has_many :game_genres
has_many :genres, :through => :game_genres
has_many :game_elements
has_many :elements, :through => :game_elements
end
class Genre < ActiveRecord::Base
has_many :game_genres
has_many :games, :through => :game_genres
end
class Element < ActiveRecord::Base
has_many :game_elements
has_many :games, :through => :game_elements
end
class GameGenre
belongs_to :game
belongs_to :genre
end
class GameElement
belongs_to :game
belongs_to :element
end

So I’ve got those associations down pat (although, yes, I realize I
probably could have used habtm). Here comes the hard part. As well as
joining multiple genres and multiple elements to multiple games, I’d
also like to be able to join one genre to multiple genres, one element
to multiple elements, and multiple genres to multiple elements.
Visually speaking, instead of being able to do just this:

element - game ----- genre
element -. .´ game \ / genre
element X— game X genre
element / -- game /- genre
element / game - genre

I’d like to be able to do this:

.-----------------------------------.
/’- element - game ----- genre -’
/ .- element -. .´ game \ / genre —.
\ ‘- element X— game X genre —|
-- element /– game / `- genre -. |
.- element / game ´ genre -|-’
‘-----------------------------------’

My first idea was to create three extra join tables, “genre_genres”,
“element_elements”, and “genre_elements”. I realized, however, that I
could collapse that into one “connections” table:

id :integer
a_id :integer
b_id :integer
a_class_name :string
b_class_name :string

The _id’s can hold either/both the id of a genre or an element; likewise
the _class_name’s can hold either/both “genre” or “element”.

However, that would require hacking together a custom ActiveRecord class
that created relations given a table in the format I described, and I
thought, surely there’s already something out there that does this?

Then I realized that my unified join table vaguely resembled something I
remembered reading about in the Agile Web D. using Rails book I
picked up not too long ago: polymorphic associations. Intrigued, I
looked around on the web for a while and found a plugin
“has_many_polymorphs”. I’m not sure if this is what I should use though.

Could someone give me some pointers on how to pull this trick off?
Thanks in advance.

– Elliot W.

On 09/22/2007 09:02 PM, Elliot W. wrote:
[…]

class GameGenre
belongs_to :game
belongs_to :genre
end
class GameElement
belongs_to :game
belongs_to :element
end

Sorry, that’s supposed to be:

class GameGenre < ActiveRecord::Base
belongs_to :game
belongs_to :genre
end
class GameElement < ActiveRecord::Base
belongs_to :game
belongs_to :element
end

– Elliot

Okay. I’ve checked out has_many_polymorphs and it does seem like it fits
in this situation. So now I’ve come to this point:

class Association < ActiveRecord::Base
belongs_to :left, :polymorphic => true
belongs_to :right, :polymorphic => true

acts_as_double_polymorphic_join(
:lefts => [:genres, :elements],
:rights => [:genres, :elements]
)
end

And table “associations”:

left_id :integer
left_type :string
right_id :integer
right_type :string

That’s great, I’ll be able do stuff like:

g = Genre.find(1)
g.lefts.map(&:class) #=> [Genre, Genre, Element, Genre, …]
g.rights.map(&:class) #=> [Element, Genre, …]
g.genres.map(&:name) #=> [‘Action’, ‘Arcade’, …]
g.elements(&:name) #=> [‘First-Person View’, ‘Jumping’, …]
e = Element.find(1)

etc…

However, now I foresee a problem. If I tried to do this:

g.genres << Genre.find(2)

since both lefts and rights can contain Genres, which collection would
the new Genre go into?

But you know, I don’t care so much being able to access both lefts and
rights – ideally what would be needed is just this:

g.associated.map(&:class) #=> [Genre, Element, Genre, Genre, Element,
…]

That should essentially find every genre and element whose id ==
associations.right_id (if associations.left_id == g.id) or whose id ==
associations.left_id (if associations.right_id == g.id), and return the
objects corresponding to those ids.

Ugh. This does not look easy :slight_smile: Ideas, anyone?

– Elliot

Hi. I have a problem with acts_as_double_polymorphic_join …

I put an example from script/console:

i=Image.find 1
=> #<Image:0xb76fe41c @attributes={“href”=>nil, “id”=>“1”}

i.links.map(&:id)
=> [1, 2, 3, 4, 5, 6]

i.links << Link.create
=> …

i.links.map(&:id)
=> [1, 2, 3, 4, 5, 6]

i.reload
=> …

i.links.map(&:id)
=> [1, 2, 3, 4, 5, 6, 7]

i.links doesn’t update his values automaticaly. But if I use
i.related_tos (plural of related_to) in spite of i.links it works
perfectly…

example: (include related links and images…)

i=Image.find 1
=> #<Image:0xb764e634 @attributes={“href”=>nil, “id”=>“1”}

i.related_tos.map(&:id)
=> [1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8]

i.links << Link.create
=> …

i.related_tos.map(&:id)
=> [1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]

Any idea?