Liz Liz - 5 months ago 17
Ruby Question

Ruby on Rails Automatic Variable Name (Plural vs Singular)

I am building a rails app that has several quizzes with the exact same structure:


  • A name such as
    @quiz_bf
    or
    @quiz_bs

  • new.html.erb
    and
    edit.html.erb
    views for each quiz

  • A partial
    _quiz.html.erb
    that stores the actual quiz questions for each quiz

  • A model and controller for each separate quiz

  • Two places (the main nav and a user profile page) hold a link to the
    new
    view (or to the
    edit
    view if people have already taken the quiz, decided by an erb if/else statement) of each quiz



I 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

Answer

Undefined method error

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.

Bonus on routes

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.