Reputation: 8982
Given the following associations, I need to reference the Question
that a Choice
is attached through from the Choice
model. I have been attempting to use belongs_to :question, through: :answer
to perform this action.
class User
has_many :questions
has_many :choices
end
class Question
belongs_to :user
has_many :answers
has_one :choice, :through => :answer
end
class Answer
belongs_to :question
end
class Choice
belongs_to :user
belongs_to :answer
belongs_to :question, :through => :answer
validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
end
I am getting
NameError uninitialized constant
User::Choice
when I try to do current_user.choices
It works fine, if I don't include the
belongs_to :question, :through => :answer
But I want to use that because I want to be able to do the validates_uniqueness_of
I am probably overlooking something simple. Any help would be appreciated.
Upvotes: 174
Views: 141709
Reputation: 5212
Just use has_one
instead of belongs_to
in your :through
, like this:
class Choice
belongs_to :user
belongs_to :answer
has_one :question, :through => :answer
end
Unrelated, but I'd be hesitant to use validates_uniqueness_of instead of using a proper unique constraint in your database. When you do this in ruby you have race conditions.
Upvotes: 160
Reputation: 10111
So you cant have the behavior that you want but you can do something that feels like it. You want to be able to do Choice.first.question
what I have done in the past is something like this
class Choice
belongs_to :user
belongs_to :answer
validates_uniqueness_of :answer_id, :scope => [ :question_id, :user_id ]
...
def question
answer.question
end
end
this way the you can now call question on Choice
Upvotes: 3
Reputation: 5649
You can also delegate:
class Company < ActiveRecord::Base
has_many :employees
has_many :dogs, :through => :employees
end
class Employee < ActiveRescord::Base
belongs_to :company
has_many :dogs
end
class Dog < ActiveRecord::Base
belongs_to :employee
delegate :company, :to => :employee, :allow_nil => true
end
Upvotes: 433
Reputation: 18198
My approach was to make a virtual attribute instead of adding database columns.
class Choice
belongs_to :user
belongs_to :answer
# ------- Helpers -------
def question
answer.question
end
# extra sugar
def question_id
answer.question_id
end
end
This approach is pretty simple, but comes with tradeoffs. It requires Rails to load answer
from the db, and then question
. This can be optimized later by eager loading the associations you need (i.e. c = Choice.first(include: {answer: :question})
), however, if this optimization is necessary, then stephencelis' answer is probably a better performance decision.
There's a time and place for certain choices, and I think this choice is better when prototyping. I wouldn't use it for production code unless I knew it was for an infrequent use case.
Upvotes: 3
Reputation: 917
It sounds like what you want is a User who has many Questions.
The Question has many Answers, one of which is the User's Choice.
Is this what you are after?
I would model something like that along these lines:
class User
has_many :questions
end
class Question
belongs_to :user
has_many :answers
has_one :choice, :class_name => "Answer"
validates_inclusion_of :choice, :in => lambda { answers }
end
class Answer
belongs_to :question
end
Upvotes: 1
Reputation: 4964
A belongs_to
association cannot have a :through
option. You're better off caching the question_id
on Choice
and adding a unique index to the table (especially because validates_uniqueness_of
is prone to race conditions).
If you're paranoid, add a custom validation to Choice
that confirms that the answer's question_id
matches, but it sounds like the end user should never be given the opportunity to submit data that would create this kind of mismatch.
Upvotes: 65
Reputation: 1335
The has_many :choices
creates an association named choices
, not choice
. Try using current_user.choices
instead.
See the ActiveRecord::Associations documentation for information about about the has_many
magic.
Upvotes: 0