1

In my Ruby on Rails app, bike rental companies can manage all their bikes (reservations, payments etc.).

Context I would like to offer a bike rental companies (shops) the option to implement a booking form on their own website, so they can let customers make a reservation for a bike.

  • This booking form would then show bike_categories of which bikes are available for a given arrival and departure date.

Question In order to manage this, I would like to generate an API controller action showing the availability for a certain bike_category displaying the count for the number of available bikes belonging to this bike_category.

According to this post

Design RESTful query API with a long list of query parameters

I should be able to deal with queries in my api, but how do I get the queries in my Rails controller?

Code

models

class Shop < ApplicationRecord
  has_many :bike_categories, dependent: :destroy
  has_many :bikes, through: :bike_categories
  has_many :reservations, dependent: :destroy
end

class Reservation < ApplicationRecord
  belongs_to :shop
  belongs_to :bike
end

class Bike < ApplicationRecord
  belongs_to :bike_category
  has_many :reservations, dependent: :destroy
end

class BikeCategory < ApplicationRecord
  belongs_to :shop
  has_many :bikes, dependent: :destroy
end

routes

# api
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      resources :shops, only: [ :show ]
      resources :reservations, only: [ :show, :create ]
      resources :bike_categories, only: [:index, :show, :availability]
    end
  end

controller/api/v1/bike_categories_controller.rb


class Api::V1::BikeCategoriesController < Api::V1::BaseController
  acts_as_token_authentication_handler_for User, only: [:show, :index, availability]

  def availability
    # How to get the bike_category, arrival and departure?
  end

end
techquestion
  • 489
  • 5
  • 20
  • @Int'lManOfCodingMystery or don't use a POST request and send query parameters... This is clearly a case where POST is not suitible as you are not creating a resouce and the action is idempotent. – max Jan 29 '20 at 10:01

2 Answers2

6

Rails provides query string parameters in the params hash which is actually a mashup of:

  • The request body (for POST, PATCH, PUT etc. methods that have a body)
  • The query string
  • Named segments from the path (params[:id] for example).

So there is really nothing to it:

class Api::V1::BikeCategoriesController < Api::V1::BaseController
  acts_as_token_authentication_handler_for User, only: [:show, :index, availability]
  def availability
    # How to get the bike_category, arrival and departure?
    bike_category = params[:bike_categories] 
    arrival = params[:arrival]
    departure = params[:departure]
  end
end

However your routes are not correctly configured. You don't actually have a route for availiblity. The only and except option just limit the output of the default CRUD routes that resources generates. Valid values are thus:

  • show
  • index
  • new
  • create
  • edit
  • update
  • delete

Any other values have no effect at all. If you want to add an additional RESTful route you can do it like so:

resources :bike_categories, only: [:index, :show] do
  # GET .../bike_categories/:bike_category_id/availability 
  get :availability 

  # GET .../bike_categories/:id/availability 
  get :availability, on: :member

  # GET .../bike_categories/availability 
  get :availability, on: :collection
end
max
  • 96,212
  • 14
  • 104
  • 165
2

You can use the has_scope gem.

Inside your Bike model you could create the scope not_reserved (I'm assuming you named your columns of arrive and departure as arrive_at and departure_at:

class Bike < ApplicationRecord
# ...
  scope :not_reserved, -> arrive, departure { left_outer_joins(:reservations).distinct.where("reservations.arrival_at < ? AND reservations.departure_at > ?", departure, arrive) }
  # If you need to return the result for a specific bike category
  scope :by_bike_category, -> bike_category_id { where(bike_category_id: bike_category_id) }
# ...
end

In your BikeCategoriesController:

class Api::V1::BikeCategoriesController < Api::V1::BaseController
  has_scope :not_reserved, using: [:arrive, :departure], type: :hash
  has_scope :by_bike_category

  def availability
    category_availability = apply_scopes(Bike).group(:bike_category_id).count

    render json: category_availability
  end

end

And your query string would be like:

?not_reserved[arrive]=20200110&not_reserved[departure]=20200125&by_bike_category=3
Manoel M. Neto
  • 616
  • 4
  • 5