Two-to-one mappings


#1

Hey all,

I have a pretty simple question, but I’m not sure of a good solution
to it. Essentially, I want to provide a two-to-one mapping of models.
I’m working on an application for a contest, where every (unordered)
combination of two Rounds is supposed to be assigned to one Room. Any
Room might have many different combinations of Rounds, however.

What is the Right Way of doing this in Rails? Maybe create a model
that holds the associated foreign keys? Also, what would be a good
way to scale this out, if I wanted to be able to map unordered
n-element collections of Rounds to obtain a Room?

Thanks,

Greg


#2

That’s a clean solution, however I don’t know if it satisfies the fact
that “any Room might have many different combinations of Rounds”

Not sure I understand correctly, but if a room can have many
combination of rounds, and each combination of rounds has more than
one round, you could try this:

Room and Round models I assume you already have. A room can have many
round_combinations (create the RoundCombination model with a room_id.
Room :has_many :room_combinations, and
RoomCombination :belongs_to :room). Then create a join table between
round_combinations and rounds (HABTM).

You can use callbacks or validation to limit the relationship to two
rounds maximum.

On Feb 14, 11:42 pm, Maurício Linhares removed_email_address@domain.invalid


#3

Imagining that every round must belong to a room, here’s a simple and
straightforward implementation:

class Room < ActiveRecord::Base

this class needs a max_rounds and rounds_count integer columns

has_many :rounds

def accepts_more_rounds?
max_rounds < rounds_count
end

end

class Round < ActiveRecord::Base

this class needs a room_id integer column

belongs_to :room, :counter_cache => true

validate_on_create :validate_rounds_count

protected

def validate_rounds_count
self.room.reload
unless self.room.accepts_more_rounds?
errors.add( :room, “has already met it’s rounds limit” )
end
end

end

room = Room.create( :max_rounds => 2 )
round_1 = room.rounds.create( :name => ‘round 1’ )
round_2 = room.rounds.create( :name => ‘round 2’ )
round_3 = room.rounds.create( :name => ‘round 3’ ) # this one isn’t
going to be created
round_3.new_record? == true

Maurício Linhares
http://alinhavado.wordpress.com/ (pt-br) | http://blog.codevader.com/
(en)

On Sat, Feb 14, 2009 at 7:53 PM, Greg B.


#4

Cool! Harold, your solution strikes me as being exactly the way to do
it. I’ve implemented it, and things seem to be sailing smoothly.
Thanks a lot to both of you.

Sincerely,

Greg


#5

Ah, so one follow up question: each Competitor in the competition is
signed up for two rounds, and needs to know which room he or she is
assigned. This is a has_one relationship; however, I’m not quite sure
of the right way to map it. Thoughts?

Greg

On Feb 15, 2:49 am, Harold A. Giménez Ch. removed_email_address@domain.invalid


#6

Does a user have many round_combinations? and a round_combination has
many users? Seems like it. If so, first thought is to create another
HABTM table between round_combinations and users. Then
@user.round_combinations.each { |rc| rc.room } gives you the rooms…


#7

Great! Glad it worked.


#8

Actually, by user I mean competitor, but you get the point…


#9

Ah, sorry, my specification was not very good. The Big Picture here
is that we assign each Competitor to a room based on the particular
combination of Rounds he or she is signed up for. So for example, we
could have

r = RoundCombination.new
r.room = room_1
r.rounds = [round_1, round_2]
r.save

Now if c is a Competitor who is signed up for round_1 and round_2, I
would like to be able to do

c.room
#=> room_1

So essentially, a HABTM table does not seem to be the right way of
capturing this relationship. A Competitor “knows” its
RoundCombination because the Competitor is signed up for a particular
combination of Rounds. I’ve managed a provisional, rather inefficient
implementation, of c.room as

class Competitor < ActiveRecord::Base
has_many :rounds

def room
RoundCombination.all.each do |rc|
return rc.room if rc.rounds.sort_by {|r| r.id} == rounds.sort_by
{|r| r.id}
end
nil
end
end

I feel this should be done with some SQL joins or something. Does
Rails provide a relationship to manage this?

Thanks again,

Greg

On Feb 15, 6:06 pm, “Harold A. Gimenez” removed_email_address@domain.invalid


#10

Not quite, unfortunately. The point here (that is making things
complex), is that

round.round_combination

has no semantic. Rather, the semantic is more like

[round_1, round_2].round_combination

That is, an (unordered) set of two rounds determines the
round_combination.

On the flip side, a Competitor has one Room. As motivation, what’s
going on here is that Competitors are competing in a two-round math
competition. They each sign up for two rounds ahead of time, and we
put them in a room determined by which two rounds they signed up for.
(Hence, a Competitor also has one RoundCombination; it’s just not
clear to me how the join query should work… I’m starting to think
maybe I should just write that by hand.) In any case, the code I
provided has the right functionality, but it’s bad in the sense that
it loads all of the RoundCombinations as AR objects each time its
called, which is a lot of overhead…

Greg

On Feb 15, 9:29 pm, Harold A. Giménez Ch. removed_email_address@domain.invalid


#11

From what I understand, one competitor will have as many rooms as
RoundCombinations. Your room definition below would return the first one
it
finds.

I think you can do something like:

@competitor.rounds.each { |round| round.round_combination.room }

Would return an array of rooms for each of @competitor’s rounds.

That sound right?