This could have been solved earlier. But I couldn't find a solution.
Lets say I have an abstract base model called 'Question' and sub-
models like Mcq, TrueFalse and FillInTheBlank. Mcq and TrueFalse
models have a has_many relation with another model named 'Choice'
whereas FillInTheBlank model doesn't have this. So I have defined the
choice relation in Mcq and TrueFalse class. While instantiating the
Mcq and TrueFalse classes in my controllers I don't know which class
the instance is going to belong (all I know is it doesn't belong to
FillInTheBlank). So I call it like 'Question.find(params[:id])'. Due
to the 'type' column I get either an 'Mcq' instance or a 'TrueFalse'
instance. Perfect. But the code breaks down when I call choices method
on this new instance. It says "NoMethodError: undefined method
`choices' for #<Mcq:0x3046a84>"
However this problem doesn't come up if I call the find method on the
correct class i.e. Mcq.find() directly. But as I said earlier the
problem is I don't know whether the question is going to be a Mcq or a
TrueFalse.
One dirty solution I tried is to call find on the abstract class
(Question.find()), get value of 'type' attribute and issue another
find statement on the correct class (Mcq.find()). But even that didn't
solve the problem.
Has anybody faced this kind of a problem? Can someone point me in the
right direction?
I had 2 definitions for the classes Mcq and TrueFalse, one in separate
files named mcq.rb and true_false.rb and another definition in the
file question.rb (the abstract class itself). I had to have these
definitions in 2 files because otherwise I couldn't call the concrete
classes directly without first loading the abstract class. But that
apart, my has_many relation was not in the abstract class file. So it
was not getting loaded when I issued the find statement on the
abstract class. But if I issue the find on the concrete class directly
(without calling the abstract class before) it would've loaded the
has_many by then and would load the relations properly. Bit tricky. To
be on the safer side now I have moved all my definitions to the
abstract class file and keeping the concrete class files empty.
the fact that you had 2 definitions each for your concretes looks to be
your issue.. in order to do this and behave well under rails you will
need exactly 3 files and 3 classes..
I am willing to bet that one of your implementations had the choices()
method implemented and the other didn't..
app/models/choice.rb
class Choice < ActiveRecord::Base
abstract true
# no need to implement choices here unless you have a default
implementation
end
app/models/mcq.rb
class Mcq < Choice
def choices
end
end
app/models/true_false.rb
class TrueFalse < Choice
def choices
end
end
Mcq and TrueFalse descend from question and not choice. The correct
structure is below.
app/models/choice.rb
class Question < ActiveRecord::Base
end
app/models/question.rb
class Question < ActiveRecord::Base
self.abstract_class = true
end
class Mcq < Question;end
class TrueFalse < Question;end
app/models/mcq.rb
class Mcq < Question
has_many choices
end
app/models/true_false.rb
class TrueFalse < Question
has_many choices
end
As you can see my question.rb had definitions for all the classes, one
abstract and 2 concrete but they didn't have the has_many relation to
choices. This relation was present in a different file which was not
loaded at the time of calling the method. That was causing the issue.