Skip to content

Instantly share code, notes, and snippets.

@abinoam
Forked from ErvalhouS/frontend_api.rb
Created July 9, 2018 16:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abinoam/762802f4f48e9d1aa98d48940b2fd724 to your computer and use it in GitHub Desktop.
Save abinoam/762802f4f48e9d1aa98d48940b2fd724 to your computer and use it in GitHub Desktop.
A read-only frontend API abstraction applicable to any application.
# frozen_string_literal: true
module Api
module V1
# Controller to consume read-only data to be used on client's frontend
class FrontEndController < ActionController::API
include Orderable
# GET /:resource
# GET /:resource.json
def index
render json: ransack_result.paginate(page: params[:page], per_page: params[:per_page])
.to_json(include: params[:include]&.split(','))
end
# GET /:resource/1
# GET /:resource/1.json
def show
render json: object.to_json(include: params[:include]&.split(','))
end
# GET /:resource/1/:nested
# GET /:resource/1/:nested.json
alias nested_index index
# OPTIONS /:resource
# OPTIONS /:resource.json
# OPTIONS /:resource/1/:nested
# OPTIONS /:resource/1/:nested.json
def schema
render json: schemated.to_json
end
private
# Common setup to stablish which model is the resource of this request
def resource
@resource ||= params[:resource].classify.constantize
end
# Common setup to stablish which object this request is querying
def object
resource_id = params[:id]
@object ||= if resource.respond_to?(:friendly)
resource.friendly.find(resource_id)
else
resource.find(resource_id)
end
end
# Setup to stablish the nested model to be queried
def nested
@nested ||= object.send(params[:nested])
end
# Used to setup the resource's schema
def schemated
raise ActionController::BadRequest('Invalid resource') unless records.present?
@schemated ||= {}.tap do |schemated|
records.columns_hash.each { |key, value| schemated[key] = value.type }
end
end
# Used to setup the records from the selected resource
# that are going to be rendered
def ransack_result
records.order(ordering_params(params))
.ransack(JSON.parse(params[:q])).result
end
def records
@records ||= (params[:nested] ? nested : resource)
end
end
end
end
# frozen_string_literal: true
# This concern is used to provide abstract ordering based on `params[:sort]`
module Orderable
extend ActiveSupport::Concern
SORT_ORDER = { '+' => :asc, '-' => :desc }.freeze
# A list of the param names that can be used for ordering the model list
def ordering_params(params)
# For example it retrieves a list of orders in descending order of total_value.
# Within a specific total_value, older orders are ordered first
#
# GET /orders?sort=-total_value,created_at
# ordering_params(params) # => { total_value: :desc, created_at: :asc }
#
# Usage:
# Order.order(ordering_params(params))
ordering = {}
params[:sort].try(:split, ',').try(:each) do |attr|
attr = parse_attr attr
model = controller_name.titlecase.singularize.constantize
if model.attribute_names.include?(attr)
ordering[attr] = SORT_ORDER[parse_sign attr]
end
end
ordering
end
private
# Parsing of attributes to avoid empty starts in case browser passes "+" as " "
def parse_attr(attr)
'+' + attr.gsub(/^\ (.*)/, '\1') if attr.starts_with?(' ')
end
# Ordering sign parse, which separates
def parse_sign(attr)
attr =~ /\A[+-]/ ? attr.slice!(0) : '+'
end
end
get '/:resource/', to: 'front_end#index', via: :get
get '/:resource/:id', to: 'front_end#show', via: :get
get '/:resource/:id/:nested/', to: 'front_end#nested_index', via: :get
match '/:resource/', to: 'front_end#schema', via: :options
match '/:resource/:id/:nested/', to: 'front_end#schema', via: :options
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment