Re: many to many link table compound primary key explosion

I think to begin with, an appropriate change to your data model would
be:

create table answers_questions (
id int not null autoincrement,
question_id int unsigned not null,
answer_id int unsigned not null,
constraint fk_qid foreign key (question_id) references questions(id),
constraint fk_aid foreign key (answer_id) references answers(id),
primary key (id)
) ENGINE=InnoDB;

In general, it’s considered better practice to have primary keys be
‘meaningless’ and not use them to implement what essentially is a
business
rule.

You can then implement uniqueness on the two foreign keys directly in
active
record using the built in validation capabilities it provides to ensure
that
you don’t get duplicates.

For info on validation, see:

 http://api.rubyonrails.com/classes/ActiveRecord/Validations.html

You have a choice to put the validation logic in the database or put it
in the
ActiveRecord models. There are reasons to go either way, but in this
case
pulling the valdation out of the database and putting it in the
ActiveRecord
models looks like it may be easier nd give you more flexibility.


Kevin B.
http://www.kbedell.com

“The future belongs to those who believe in the beauty of their dreams.”

  • Eleanor Roosevelt

----- End forwarded message -----


Kevin B.
http://www.kbedell.com

“The future belongs to those who believe in the beauty of their dreams.”

  • Eleanor Roosevelt

Hi Kevin,

thanks for the advice.

I don’t believe changing my data model is the right thing here. For a
start it’s already helped me identify this potential problem :slight_smile:

Also, those rows are already unique by the combination of answer_id and
question_id. Adding a new primary key called id is meaningless.

An additional problem is that ActiveRecord includes all columns from the
link table when joining the linked table, so the id field would collide
(the link table id would take preference as I understand).

Technically, the primary keys in this instance aren’t implementing a
business rule. The link table should never include duplicate link
records. At the end of the day, I’m trying to do something that
violates this rule, which is wrong. Perhaps the best course of action
is just to figure out a graceful way to handle it in my code. I don’t
know how to override the @question.answers.push method, if this were to
be the best option.

John.
http://johnleach.co.uk

p.s: Having just tested it, I see that without this compound primary
key, rails creates duplicate rows as I suspected.

I agree that putting an id column in the join table is a bad idea
because of
the ‘clobbering’ that will happen. The documentation even mentions
this.
Not to say that you can’t, but you would then have to create a different
relationship between the models

questions (hm)…(bt) answers_questions (bt)…(hm) answers

and answers_questions would become a model

I might recommend the following however

class Question < AR::Base
has_and_belongs_to_many :answers

only add the answer to the question only if the answer is not

already
associated
def add_answer(answer)
answers << answer unless answers.include?(answer)
end
end

a = Answer.create(…)
q = Question.find(1)
q.add_answer(a)

Hi Chris,

thanks, I’ll use that method.

Do you think ActiveRecord’s behaviour is correct? Or should it check
for duplicates itself here? Without a compound primary key this might
have gone undetected and I think DHH’s “single layer of cleverness”[1]
post has people believing the db shouldn’t be checking this stuff.

Should I perhaps file a bug?

John.
http://johnleach.co.uk

[1] http://www.loudthinking.com/arc/000516.html
I’m not suggesting that is what DHH is saying though :slight_smile: