Tom Hammond
Tom Hammond

Reputation: 6080

Rails Has_Many :Through confusion

I'm working on creating a basic survey app as I'm learning rails. I've setup a has_many through relationship between the surveys as the questions (as questions may be used in multiple surveys). I've been struggling with adding a question to a survey though. Any idea what I need to do to create a new surveytization when creating my new question (and thus adding the question to the survey)? I'm able to do it in the console but am struggling with translating that to the controllers/views/params - if you know of any good documentation about those I'd love to check them out to (but thus far haven't found much).

It seems to error out when I try to assign my @survey variable using the :survey_id in the params I'm sending to the Question controller.

I really appreciate your help!

Question.rb:

class Question < ActiveRecord::Base
  has_many :answers, dependent: :delete_all
  validates :title, presence: true
  has_many :surveytizations
  has_many :surveys, :through => :surveytizations
end

Survey.rb

class Survey < ActiveRecord::Base
  has_many :surveytizations
  has_many :questions, :through => :surveytizations
end

Surveytization.rb:

class Surveytization < ActiveRecord::Base
  has_many :surveys
  has_many :questions
  validates :survey_id, presence: true
  validates :question_id, presence:true
end

SurveyController.rb:

class SurveysController < ApplicationController
  before_action :set_survey, only: [:show, :edit, :update, :destroy]
  before_action :set_question

  # GET /surveys
  # GET /surveys.json
  def index
    @surveys = Survey.all
  end

  # GET /surveys/1
  # GET /surveys/1.json
  def show
  end

  # GET /surveys/new
  def new
    @survey = Survey.new
  end

  # GET /surveys/1/edit
  def edit
  end

  # POST /surveys
  # POST /surveys.json
  def create
    @survey = Survey.new(survey_params)

    respond_to do |format|
      if @survey.save
        format.html { redirect_to @survey, notice: 'Survey was successfully created.' }
        format.json { render action: 'show', status: :created, location: @survey }
      else
        format.html { render action: 'new' }
        format.json { render json: @survey.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /surveys/1
  # PATCH/PUT /surveys/1.json
  def update
    respond_to do |format|
      if @survey.update(survey_params)
        format.html { redirect_to @survey, notice: 'Survey was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @survey.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /surveys/1
  # DELETE /surveys/1.json
  def destroy
    @survey.destroy
    respond_to do |format|
      format.html { redirect_to surveys_url }
      format.json { head :no_content }
    end
  end

  def add_question(question)
    surveytizations.create!(question_id: question.id)
  end

  def remove_question(question)
    surveytizations.find_by(question_id: question.id).destroy
  end

  def find_question(question)
    @question = surveytizations.find_by(question_id: question.id)
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_survey
      @survey = Survey.find(params[:id])
    end

    def set_question
      @question = Question.new
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def survey_params
      params.require(:survey).permit(:title)
    end
end

Survey show.html.erb:

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @survey.title %>
</p>

<%= link_to 'Edit', edit_survey_path(@survey) %> |
<%= link_to 'Back', surveys_path %>

<%= link_to "Add Question", new_question_path(:survey_id => @survey.id)%>

QuestionController:

class QuestionsController < ApplicationController
  before_action :set_question, only: [:show, :edit, :update, :destroy]

  # GET /questions
  # GET /questions.json
  def index
    @questions = Question.all
  end

  # GET /questions/1
  # GET /questions/1.json
  def show
    @answers = @question.answers
  end

  # GET /questions/new
  def new
    @question = Question.new
    @survey = Survey.find(:survey_id)
  end

  # GET /questions/1/edit
  def edit
  end

  # POST /questions
  # POST /questions.json
  def create
    @question = Question.new(question_params)

    respond_to do |format|
      if @question.save
        format.html { redirect_to @question, notice: 'Question was successfully created.' }
        format.json { render action: 'show', status: :created, location: @question }
      else
        format.html { render action: 'new' }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /questions/1
  # PATCH/PUT /questions/1.json
  def update
    respond_to do |format|
      if @question.update(question_params)
        format.html { redirect_to @question, notice: 'Question was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /questions/1
  # DELETE /questions/1.json
  def destroy
    @question.destroy
    respond_to do |format|
      format.html { redirect_to questions_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_question
      @question = Question.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def question_params
      params.require(:question).permit(:title, :single_response, :surveytization)
    end
end

Upvotes: 0

Views: 159

Answers (1)

sonnyhe2002
sonnyhe2002

Reputation: 2121

One problem is your join relation should have belongs_to instead of has_many, to get the has_many through working:

class Surveytization < ActiveRecord::Base
  belongs_to :survey
  belongs_to :question
  validates :survey_id, presence: true
  validates :question_id, presence:true
end

Notice the :survey and :question are singular name in the belongs_to

To Add a question to a survey you can

# create new question or find existing question and store it in @question
@question 

# get the survey into @survey
@survey

@survey.questions << @question

This will magically create the surveytization as well. Now that question will belong to that survey. You Don't even have to call @survey.save! after.

Upvotes: 2

Related Questions