mazing
mazing

Reputation: 743

Rails 5 Showing previous or next nested resource of only the parent resource

I have a resource PracticeQuiz which has a nested child called PracticeQuestion. I can successfully create both of them, and can go to the next question, but what I would like is that when a user is taking a quiz, the "previous" and "next" buttons should only show questions that are in the parent id.

For example:

foo.com/practice_quizzes/mountains/practice_questions/1-11,24,25

The user should be able to go from question 1-11, then at 11 it should go to 24,and if the user wants back to 11 once they get to question 24. At question 25 it should either not show the button or say that there are no more questions.

foo.com/practice_quizzes/fruits/practice_questions/12-23,26,27,31

foo.com/practice_quizzes/animals/practice_questions/28-29,30,32

(Please note that this is about the amount of questions in a quiz, because questions will be deleted or updated or changed in the future. I don't want to make sorting ID based, unless this is a bad idea and i don't know why)

What I need is for the user to be able to navigate the show.html.erb page by clicking previous or next, and ONLY be shown the questions that are associated with a quiz, no matter what their ID is, and for the app to not crash when it detects that there is no question at the very first or last, or if a new question is being added.

Here's what I've tried so far:

views/practice_question.show.html.erb

<%= link_to 'next', practice_quiz_practice_question_path(@practice_quiz, @practice_question.next) %>

models/practice_question.rb

class PracticeQuestion < ApplicationRecord
  belongs_to :user, optional: true
  belongs_to :practice_quiz

  def previous
    PracticeQuestion.where("practice_quiz_id = ? AND id < ?", self.practice_quiz.id, self.id).first
  end

  def next
    PracticeQuestion.where("practice_quiz_id = ? AND id > ?", self.practice_quiz.id, self.id).first
  end

end

models/practice_quiz.rb

class PracticeAnswer < ApplicationRecord
  belongs_to :practice_question
end

controllers/practice_questions_controller.rb

class PracticeQuestionsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_practice_quiz, only: [:show, :edit, :update, :destroy]
  before_action :set_practice_question, only: [:show, :edit, :update, :destroy]

  def index
    if params[:practice_quiz_id]
      @practice_questions = PracticeQuiz.friendly.find(params[:practice_quiz_id]).practice_questions
    else
      @practice_questions = PracticeQuestion.all
    end

    @all_practice_questions = PracticeQuestion.all
  end

  def show
    @current_practice_question = PracticeQuestion.find(params[:id])
  end

  def new
    @practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
    @practice_question = PracticeQuestion.new
  end

  def edit
  end

  def create
    @practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
    @practice_question = @practice_quiz.practice_questions.build(practice_question_params)
    @practice_question.user = current_user

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

  private
    def set_practice_question
      @practice_question = PracticeQuestion.find(params[:id])
    end

    def set_practice_quiz
        @practice_quiz = PracticeQuiz.friendly.find(params[:practice_quiz_id])
    end

    def practice_question_params
        params.require(:practice_question).permit(:question, :explanation, :flagged, practice_questions_attributes: [:answer])
     end
end

How can I do previous/next for practice_questions to ONLY show questions that are associated with the id of its parent? And to avoid any errors/pitfalls such as reaching the first or last question of the quiz?

Thanks very much in advance :)

P.S - Some have said this is not a nested resource as far as controllers/models/views go, but In my routes.rb i did this, so doesn't that make it a nested resource?

  resources :practice_quizzes do 
    resources :practice_questions
  end

Upvotes: 0

Views: 59

Answers (1)

Pablo
Pablo

Reputation: 3005

I imagine two options:

You can use the index in the Quiz controller to get all Questions for this Quiz, and show them in the index view with pagination. If you set pagination to 1 item per page, you get exactly what you want.

PracticeQuizzesController

def show
  @quiz = PracticeQuizz.find_by(id: params[:id])
  @questions = @quiz.practice_questions.paginate(:page => params[:page], :per_page => 1)
end

views/practice_quizzes/show.html.erb

<%= @quiz.name %>
<%= @quiz.description %>
<%= will_paginate @questions %>
<%= render @questions %>

views/practice_questions/_practice_question.html.erb

<%= practice_question.text %>
etc...

Second option

In the show method for the Question, get the next and previous questions, to show on the show view

PracticeQuestionsController

def show
  @question = PracticeQuestion.find_by(id: params[:id])
  @next = @question.practice_quizz.practice_questions.where("id > ?", @question.id).order("id").first
  @prev = @question.practice_quizz.practice_questions.where("id < ?", @question.id).order("id desc").first
end

views/practice_questions/show.html.erb

<% if @next %>
  <%= link_to 'next', practice_quiz_practice_question_path(@practice_quiz, @next) %>
<% end %>

Upvotes: 1

Related Questions