metamaps--metamaps/app/controllers/api/v2/restful_controller.rb
2017-11-25 11:23:47 -08:00

211 lines
6.2 KiB
Ruby

# frozen_string_literal: true
module Api
module V2
class RestfulController < ActionController::Base
include Pundit
include PunditExtra
snorlax_used_rest!
before_action :load_resource, only: %i(show update destroy)
after_action :verify_authorized
def index
authorize resource_class
instantiate_collection
respond_with_collection
end
def create
instantiate_resource
resource.user = current_user if current_user.present?
authorize resource
create_action
respond_with_resource
end
def destroy
destroy_action
head :no_content
end
def catch_404
skip_authorization
render json: { error: '404 Not found' }, status: :not_found
end
private
def accessible_records
if current_user
visible_records
else
public_records
end
end
def current_user
token_user || doorkeeper_user
end
def load_resource
super
authorize resource
end
def resource_serializer
"Api::V2::#{resource_name.camelize}Serializer".constantize
end
def respond_with_resource(scope: default_scope, serializer: resource_serializer,
root: serializer_root)
if resource.errors.empty?
render json: resource, scope: scope, serializer: serializer, root: root
else
respond_with_errors
end
end
def respond_with_collection(resources: collection, scope: default_scope,
serializer: resource_serializer, root: serializer_root)
pagination_link_headers!(pagination(resources))
render json: resources, scope: scope, each_serializer: serializer, root: root,
meta: pagination(resources), meta_key: :page
end
def default_scope
{
embeds: embeds,
current_user: current_user
}
end
def embeds
(params[:embed] || '').split(',').map(&:to_sym)
end
def token_user
token = params[:access_token]
access_token = Token.find_by(token: token)
@token_user ||= access_token.user if access_token
end
def doorkeeper_user
return if doorkeeper_token.blank?
doorkeeper_render_error unless valid_doorkeeper_token?
@doorkeeper_user ||= User.find(doorkeeper_token.resource_owner_id)
end
def permitted_params
@permitted_params ||= PermittedParams.new(params)
end
def serializer_root
'data'
end
def pagination(collection)
return @pagination_data unless @pagination_data.nil?
current_page = (params[:page] || 1).to_i
per = (params[:per] || 25).to_i
total_pages = (collection.total_count.to_f / per).ceil
@pagination_data = {
current_page: current_page,
next_page: current_page < total_pages ? current_page + 1 : 0,
prev_page: current_page > 1 ? current_page - 1 : 0,
total_pages: total_pages,
total_count: collection.total_count,
per: per
}
end
def pagination_link_headers!(data)
base_url = request.base_url + request.path
old_query = request.query_parameters
nxt = old_query.merge(page: data[:next_page]).map { |x| x.join('=') }.join('&')
prev = old_query.merge(page: data[:prev_page]).map { |x| x.join('=') }.join('&')
last = old_query.merge(page: data[:total_pages]).map { |x| x.join('=') }.join('&')
response.headers['Link'] = [
%(<#{base_url}?#{nxt}>; rel="next"),
%(<#{base_url}?#{prev}>; rel="prev"),
%(<#{base_url}?#{last}>; rel="last")
].join(',')
response.headers['X-Total-Pages'] = data[:total_pages].to_s
response.headers['X-Total-Count'] = data[:total_count].to_s
response.headers['X-Per-Page'] = data[:per].to_s
end
def instantiate_collection
collection = accessible_records
collection = yield collection if block_given?
collection = search_by_q(collection) if params[:q]
collection = apply_filters(collection)
collection = order_by_sort(collection) if params[:sort]
collection = collection.page(params[:page]).per(params[:per])
self.collection = collection
end
# override this method to explicitly set searchable columns
def searchable_columns
return @searchable_columns unless @searchable_columns.nil?
columns = resource_class.columns.select do |column|
column.type == :text || column.type == :string
end
@searchable_columns = columns.map(&:name)
end
# e.g. ?q=test&searchfields=name,desc
def searchfields
return searchable_columns if params[:searchfields].blank?
searchfields = params[:searchfields].split(',')
searchfields.select! { |f| searchable_columns.include?(f.to_sym) }
searchfields.empty? ? searchable_columns : searchfields
end
# thanks to http://stackoverflow.com/questions/4430578
def search_by_q(collection)
table = resource_class.arel_table
safe_query = "%#{params[:q].gsub(/[%_]/, '\\\\\0')}%"
search_column = ->(column) { table[column].matches(safe_query) }
condition = searchfields.reduce(nil) do |prev, column|
next search_column.call(column) if prev.nil?
search_column.call(column).or(prev)
end
collection.where(condition)
end
def apply_filters(collection)
# override this function for specific filters
collection
end
def order_by_sort(collection)
builder = collection
sorts = params[:sort].split(',')
sorts.each do |sort|
direction = sort.starts_with?('-') ? 'desc' : 'asc'
sort = sort.sub(/^-/, '')
if resource_class.columns.map(&:name).include?(sort)
builder = builder.order(sort => direction)
end
end
builder
end
def visible_records
policy_scope(resource_class)
end
def public_records
policy_scope(resource_class)
end
end
end
end