Search code examples
ruby-on-railscollection-select

Rails 4 - How to display name of record in index view instead of its id after using collection_select


I have two models, Subject and Course. A Course belongs_to a Subject and a Subject has_many Courses. When creating a new course, I am using collection_select to get all subjects in the database so I can select the Subject which the course belongs to. This is working fine, however im having a problem getting subject.name from displaying in the course index.html page, and instead I can only get its id to appear. I have tried doing <%= course.subject.name %> which gives me an error: undefined method `name' for nil:NilClass. What should I do in order to display the subject.name in the course view page?

Here is my code:

course _form:

  <div class="field">
    <%= f.label "Subject" %>
    <%= collection_select :course, :subject_id, Subject.all, :id, :name %>
  </div>

index.html:

<div class="courses">
  <div class="semesters">
    <h3>Courses</h3>
    <% @grade.courses.each do |course| %>
    <div class="course_group">
      <div class="course_list">
        <h3><%= course.subject.name %></h3>
        <p><%= course.title %></p>
        <%= link_to "View Course", [@grade, course], :class => "button button_orange" %>
        <div class="course_links">
        <% if can? :update, @course %>
          <%= link_to 'Edit', edit_grade_course_path(@grade, course) %> |
        <% end %>
        <% if can? :destroy, @course %>
          <%= link_to 'Destroy', [@grade, course], method: :delete, data: { confirm: 'Are you sure?' } %>
        <% end %>
        </div>
      </div>
      </div>
    <% end %>
  </div>
  <div class="create_course">
    <% if can? :create, @course %>
    <%= link_to 'Add New Course', new_grade_course_path(@grade, @course), :class => "button button_orange" %>
    <% end %>
  </div>
</div>

courses_controller.rb:

class CoursesController < ApplicationController
  before_action :set_course, only: [:show, :edit, :update, :destroy]
  before_action :set_grade
  load_and_authorize_resource
  respond_to :html

  def index
   @grade = Grade.find(params[:grade_id])
   @subjects = Subject.all
   @courses = @grade.courses.all
   # respond_with(@course)
  end
.
.
.
  def course_params
      params.require(:course).permit(:title, :period, :description, :semester, :grade, :grade_id, :video_url, :chapter_id, :subject_id)
  end
end

course.rb :

class Course < ActiveRecord::Base
    # TODO: add validations
    resourcify
    belongs_to :grade
    has_many :chapters
    has_many :packages
    belongs_to :subject
end

subject.rb:

class Subject < ActiveRecord::Base
    has_many :courses
end

Solution

  • I'd say to go for validations than to check for each column's existence; it's simply redundant.

    class Subject < ActiveRecord::Base
      validates :name, presence: true
    end
    

    This way, a subject can't be saved in the database until it has a name, and then in your view you do not need to check for the existence of name field.

    Or if you would like to go for existence check, Rails provides a way for it as well. Here's how:

    course.try(:subject).try(:name) # this code won't crash even if the `name` or `subject` is `nil`.