Search code examples
ruby-on-railsjsonapi-design

How to deal with API call from website and consequently show information belonging to the website?


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

Goal

I would like to offer a bike rental companies the option to implement a booking form on their own website, so they can let customers create a reservation for a specific bike.

This booking form would then take available bikes from my Rails app to their website and consequently send new reservation data back to the Rails app.

Question

How can I link the website of a shop, to a specific Shop within my application. Thereby, making sure a specific website indeed should be able to GET information belonging to a Shop.

Code

models

class Shop < ApplicationRecord
  has_many :bike_categories, dependent: :destroy
  has_many :bikes, through: :bike_categories
  has_many :user_shops, dependent: :destroy
  has_many :users, through: :user_shops
  has_many :reservations, dependent: :destroy
  accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end

class User < ApplicationRecord
  has_many :user_shops, dependent: :destroy
  has_many :shops, through: :user_shops
  accepts_nested_attributes_for :user_shops
  enum role: [:owner, :admin, :employee, :accountant, :demo, :app_owner]
end

class Reservation < ApplicationRecord
  belongs_to :shop
  belongs_to :bike
end

controllers/api/v1/reservations_controller

  def create
    # How to know/specify which shop?
    @shop = Shop.new(shop_params)
    authorize @shop
    if @shop.save
      render :show, status: :created
    else
      render_error
    end
  end

Solution

  • This is your textbook example of a nested resource. In REST a nested resouces is nested in the path of another resource:

    /authors/1/books
    /countries/uk/cities
    /blogs/1/posts/2
    

    The genius here is that the path itself describes the relation between the resources.

    You can make the route nested by passing a block to resources:

    namespace :api do
      namespace :v1 do
        resources :shops do 
          resources :reservations, shallow: true
        end
      end
    end
    

    The shallow option makes only the collection routes nested (new, create, index) which is usually a good thing since records have a unique id anyways which they can be fetched through.

    /blogs/1/posts/2 is an example of deeply nested route. If the id is unique we should be able to get the exact same resource through /posts/2 which simplefies the code greatly as it does not need to know about the blog.

    module API
      module V1
        class ReservationsController < ApiController
          before_action :set_shop, only: [:create, :index]
    
          # GET /api/v1/shops/1/resevations
          def index
            @reservations = @shop.reservations
          end 
    
          # POST/api/v1/shops/1/resevations
          def create
            @reservation = @shop.reservations.new(reservation_params)
            # ...
          end
    
          # ...
          private
    
          def set_shop
             @shop = Shop.includes(:reservations).find(params[:shop_id])
          end
    
          # ...
        end
      end
    end