Files
dunlop-ember/lib/dunlop/ember/api_base_controller.rb
T

271 lines
8.7 KiB
Ruby

module Dunlop::Ember::ApiBaseController
extend ActiveSupport::Concern
included do
cattr_accessor :predicate_end_matcher
before_action :authenticate_user_from_token!
before_action :authenticate_user!
before_action :set_current_user
before_action :find_record, only: %i[show update destroy]
respond_to :json, :csv
rescue_from CanCan::AccessDenied do
render json: {errors: [{status: 403, detail: 'unauthorized'}]}, status: 403
end
rescue_from ActiveRecord::RecordNotFound do
render json: {data: nil}, status: 404
end
end
def index
index_safeguard record_class
result = record_scope.search(query).result
result = result.includes(*FlatKeys.as_nested_structure(index_include_relations)) if index_include_relations.present?
result = result.uniq if params[:uniq].present?
if index_pagination?
paginated = result.page(params[:page]).per(params[:per_page])
render json: paginated, meta: index_meta_params.merge(total_pages: paginated.total_pages, total_count: paginated.total_count)
else
render json: result, meta: index_meta_params
end
end
def show
show_safeguard @record
render json: @record
end
def create
@record ||= record_scope.new(record_params)
create_safeguard @record
return if performed?
if @record.save
render json: @record
else
render json: {errors: @record.errors.map{|attr, errors| {attr => errors.to_s}}}, status: :unprocessable_entity
end
end
def update
update_safeguard @record
return if performed?
@record.update_attributes record_params
render json: @record
end
def destroy
destroy_safeguard @record
@record.destroy
head :no_content
end
private
# used to include relations on database lookup
# has form:
# ['comments.replies', 'authors.orginizations']
#
def include_relations
return [] unless includes = params[:include].presence
includes.split(',')
end
%i[index show].each do |action|
define_method "#{action}_include_relations" do
include_relations
end
end
def publish(event, data = {})
return
data = data.merge(event: event)
data[:client_id] = request.headers['HTTP_CLIENT_ID'] if request.present?
FayeRails.client(nil).publish('/activity', data)
end
def index_safeguard(model)
# can be overloaded
end
def show_safeguard(record)
# can be overloaded
record_safeguard(record)
end
def create_safeguard(record)
# can be overloaded
record_safeguard(record)
end
def update_safeguard(record)
# can be overloaded
record_safeguard(record)
end
def destroy_safeguard(record)
# can be overloaded
record_safeguard(record)
end
def record_safeguard(record)
# can be overloaded
return
render json: {}, status: :unauthorized unless authorized_ids(record).include?(current_user.id)
end
def authorized_ids(*)
# can be overloaded
[]
end
def authenticate_user_from_token!
# https://romulomachado.github.io/2015/09/28/using-ember-simple-auth-with-devise.html
authenticate_with_http_token do |token, options|
user_email = options[:email].presence
user = user_email && User.find_by_email(user_email)
if user && Devise.secure_compare(user.authentication_token, token)
sign_in user, store: false
end
end
end
public def record_class
deducted_class_name = controller_path.sub(/api\//,'').classify
deducted_class_name.safe_constantize || deducted_class_name.demodulize.constantize
end
# basic scope for find and index. Can be overloaded to add extra security restrictions
def record_scope
record_class.all
end
def find_record(options = {})
scope = record_scope
scope = scope.includes(*Array.wrap(options[:includes])) if options[:includes]
@record = scope.find(params[:id])
end
def record_params
#params.require(controller_path.sub(/^api\//, '').singularize).permit(*permitted_params)
params.require(controller_path.sub(/api\//, '').singularize).permit(*permitted_params)
end
def permitted_params
[]
end
# used for serialization
def serialize_options
return {} unless includes = params[:include].presence
{include: includes.split(',')}
end
# map action specific serialize options to general serialize options
%i[index show create update].each do |action|
define_method "#{action}_serialize_options" do
serialize_options
end
end
# This method can be overloaded by the including controller to enrich the meta information of the collection
# with other meta-data
def index_meta_params
{}
end
# predicate_end_matcher =>
# /(_eq|_eq_any|_eq_all|_not_eq|_not_eq_any|_not_eq_all|_matches|_matches_any|_matches_all|
# _does_not_match|_does_not_match_any|_does_not_match_all|_lt|_lt_any|_lt_all|_lteq|_lteq_any|
# _lteq_all|_gt|_gt_any|_gt_all|_gteq|_gteq_any|_gteq_all|_in|_in_any|_in_all|_not_in|_not_in_any|
# _not_in_all|_cont|_cont_any|_cont_all|_i_cont|_i_cont_any|_i_cont_all|_not_cont|_not_cont_any|
# _not_cont_all|_i_not_cont|_i_not_cont_any|_i_not_cont_all|_start|_start_any|_start_all|_not_start|
# _not_start_any|_not_start_all|_end|_end_any|_end_all|_not_end|_not_end_any|_not_end_all|
# _true|_not_true|_false|_not_false|_present|_blank|_null|_not_null)$/
#TODO: make me pretty, specs are in place
def query
self.class.predicate_end_matcher ||= Regexp.new("(_#{Ransack.predicates.keys.join('|_')})$")
mapped_query = {}
(params[:q] || {}).each do |key, value|
# not setting the matcher means equality
key = "#{key}_eq" unless %w[s sorts].include?(key) or record_class.try(:ransackable_scopes).try(:include?, key.to_sym) or key =~ self.class.predicate_end_matcher # expect equality when no predicate is given
if key =~ /^type_/
# map type to sti_type
mapped_value = case value
when Array then value.map{|v| "#{record_class.name}::#{v.to_s.gsub('-', '_').classify}"}
else
"#{record_class.name}::#{value.to_s.gsub('-', '_').classify}"
end
mapped_query["sti_#{key}"] = mapped_value
else
mapped_query[key] = value
end
end
mapped_query
end
def _render_with_renderer_json(resource, options = {})
if resource == nil
return params[:id].blank? ? '{"data":[]}' : '{"data":null}'
end
return resource.to_json if resource.is_a?(Hash)
return resource if resource.is_a?(String)
return resource.to_json if resource.try(:custom_data?)
action_serialize_options_method = :"#{action_name}_serialize_options"
#action_serialize_options = respond_to?(action_serialize_options_method) ? __send__(action_serialize_options_method) : {}
action_serialize_options = __send__(action_serialize_options_method) rescue {}
action_serialize_options[:include] = options[:include] if options.has_key?(:include)
action_serialize_options[:meta] = options[:meta] if options.has_key?(:meta)
serializer_class = if options.has_key?(:serializer)
options.delete(:serializer)
elsif action_serialize_options[:serializer].present?
action_serialize_options[:serializer]
elsif resource.is_a?(ActiveRecord::Base)
#infer based on controller path replacing actual controller part with resouce part /workflow_step_definitions/:id/table
"#{self.class.name.deconstantize}::#{resource.class.name.demodulize}Serializer".constantize
elsif resource.is_a?(Array) and resource.first.is_a?(ActiveRecord::Base)
# infer based in first element of collection
"#{self.class.name.deconstantize}::#{resource.first.class.name.demodulize}Serializer".constantize
else
# infer based on controller path
"#{controller_path.underscore.classify}Serializer".constantize
end
serializer = if resource.respond_to?(:each)
ActiveModel::Serializer::CollectionSerializer.new(resource, action_serialize_options.merge(serializer: serializer_class)) #TODO: fix index includes
else
serializer_class.new(resource, action_serialize_options)
end
adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer, action_serialize_options)
res = {}
time = Benchmark.ms { res = adapter.as_json }
logger.info "Generated serialized output in #{time.round} ms"
#binding.pry
#return res
return res.is_a?(String) ? res : res.to_json
end
def has_permission_of_type(type, user = current_user)
Dunlop::Ability.has_permission_of_type(type, user)
end
def any_permission_starts_with(expression, user = current_user)
Dunlop::Ability.any_permission_starts_with(expression, user)
end
def index_pagination?
true
end
def set_current_user
request.env["exception_notifier.exception_data"] = {
current_user: current_user.try(:inspect)
}
User.current = current_user
end
end