I am building a rails app that has several quizzes with the exact same structure:
@quiz_bf
or @quiz_bs
new.html.erb
and edit.html.erb
views for each quiz_quiz.html.erb
that stores the actual quiz questions for each quiznew
view (or to the edit
view if people have already taken the quiz, decided by an erb if/else statement) of each quizI successfully managed to set up the first quiz, but as I am new to Ruby I made the (terrible) mistake of setting up a variable name that ended in -s (quiz_bs
) for the singular version of the quiz. This wrought havoc with Ruby's singular vs. plural naming conventions.
I currently have the code for my second quiz (quiz_bf
) set up, but am getting a no method error saying undefined method 'quiz_bfs' for #<User:0x007fb9a3247f98>
on the definition of new quiz in my controller.
Here's my controller:
class QuizBfController < ApplicationController
before_action :require_sign_in
def show
@quiz_bf = QuizBf.find(params[:id])
end
def new
@quiz_bf = current_user.quiz_bfs || current_user.build_quiz_bfs
end
def create
@quiz_bf = QuizBf.new
@quiz_bf.bf01 = params[:quiz_bfs][:bf01]
@quiz_bf.bf02 = params[:quiz_bfs][:bf02]
@quiz_bf.bf03 = params[:quiz_bfs][:bf03]
@quiz_bf.bf04 = params[:quiz_bfs][:bf04]
@quiz_bf.bf05 = params[:quiz_bfs][:bf05]
@quiz_bf.bf06 = params[:quiz_bfs][:bf06]
@quiz_bf.bf07 = params[:quiz_bfs][:bf07]
@quiz_bf.bf08 = params[:quiz_bfs][:bf08]
@quiz_bf.bf09 = params[:quiz_bfs][:bf09]
@quiz_bf.bf10 = params[:quiz_bfs][:bf10]
@quiz_bf.user = current_user
if @quiz_bf.save
flash[:notice] = "Quiz results saved successfully."
redirect_to user_path(current_user)
else
flash[:alert] = "Sorry, your quiz results failed to save."
redirect_to welcome_index_path
end
end
def edit
@quiz_bf = QuizBf.find(params[:id])
end
def update
@quiz_bf = QuizBf.find(params[:id])
@quiz_bf.assign_attributes(quiz_bfs_params)
if @quiz_bf.save
flash[:notice] = "Post was updated successfully."
redirect_to user_path(current_user)
else
flash.now[:alert] = "There was an error saving the post. Please try again."
redirect_to welcome_index_path
end
end
private
def quiz_bfs_params
params.require(:quiz_bfs).permit(:bf01, :bf02, :bf03, :bf04, :bf05, :bf06, :bf07, :bf08, :bf09, :bf10)
end
end
Here's my model (user has_one :quiz_bf
):
class QuizBf < ActiveRecord::Base
before_save :set_bfcode
def set_bfcode
self.bfcode = "#{self.bf01}#{self.bf02}#{self.bf03}-#{self.bf04}#{self.bf05}#{self.bf06}-#{self.bf07}#{self.bf08}#{self.bf09}#{self.bf10}"
end
belongs_to :user
validates :user, presence: true
end
Here is my user model:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, length: { minimum: 1, maximum: 100 }, presence: true
validates :password, presence: true, length: { minimum: 6 }, unless: :password_digest
validates :password, length: { minimum: 6 }, allow_blank: true
validates :email,
presence: true,
uniqueness: { case_sensitive: false },
length: { minimum: 3, maximum: 254 }
has_secure_password
has_one :quiz_bs
has_one :quiz_bf
end
Here are the germane bits of my quiz partial:
<%= form_for @quiz_bf do |f| %>
...
<%= f.submit "Submit Answers" %>
Here's how I'm linking from my new
view:
<%= render partial: "quiz", locals: { url: quiz_bfs_path, method: :post } %>
And my edit
view:
<%= render "quiz", url: quiz_bf_path(@quiz_bf), method: :put %>
And (finally) here's how I'm linking to it from my application
view:
<% if current_user.quiz_bfs == nil? %>
<%= link_to "Body Flexibility Quiz", quiz_bf_path %>
<% else %>
<%= link_to "Body Flexibility Quiz ✓", edit_quiz_bf_path(current_user.quiz_bfs) %>
<% end %>
And my users show
page:
<% if @user.quiz_bfs == nil %>
<p><%= link_to "Test Your Body Flexibility", new_quiz_bf_path %></p>
<% else %>
<h3><%= @user.quiz_bfs.bfcode %></h3>
<p><%= link_to "Retest Results", edit_quiz_bf_path(@user.quiz_bfs) %></p>
<% end %>
I know this code worked successfully for the quiz_bs
, but as you can see in my rake routes (shown below), the plural/singular issue of my idiotic variable name made it hard to see what was actually named what. Can anyone with more experienced ruby eyes than mine show me what I need to change?
quiz_bs GET /quiz_bs(.:format) quiz_bs#index
POST /quiz_bs(.:format) quiz_bs#create
new_quiz_b GET /quiz_bs/new(.:format) quiz_bs#new
edit_quiz_b GET /quiz_bs/:id/edit(.:format) quiz_bs#edit
quiz_b GET /quiz_bs/:id(.:format) quiz_bs#show
PATCH /quiz_bs/:id(.:format) quiz_bs#update
PUT /quiz_bs/:id(.:format) quiz_bs#update
DELETE /quiz_bs/:id(.:format) quiz_bs#destroy
quiz_bf_index GET /quiz_bf(.:format) quiz_bf#index
POST /quiz_bf(.:format) quiz_bf#create
new_quiz_bf GET /quiz_bf/new(.:format) quiz_bf#new
edit_quiz_bf GET /quiz_bf/:id/edit(.:format) quiz_bf#edit
quiz_bf GET /quiz_bf/:id(.:format) quiz_bf#show
PATCH /quiz_bf/:id(.:format) quiz_bf#update
PUT /quiz_bf/:id(.:format) quiz_bf#update
DELETE /quiz_bf/:id(.:format) quiz_bf#destroy
The issue with the undefined method probably has to do with the call being a plural call (quiz_bfs
), which expects that you have a has_many association, while your model only defines a has_one association:
has_one :quiz_bs
has_one :quiz_bf
Either a) the use of quiz_bfs
is a typo and should be quiz_bf
, b) the has_one is incorrect, or c) you don't need the call to current_user.quiz_bfs
at all to assign a value to @quiz_bf
. It's hard to tell without knowing more details, but it looks like you can just remove current_user.quiz_bfs
.
While it wasn't this question, specifically, you mentioned the pain of a resource ending in 's' being treated as a plural. If you haven't seen it yet, you can have your quiz_bs
routes named appropriately. To do so, you can use this form in your config/routes.rb
file:
resources :quiz_bs, as: :quiz_bs
This should give you the route helper names that you would like. You can read more about that in the Overriding Named Helpers section of Rails Routing from the Outside In.
You may want to name your controller QuizBsController
, as well, and you can use the controller:
override on the resources route definition to do that. Try this and see if it gives you the right controller:
resources :quiz_bs, controller: 'quiz_bs'
You can always combine both route methods and use something like this:
resources :quiz_bs, controller: 'quiz_bs', as: :quiz_bs
Check out the Specifying a Controller to Use section of Rails Routing from the Outside In for more information and usage details.