user4227507
user4227507

Reputation:

Why can I not get data from an associated table?

In my Ruby on Rails application I am trying to display the first_name and last_name of the user who made a booking.

On the bookings/index.html.erb I have this:

<% @bookings.each do |booking| %>
                                    <tr>
                                        <td><%= booking.user.first_name %> <%#= booking.user.last_name %></td>
                                        <td><%= booking.showing.film.title %></td>
                                        <td><%= booking.showing.show_date %></td>
                                        <td><%= booking.showing.show_time.strftime("%H:%M") %></td>
                                        <td></td>
                                        <td><%= booking.seat.row_letter %><%= booking.seat.number %></td>
                                        <td style="padding-right:65px">
                                            <%#= link_to 'Edit', edit_user_path(user) %>  
                                            <%#= link_to 'Show', user_path(user) %>  
                                            <%#= link_to 'Destroy', user_path(user), method: :delete, data: { confirm: 'Are you sure?' } %>
                                        </td>
                                    </tr>
                                <% end %>

The booking.showing.film.title and the code below that works but with the booking.user.first_name I get the error:

NoMethodError in Bookings#index     
undefined method `first_name' for nil:NilClass

But my models I think are set up correctly, user.rb:

has_many :bookings

bookings.rb:

belongs_to :user

bookings_controller:

class BookingsController < ApplicationController
  before_action :set_booking, only: [:show, :edit, :update, :destroy]

  # GET /bookings
  # GET /bookings.json
  def index
    @bookings = Booking.all
  end

  # GET /bookings/1
  # GET /bookings/1.json
  def show
  end

  # GET /bookings/new
  def new
    @booking = Booking.new
  end

  # GET /bookings/1/edit
  def edit
  end

  # POST /bookings
  # POST /bookings.json
  def create
    @booking = Booking.new(booking_params)

    respond_to do |format|
      if @booking.save
        format.html { redirect_to @booking, notice: 'Booking was successfully created.' }
        format.json { render :show, status: :created, location: @booking }
      else
        format.html { render :new }
        format.json { render json: @booking.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /bookings/1
  # PATCH/PUT /bookings/1.json
  def update
     respond_to do |format|
      if @booking.update(booking_params)
        format.html { redirect_to @booking, notice: 'Booking was successfully updated.' }
        format.json { render :show, status: :ok, location: @booking }
      else
        format.html { render :edit }
        format.json { render json: @booking.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /bookings/1
  # DELETE /bookings/1.json
  def destroy
     @booking.destroy
    respond_to do |format|
      format.html { redirect_to bookings_url, notice: 'Booking was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_booking
      @booking = Booking.find(params[:id])
    end

   # Never trust parameters from the scary internet, only allow the white list through.
    def booking_params
      params.require(:booking).permit(:showing_id, :user_id, :seat_id)
    end
end

Am I missing something? The attribute names and table names are correct.

Upvotes: 0

Views: 61

Answers (3)

Mohammad AbuShady
Mohammad AbuShady

Reputation: 42909

Using more than one dot is a violation of Law of Demeter and is considered as a bad practice. To follow the Law of Demeter, you can delegate the name to the user model and create a instance method in the user class to return the full name,

booking class

class Booking < ActiveRecord::Base
  belongs_to :user
  delegate :full_name, to: :user, prefix: true, allow_nil: true
end

user class

class User < ActiveRecord::Base
  has_many :bookings

  def full_name
    "#{first_name} #{last_name}"
  end
end

the view

<td><%= booking.user_full_name %></td>

If the user is nil this function will return gracefully return nil without complaining about not having a user.

It's always better to delegate the method calls ( don't mean delegate as a keyword specifically ) rather than call wit so many dots, take booking.showing.film.title for example, there's so many things that could go wrong with this, if booking or showing or film changed any attribute you'll need to keep iterating on all your views and fix every one of them every time, instead try to create a centralized function called for example booking.film_title, then you only need to maintain it in a single place.

Upvotes: 1

Nermin
Nermin

Reputation: 6100

You have correct set up associations. But question is have you add user_id in your booking record, when you created it.

<% @bookings.each do |booking| %>
  <tr>
     <td><%= booking.user.try(:first_name) %> <%#= booking.user.try(:last_name) %></td>
     <td><%= booking.showing.film.title %></td>
     <td><%= booking.showing.show_date %></td>
     <td><%= booking.showing.show_time.strftime("%H:%M") %></td>
     <td></td>
     <td><%= booking.seat.row_letter %><%= booking.seat.number %></td>
     <td style="padding-right:65px">
       <%#= link_to 'Edit', edit_user_path(user) %>  
       <%#= link_to 'Show', user_path(user) %>  
       <%#= link_to 'Destroy', user_path(user), method: :delete, data: { confirm: 'Are you sure?' } %>
     </td>
  </tr>
<% end %>

using method try will ensure that error does not occur

booking.user.try(:first_name) # display user first name if exist
booking.user.try(:last_name)  # display user last  name if exist

Open rails c and check which your booking record does not have user_id

Upvotes: 0

Rajarshi Das
Rajarshi Das

Reputation: 12340

because the user is nil so booking.user throws the error

Simply try this booking.try(:user).try(:first_name)

 <% @bookings.each do |booking| %>
                                    <tr>
                                        <td><%= booking.try(:user).try(:first_name) %> <%= booking.try(:user).try(:last_name) %></td>
                                        <td><%= booking.showing.film.title %></td>
                                        <td><%= booking.showing.show_date %></td>
                                        <td><%= booking.showing.show_time.strftime("%H:%M") %></td>
                                        <td></td>
                                        <td><%= booking.seat.row_letter %><%= booking.seat.number %></td>
                                        <td style="padding-right:65px">
                                            <%#= link_to 'Edit', edit_user_path(user) %>  
                                            <%#= link_to 'Show', user_path(user) %>  
                                            <%#= link_to 'Destroy', user_path(user), method: :delete, data: { confirm: 'Are you sure?' } %>
                                        </td>
                                    </tr>
                                <% end %>

For more information of nil handling of click try

Upvotes: 0

Related Questions