ruby-on-railsrubyvariablesactiverecordplural

Ruby on Rails Automatic Variable Name (Plural vs Singular)


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

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

Solution

  • 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.