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