cthulhu
cthulhu

Reputation: 133

How do I build a Rails form using inheritance and nested attributes?

I have a survey app, basically along the lines of Railscast 196, but with one snag: where the Railscast has one Question class, which has_many :answers, I have several:

Question (self.abstract_class = true)
BasicQuestion < Question
MultipleChoiceQuestion < Question

To make this work I had to override the questions getter in Survey, which seems a bit of a kludge but not too bad (is there a standard way to do this?):

Survey.rb
has_many :questions
accepts_nested_attributes_for :questions

def questions # simplified a bit for brevity
  questions = []
  [BasicQuestion, LikertQuestion, MultipleChoiceQuestion].each do |model|
    questions += model.where(:survey_id => self.id)
  end
  questions
end

Survey_Controller.rb
def survey_params
    params.require(:survey).permit(:name, :questions_attributes => [:id, :name])
end

So far, so good. The problem is this:

Again from the Railscast, I have this in surveys/edit.html.erb:

surveys/edit.html.erb
<%= f.fields_for :questions do |builder| %>
    <%= render 'edit_question_fields', f: builder %>
<% end %>

However, this returns a hash of the form:

{ "survey" => { "name" => "Howard", questions_attributes => { "id" => "1", "name" => "Vince" }}}

Rails gives me an error: ActiveRecord::StatementInvalid (Could not find table '') -- presumably, because there is no Questions table (it's an abstract class).

So, how do I fix it? Without abandoning nested_attributes or inheritance entirely, I can think of four ways:

  1. Switch to STI (instead of Question being an abstract class), include the _type field in the params hash, and go from there.
  2. Let Survey deal with each question type separately:

    Survey.rb
    has_many :basic_questions
    accepts_nested_attributes_for :basic_questions
    has_many :multiple_choice_questions
    accepts_nested_attributes_for :multiple_choice_questions
    
    def questions
      # same as before, still comes in handy
    end
    
    surveys/edit.html.erb
    <% @survey.questions.each do |question| %>
      <%= f.fields_for question do |builder| %>
        <%= render 'edit_question_fields', f: builder %>
      <% end %>
    <% end %>`
    

    This almost works, except that now my hash looks like this:

    { "survey" => { "name" => "Howard", "basic_question" => { "id" => "1", "name" => "Vince" }, "multiple_choice_question" => { "id" => "1", "name" => "Naboo" }}}
    

    I need the questions indexed by, e.g., "basic_questions_attributes" instead of "basic_question" -- anyone know how to do this?

  3. Override questions_attributes= in Survey.rb to sort it all out.

  4. Create a new QuestionsFormBuilder object to handle everything, along the lines of "Rails nested attributes form for polymorphic/single table inheritance associations".

Obviously a primary concern is being able to drop in new Question subclasses later (or change the behavior of existing ones) with a minimum of hassle.

At the moment I'm inclined to go with option #3, as it seems simplest and most elegant, however, I'm not sure I'm not missing some better way to do this. (Or somehow screwing up the Question subclassing implementation.) Does anyone have any better ideas or more Rails-like ways of getting this to work?!

Upvotes: 4

Views: 1001

Answers (1)

MatthewFord
MatthewFord

Reputation: 2926

Take a look at using a form object to encapsulate the logic and creation of the questions? http://railscasts.com/episodes/416-form-objects

I would also look at using STI so that your Survey.rb doesn't need to redefine questions

Upvotes: 1

Related Questions