-
-
Save ErvalhouS/31f62edb9ea704d3abf8269922ded732 to your computer and use it in GitHub Desktop.
| # 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 |
| # 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 |
Cool and (overall) clean code. 👏
I don't use append, prepend, at before_actions oftenly. What did you used them for? (I got curious about it).
Append and prepend are there to assure the expected execution order, is it a little overkilled? ![]()
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
The only dependencies here are: