Search code examples
ruby-on-railsnested-routes

Getting the ID of a Model that Uses Nested Routes and Permalinks in Rails


So I had my app set up with ids like so:

  resources :studios do
     resources :bookings
  end

This gave me the route to the index (which later I'm going to use json for to get calendars for each studio.

 studio_bookings GET    /studios/:studio_id/bookings(.:format)          bookings#index

This is good, but I wanted to get rid of the ID and use a permalink instead, just for a friendlier URL.

Change to:

  namespace :studio, :path =>'/:permalink' do
     resources :bookings
  end

Now I'm getting

studio_bookings GET   /:permalink/bookings(.:format)      studio/bookings#index

Great! this is how I want my url to look, however, now the :id isn't anywhere in the route so... I get

Couldn't find Booking without an ID

It isn't even being passed. Is there a way to pass the :id in with the url without it being actually USED in the url? Otherwise, do I change the primary key from :id to :permalink in order to fix this?

I tried changing my controller from

    @studio = Studio.find(params[:id])

to

    @studio = Studio.find(params[:permalink])

but that gives me

Couldn't find Booking with 'id'=40frost 

Which tells me what I'm doing isn't really meant to be done? It's trying to put the permalink as the id, so even though I'm telling rails to look for the permalink, it's still seemingly looking it up as an ID.

Hopefully my problem is clear: essentially - how can I pass the id so it knows which studio without displaying it in the URL. If there's some controller magic I can do instead that would be convenient.

Here's my controller for good measure

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

  # GET /bookings
  # GET /bookings.json
  def index
    @studio = Studio.find(params[:permalink])
    @bookings = Booking.where("studio_id => '@studio.id'")
  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 action: 'show', status: :created, location: @booking }
      else
        format.html { render action: '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 { head :no_content }
      else
        format.html { render action: '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 }
      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[:permalink])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def booking_params
      params.require(:booking).permit(:start_time, :end_time, :studio_id, :engineer_id, :title, :allDay)
    end
end

Solution

  • You could just do

    self.primary_key = 'permalink' 
    

    in your Studio model, or you could do

    def index
      @studio = Studio.find_by permalink: params[:permalink]
      @bookings = Booking.where(studio_id: @studio.id)
    end
    

    depends if you just want to locally change the behavior or adress the Studio model by permalink always.

    Hope that helps!