Search code examples
ruby-on-railsmodelassociations

Ruby on Rails associating a User to another models text field


hello I have 2 models User.rb and Guest.rb.

in my app the user is responsible for entering the Guest information. I have a table in my views that will show all guests. I would like each guest row to display the user who has entered their information. Iam having some trouble properly setting up the current_user method in my controller methods. Currently iam grabbing the current_usera nd entering it next to every guest. Thank you so much in advance.

Controller:

  def new
      @guest = Guest.new
  end
   
   def create
   @guest = Guest.new(guest_params)
   
     if @guest.save
         redirect_to  guests_path
     else
        render 'new'
   end
end
   
   def index
       @guests = Guest.all
      @user = current_user
   end
   
 
 def show
   @guest = Guest.find(params[:id])
   @user  = current_user
end
 
 def edit
     @guest = Guest.find(params[:id])
 end
 
 def update
     @guest = Guest.find(params[:id])
     if @guest.update(guest_params)
       flash[:success] = "Profile updated"
       redirect_to @guest
     else
       render 'edit'
     end
   end
 
 
 def destroy
   Guest.find(params[:id]).destroy
   flash[:success] = "User deleted"
   redirect_to guests_url
 end

  
  def guest_params
      params.require(:guest).permit(:experience,:interaction,:mood,:guest_name,:room_num,:arrival_date,:departure_date,:opportunity_string,:employee,:notes,:opportunity)
 end
end

Models:

    has_and_belongs_to_many :users
    
end
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
        
        has_and_belongs_to_many :guests
end

Views:

body{background-color:white;}



</style>

<h1 class="text-center mt-3">Guests</h1>

<div class="container-fluid" style="overflow-x: auto; mb-3">
  
<table class="table table-bordered  text-center ">
  <thead>
    <tr style="background-color:#CFD2CF;font-size:1.4vw">
      <th>GUEST</th>
      <th>EXPERIENCE</th>
      <th>ROOM</th>
       <th>ARRIVAL</th>
       <th>DEPARTURE</th>
       <th>OPPORTUNITY</th>
       <th>EMPLOYEE</th>
       <th>DEPARTMENT</th>
      
    </tr>
  </thead>

  <tbody>
    <% @guests.each do |guest| %>
      <tr style="background-color:<%=guest.mood%>">
     
        <td> <%= link_to guest.guest_name,  "/guests/#{guest.id}" %></td>
        <td><%= guest.experience %></td>
        <td><%= guest.room_num %></td>
        <td><%= guest.arrival_date %></td>
        <td><%= guest.departure_date %></td>
        <td ><%= @user.current_user%></td>
        <td><%=  %></td>
        <td><%= guest.interaction %></td>
       </tr>
    <% end %>
  </tbody>
</table>
</div>


Schema:


  create_table "guests", force: :cascade do |t|
    t.string "experience"
    t.string "interaction"
    t.string "mood"
    t.string "guest_name"
    t.string "room_num"
    t.string "arrival_date"
    t.string "departure_date"
    t.string "opportunity_string"
    t.string "employee"
    t.string "notes"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.integer "user_id", null: false
    t.index ["user_id"], name: "index_guests_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.boolean "superadmin_role", default: false
    t.boolean "supervisor_role", default: false
    t.boolean "user_role", default: true
    t.string "name"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "guests", "users"
end

Solution

  • A few things that will help:

    Devise and current_user:

    Devise takes care of current_user for you, so you should just be able to call current_user in your controllers and views.

    @user.current_user isn't a method unless you've created one in your User model, which I would not recommend doing.

    Don't mix @user with current_user

    It's possible to set @user = current_user, but I think it's a bad practice as it will get confusing quickly

    @user should be tied to the model User and represent the user that the current_user is interacting with.

    For example, a URL like /users/1/edit should set @user = User.find(1).

    The current_user could be someone else who is editing the @user object.

    HABTM Associations

    Given an assigned @user, you can call @user.guests to get all guests associated with that user.

    E.g. for a route that creates the following URL: /users/1/guests then your controller can have something like this:

    # users_controller.rb
    
    class UsersController < ApplicationController
    
    ...
    
      def guests
        @user = User.find(params[:id])
        @guests = @user.guests
      end
    
    end
    

    And the reverse is true as well. For a route like guests/1/users you can call @guest.users.

    But...

    Do you really want a HABTM?

    If a User can create many Guests, but a Guest is never associated with many Users, then this isn't really a "has and belongs to many" relationship. You probably just want a simple has_many belongs_to.

    I revisit this article often as a refresher when I'm considering my relationships.

    Your schema has a user_id on your guest model, which indicates to me that you want to be able to say: "A User has_many guests. A Guest belongs_to a user."

    But you wouldn't really say "A Guest can belong_to many Users"

    Code fixes

    I have a table in my views that will show all guests.

    For ALL guests, this would be the URL /guests, which should map to GuestsController#index:

    # guests_controller.rb
    
    class GuestsController < ApplicationController
    
    ...
    
      def index
       @guests = Guest.all # or some scope like Guest.active
      end
    ...
    end
    

    For guests related to a give user, this would be the URL /users/:id/guests which should map to UsersController#guests:

    # users_controller.rb
    
    class UsersController < ApplicationController
      before_action :set_user, only: %i[show edit update guests]
    ...
      def guests
       @user.guests
       ...
      end
    ...
    
      private
    
      # this method sets @user for all views defined in the `:only` hash of the `before_action` callback.
      def set_user
        @user = User.find(params[:id]
      end
    

    I would like each guest row to display the user who has entered their information.

    Since you have a user_id field on Guest, if you switch to a has_many belongs_to relationship, then you can just call the user:

    <tbody>
      <% @guests.each do |guest| %>
        <tr style="background-color:<%=guest.mood%>">
       
          <td> <%= link_to guest.guest_name,  "/guests/#{guest.id}" %></td>
          <td><%= guest.experience %></td>
          <td><%= guest.room_num %></td>
          <td><%= guest.arrival_date %></td>
          <td><%= guest.departure_date %></td>
          <td ><%= guest.user%></td> <!-- guest.user instead of @user.current_user -->
          <td></td>
          <td><%= guest.interaction %></td>
         </tr>
      <% end %>
    </tbody>
    

    Extra credit: use Includes to pre-load associations

    Also, as a pro tip, calling guest.user could get slow because each guest record needs to make a call to the User table.

    Rails offers eager loading for just this situation.

    Change @guests = Guest.all to @guest = Guest.includes(:user).all and Rails will handle the rest.