require_relative 'constants'
require_relative 'models/action'
require_relative 'models/importer'
require_relative 'models/custom_action'
require_relative 'models/bulk_action'
require_relative 'models/nested_field'
require_relative 'models/field'
require_relative 'models/form_field'
require_relative 'models/blocks'
require_relative 'models/column'
require_relative 'models/filter'
require_relative 'models/export'
require_relative 'models/section'
require_relative 'models/row'
require_relative 'models/tab'
require_relative 'models/dsl_method'
require_relative 'models/alert'
require 'pagy'
require 'axlsx'
require 'cocoon'
require 'pundit'
require 'local_time'
require 'csv_importer'
module CmAdmin
class Model
include Pagy::Backend
include Models::Blocks
include Models::DslMethod
include CmAdmin::Engine.routes.url_helpers
attr_accessor :available_actions, :actions_set, :available_fields, :additional_permitted_fields,
:current_action, :params, :filters, :available_tabs, :icon_name, :bulk_actions, :display_name,
:policy_scopes, :override_policy, :alerts, :sort_columns, :default_sort_column, :default_sort_direction
attr_reader :name, :ar_model, :is_visible_on_sidebar, :importer
def initialize(entity, &block)
@name = entity.name
@display_name = entity.name
@ar_model = entity
@is_visible_on_sidebar = true
@icon_name = 'fa fa-th-large'
@available_actions ||= []
@bulk_actions ||= []
@additional_permitted_fields ||= []
@current_action = nil
@available_tabs ||= []
@available_fields ||= { index: [], show: [], edit: [], new: [] }
@params = nil
@override_policy = false
@filters ||= []
@policy_scopes ||= [{ display_name: 'Full Access', scope_name: 'all' }]
@sort_columns ||= []
@default_sort_direction ||= 'asc'
@alerts = []
instance_eval(&block) if block_given?
actions unless @actions_set
$available_actions = @available_actions.dup
define_controller
define_pundit_policy(@ar_model) unless @override_policy
end
class << self
def find_by(search_hash)
CmAdmin.config.cm_admin_models.find { |x| x.name == search_hash[:name] }
end
end
def custom_controller_action(action_name, params)
current_action = CmAdmin::Models::Action.find_by(self, name: action_name.to_s)
return unless current_action
@current_action = current_action
@ar_object = @ar_model.name.classify.constantize.find(params[:id])
if @current_action.child_records
child_records = @ar_object.send(@current_action.child_records)
@associated_model = CmAdmin::Model.find_by(name: @ar_model.reflect_on_association(@current_action.child_records).klass.name)
@associated_ar_object = if child_records.is_a? ActiveRecord::Relation
filter_by(params, child_records)
else
child_records
end
return @ar_object, @associated_model, @associated_ar_object
end
@ar_object
end
# Insert into actions according to config block
def actions(only: [], except: [])
acts = CmAdmin::DEFAULT_ACTIONS.keys
acts &= ([] << only).flatten if only.present?
acts -= ([] << except).flatten if except.present?
acts.each do |act|
action_defaults = CmAdmin::DEFAULT_ACTIONS[act]
@available_actions << CmAdmin::Models::Action.new(name: act.to_s, verb: action_defaults[:verb], path: action_defaults[:path])
end
@actions_set = true
end
def importable(class_name:, importer_type:, sample_file_path: nil)
@importer = CmAdmin::Models::Importer.new(class_name, importer_type, sample_file_path)
end
def visible_on_sidebar(visible_option)
@is_visible_on_sidebar = visible_option
end
def set_icon(name)
@icon_name = name
end
def override_pundit_policy(override_status = false)
@override_policy = override_status
end
def set_display_name(name)
@display_name = name
end
def permit_additional_fields(fields = [])
@additional_permitted_fields = fields
end
def formatted_name
@display_name != @name ? @display_name : @name.titleize
end
def alert_box(options = {})
@alerts << CmAdmin::Models::Alert.new(options)
end
def model_name
@display_name.present? ? @display_name : @name
end
def set_policy_scopes(scopes = [])
@policy_scopes = ([{ display_name: 'Full Access', scope_name: 'all' }] + scopes).uniq
end
# Shared between export controller and resource controller
def filter_params(params)
# OPTIMIZE: Need to check if we can permit the filter_params in a better way
date_columns = filters.select { |x| x.filter_type.eql?(:date) }.map(&:db_column_name)
range_columns = filters.select { |x| x.filter_type.eql?(:range) }.map(&:db_column_name)
single_select_columns = filters.select { |x| x.filter_type.eql?(:single_select) }.map(&:db_column_name)
multi_select_columns = filters.select { |x| x.filter_type.eql?(:multi_select) }.map { |x| Hash["#{x.db_column_name}", []] }
params.require(:filters).permit(:search, date: date_columns, range: range_columns, single_select: single_select_columns, multi_select: multi_select_columns) if params[:filters]
end
private
# Controller defined for each model
# If model is User, controller will be UsersController
def define_controller
if $available_actions.present?
klass = Class.new(CmAdmin::ResourceController) do
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
$available_actions.each do |action|
define_method action.name.to_sym do
# controller_name & action_name from ActionController
@model = CmAdmin::Model.find_by(name: controller_name.classify)
@model.params = params
@action = CmAdmin::Models::Action.find_by(@model, name: action_name)
@model.current_action = @action
send(@action.controller_action_name, params)
# @ar_object = @model.try(@action.parent || action_name, params)
end
end
def pundit_user
Current.user
end
private
def user_not_authorized
flash[:alert] = 'You are not authorized to perform this action.'
redirect_to CmAdmin::Engine.mount_path + '/access-denied'
end
end
end
CmAdmin.const_set "#{@name}Controller", klass
end
def define_pundit_policy(ar_model)
if $available_actions.present?
klass = Class.new(ApplicationPolicy) do
$available_actions.each do |action|
define_method "#{action.name}?".to_sym do
return false unless Current.user.respond_to?(:cm_role_id)
return false if Current.user.cm_role.nil?
Current.user.cm_role.cm_permissions.where(action_name: action.name, ar_model_name: ar_model.name).present?
end
end
end
end
policy = CmAdmin.const_set "#{ar_model.name}Policy", klass
$available_actions.each do |action|
next if %w[custom_action_modal custom_action create update].include?(action.name)
klass = Class.new(policy) do
def initialize(user, scope)
@user = user
@scope = scope
end
define_method :resolve do
# action_name = Current.request_params.dig("action")
permission = Current.user.cm_role.cm_permissions.find_by(action_name: action.name, ar_model_name: ar_model.name)
if permission.present? && permission.scope_name.present?
scope.send(permission.scope_name)
else
scope.all
end
end
private
attr_reader :user, :scope
end
policy.const_set "#{action.name.classify}Scope", klass
end
end
end
end