Search code examples
ruby-on-railsrubyparent-childruby-on-rails-6nested-object

How to list all child objects for all parent objects?


What I have at the moment is pretty standard set of code, where all child objects can be list under only their parent objects.

customer.rb

class Customer < ApplicationRecord
  has_many :bookings, dependent: :delete_all
end

booking.rb

class Booking < ApplicationRecord
  belongs_to :customer
  has_many_attached :images
end

routes.rb

Rails.application.routes.draw do
  resources :customers do
    resources :bookings
  end
end

bookings_controller.rb

This has been automatically generated. I only removed comments and json related lines.

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

  def index
    @bookings = Booking.all.with_attached_images
  end

  def show; end

  def new
    @booking = @customer.bookings.build
  end

  def edit; end

  def create
    @booking = @customer.bookings.build(booking_params)

    respond_to do |format|
      if @booking.save
        format.html { redirect_to @customer, notice: 'Booking was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  def update
    respond_to do |format|
      if @booking.update(booking_params)
        format.html { redirect_to [@customer, @booking], notice: 'Booking was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  def destroy
    @booking.destroy
    respond_to do |format|
      format.html { redirect_to customer_bookings_url, notice: 'Booking was successfully destroyed.' }
    end
  end

  private

  def set_customer
    @customer = Customer.find(params[:customer_id])
  end

  def set_booking
    @booking = @customer.bookings.find(params[:id])
  end

  def booking_params
    params.require(:booking).permit(:name, :category, :rooms, :wifi, :phone, :address, :description, :available, :check_in, :check_out, :customer_id, images: [])
  end
end

I want to list all child objects for all parent objects.

I guess, I will have to modify routes as follows

routes.rb

Rails.application.routes.draw do
  root 'customers#index'

  resources :customers do
    resources :bookings
  end
  resources :bookings
end

I will also need to modify bookings_controller.rb

  1. By commenting out the line before_action :set_customer, otherwise I will get an error like Couldn't find Customer without an ID

  2. And I will have to put @customer = Customer.find(params[:customer_id]) for all methods except index. Which means I won't be following DRY concept...

Any other better approach to solve this?


Solution

  • Your approach is the best already in my opinion, just need to utilize Rails helpers correctly to keep your code DRY.

    By commenting out the line before_action :set_customer, otherwise I will get an error like Couldn't find Customer without an ID

    And I will have to put @customer = Customer.find(params[:customer_id]) for all methods except index. Which means I won't be following DRY concept...

    NO you don't have to.

    If the index action of customers/bookings_controller is not used anywhere else then just remove that action from the controller file and specify the same in the route file as:

    resources :customers do
      resources :bookings, except: :index
    end
    

    If the index action is still being used in other places then Rails callbacks can be declared with except option as below to specify that the set_customer will be called for all actions except the index.

    before_action :set_customer, except: :index
    

    More about Rails Controller Callback options here

    Other points that you may want to check:

    1. dependent: :delete_all. With this, there will be orphan active_storage_attachments records in your db when you delete a customer. Because it triggers the callback that deletes only the associated bookings when leave the attached images of those bookings untouched. Reference
    2. resources :bookings (last line of your route file). If you only have the index action in the controller, you should declare the same here also as resources :bookings, only: :index