Last active
July 10, 2018 15:19
-
-
Save ErvalhouS/31f62edb9ea704d3abf8269922ded732 to your computer and use it in GitHub Desktop.
A read-only frontend API abstraction applicable to any application.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
prepend_before_action :set_root_resource | |
before_action :set_object, except: %i[index schema] | |
append_before_action :set_nested_resource, only: %i[nested_index] | |
append_before_action :set_records, only: %i[index nested_index] | |
append_before_action :set_schema, only: %i[schema] | |
include Orderable | |
# GET /:resource | |
# GET /:resource.json | |
def index | |
render json: records_json | |
end | |
# GET /:resource/1 | |
# GET /:resource/1.json | |
def show | |
render json: @object.to_json(include: parsed_include) | |
end | |
# GET /:resource/1/:nested_resource | |
# GET /:resource/1/:nested_resource.json | |
def nested_index | |
render json: records_json | |
end | |
# OPTIONS /:resource | |
# OPTIONS /:resource.json | |
# OPTIONS /:resource/1/:nested_resource | |
# OPTIONS /:resource/1/:nested_resource.json | |
def schema | |
render json: @schemated.to_json | |
end | |
private | |
# Common setup to stablish which model is the resource of this request | |
def set_root_resource | |
@root_resource = params[:resource].classify.constantize | |
end | |
# Common setup to stablish which object this request is querying | |
def set_object | |
id = params[:id] | |
@object = resource.friendly.find(id) | |
rescue NoMethodError | |
@object = resource.find(id) | |
end | |
# Setup to stablish the nested model to be queried | |
def set_nested_resource | |
@nested_resource = @object.send(params[:nested]) | |
end | |
# Used to setup the resource's schema | |
def set_schema | |
@schemated = {} | |
raise ActionController::BadRequest('Invalid resource') unless resource.present? | |
resource.columns_hash.each { |key, value| @schemated[key] = value.type } | |
end | |
# Used to setup the records from the selected resource | |
# that are going to be rendered | |
def set_records | |
@records = resource.order(ordering_params(params)) | |
.ransack(parsed_query).result | |
end | |
# Used to avoid errors in JSON parsing | |
def parsed_query | |
JSON.parse(params[:q]) | |
rescue JSON::ParserError, TypeError | |
{} | |
end | |
# Used to avoid errors in included associations parsing | |
def parsed_include | |
params[:include].split(',') | |
rescue NoMethodError | |
[] | |
end | |
# Parsing of `@records` variable to paginated JSON | |
def records_json | |
@records.paginate(page: params[:page], per_page: params[:per_page]) | |
.to_json(include: parsed_include) | |
end | |
# Reutrns root_resource if nested_resource is not set | |
def resource | |
@nested_resource || @root_resource | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
For what I can see you tried to use @root_resource
and @nested_resource
to differentiate theirs uses.
But, I thought it a little confusing at first sight. First because set_resource
sets @resource
(and @root_resource), but set_nested
also sets @resource
.
What do you think about using only @resource
and changing the method names to set_root_resource
and set_nested_resource
?
Ooops... I think my suggestion don't apply. You set object from root_resource, then set nested_resource, from object.
Look at this diff. Perhaps a better approach (than my last suggestion).
https://gist.github.com/abinoam/762802f4f48e9d1aa98d48940b2fd724/revisions#diff-dd7bb1d0357534ad4ec0f31681fbe64b
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Append and prepend are there to assure the expected execution order, is it a little overkilled?