railsadminteam/rails_admin

View on GitHub
lib/rails_admin/config.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

require 'rails_admin/config/lazy_model'
require 'rails_admin/config/sections/list'
require 'active_support/core_ext/module/attribute_accessors'

module RailsAdmin
  module Config
    # RailsAdmin is setup to try and authenticate with warden
    # If warden is found, then it will try to authenticate
    #
    # This is valid for custom warden setups, and also devise
    # If you're using the admin setup for devise, you should set RailsAdmin to use the admin
    #
    # @see RailsAdmin::Config.authenticate_with
    # @see RailsAdmin::Config.authorize_with
    DEFAULT_AUTHENTICATION = proc {}

    DEFAULT_AUTHORIZE = proc {}

    DEFAULT_AUDIT = proc {}

    DEFAULT_CURRENT_USER = proc {}

    class << self
      # Application title, can be an array of two elements
      attr_accessor :main_app_name

      # Configuration option to specify which models you want to exclude.
      attr_accessor :excluded_models

      # Configuration option to specify a allowlist of models you want to RailsAdmin to work with.
      # The excluded_models list applies against the allowlist as well and further reduces the models
      # RailsAdmin will use.
      # If included_models is left empty ([]), then RailsAdmin will automatically use all the models
      # in your application (less any excluded_models you may have specified).
      attr_accessor :included_models

      # Fields to be hidden in show, create and update views
      attr_reader :default_hidden_fields

      # Default items per page value used if a model level option has not
      # been configured
      attr_accessor :default_items_per_page

      # Default association limit
      attr_accessor :default_associated_collection_limit

      attr_reader :default_search_operator

      # Configuration option to specify which method names will be searched for
      # to be used as a label for object records. This defaults to [:name, :title]
      attr_accessor :label_methods

      # hide blank fields in show view if true
      attr_accessor :compact_show_view

      # Tell browsers whether to use the native HTML5 validations (novalidate form option).
      attr_accessor :browser_validations

      # set parent controller
      attr_reader :parent_controller

      # set settings for `protect_from_forgery` method
      # By default, it raises exception upon invalid CSRF tokens
      attr_accessor :forgery_protection_settings

      # Stores model configuration objects in a hash identified by model's class
      # name.
      #
      # @see RailsAdmin.config
      attr_reader :registry

      # Bootstrap CSS classes used for Navigation bar
      attr_accessor :navbar_css_classes

      # show Gravatar in Navigation bar
      attr_accessor :show_gravatar

      # accepts a hash of static links to be shown below the main navigation
      attr_accessor :navigation_static_links
      attr_accessor :navigation_static_label

      # Set where RailsAdmin fetches JS/CSS from, defaults to :sprockets
      attr_writer :asset_source

      # Setup authentication to be run as a before filter
      # This is run inside the controller instance so you can setup any authentication you need to
      #
      # By default, the authentication will run via warden if available
      # and will run the default.
      #
      # If you use devise, this will authenticate the same as _authenticate_user!_
      #
      # @example Devise admin
      #   RailsAdmin.config do |config|
      #     config.authenticate_with do
      #       authenticate_admin!
      #     end
      #   end
      #
      # @example Custom Warden
      #   RailsAdmin.config do |config|
      #     config.authenticate_with do
      #       warden.authenticate! scope: :paranoid
      #     end
      #   end
      #
      # @see RailsAdmin::Config::DEFAULT_AUTHENTICATION
      def authenticate_with(&blk)
        @authenticate = blk if blk
        @authenticate || DEFAULT_AUTHENTICATION
      end

      # Setup auditing/versioning provider that observe objects lifecycle
      def audit_with(*args, &block)
        extension = args.shift
        if extension
          klass = RailsAdmin::AUDITING_ADAPTERS[extension]
          klass.setup if klass.respond_to? :setup
          @audit = proc do
            @auditing_adapter = klass.new(*([self] + args).compact, &block)
          end
        elsif block
          @audit = block
        end
        @audit || DEFAULT_AUDIT
      end

      # Setup authorization to be run as a before filter
      # This is run inside the controller instance so you can setup any authorization you need to.
      #
      # By default, there is no authorization.
      #
      # @example Custom
      #   RailsAdmin.config do |config|
      #     config.authorize_with do
      #       redirect_to root_path unless warden.user.is_admin?
      #     end
      #   end
      #
      # To use an authorization adapter, pass the name of the adapter. For example,
      # to use with CanCanCan[https://github.com/CanCanCommunity/cancancan/], pass it like this.
      #
      # @example CanCanCan
      #   RailsAdmin.config do |config|
      #     config.authorize_with :cancancan
      #   end
      #
      # See the wiki[https://github.com/railsadminteam/rails_admin/wiki] for more on authorization.
      #
      # @see RailsAdmin::Config::DEFAULT_AUTHORIZE
      def authorize_with(*args, &block)
        extension = args.shift
        if extension
          klass = RailsAdmin::AUTHORIZATION_ADAPTERS[extension]
          klass.setup if klass.respond_to? :setup
          @authorize = proc do
            @authorization_adapter = klass.new(*([self] + args).compact, &block)
          end
        elsif block
          @authorize = block
        end
        @authorize || DEFAULT_AUTHORIZE
      end

      # Setup configuration using an extension-provided ConfigurationAdapter
      #
      # @example Custom configuration for role-based setup.
      #   RailsAdmin.config do |config|
      #     config.configure_with(:custom) do |config|
      #       config.models = ['User', 'Comment']
      #       config.roles  = {
      #         'Admin' => :all,
      #         'User'  => ['User']
      #       }
      #     end
      #   end
      def configure_with(extension)
        configuration = RailsAdmin::CONFIGURATION_ADAPTERS[extension].new
        yield(configuration) if block_given?
      end

      # Setup a different method to determine the current user or admin logged in.
      # This is run inside the controller instance and made available as a helper.
      #
      # By default, _request.env["warden"].user_ or _current_user_ will be used.
      #
      # @example Custom
      #   RailsAdmin.config do |config|
      #     config.current_user_method do
      #       current_admin
      #     end
      #   end
      #
      # @see RailsAdmin::Config::DEFAULT_CURRENT_USER
      def current_user_method(&block)
        @current_user = block if block
        @current_user || DEFAULT_CURRENT_USER
      end

      def default_search_operator=(operator)
        if %w[default like not_like starts_with ends_with is =].include? operator
          @default_search_operator = operator
        else
          raise ArgumentError.new("Search operator '#{operator}' not supported")
        end
      end

      # pool of all found model names from the whole application
      def models_pool
        (viable_models - excluded_models.collect(&:to_s)).uniq.sort
      end

      # Loads a model configuration instance from the registry or registers
      # a new one if one is yet to be added.
      #
      # First argument can be an instance of requested model, its class object,
      # its class name as a string or symbol or a RailsAdmin::AbstractModel
      # instance.
      #
      # If a block is given it is evaluated in the context of configuration instance.
      #
      # Returns given model's configuration
      #
      # @see RailsAdmin::Config.registry
      def model(entity, &block)
        key =
          case entity
          when RailsAdmin::AbstractModel
            entity.model.try(:name).try :to_sym
          when Class, ConstLoadSuppressor::ConstProxy
            entity.name.to_sym
          when String, Symbol
            entity.to_sym
          else
            entity.class.name.to_sym
          end

        @registry[key] ||= RailsAdmin::Config::LazyModel.new(key.to_s)
        @registry[key].add_deferred_block(&block) if block
        @registry[key]
      end

      def asset_source
        @asset_source ||=
          begin
            detected = defined?(Sprockets) ? :sprockets : :invalid
            unless ARGV.join(' ').include? 'rails_admin:install'
              warn <<~MSG
                [Warning] After upgrading RailsAdmin to 3.x you haven't set asset_source yet, using :#{detected} as the default.
                To suppress this message, run 'rails rails_admin:install' to setup the asset delivery method suitable to you.
              MSG
            end
            detected
          end
      end

      def default_hidden_fields=(fields)
        if fields.is_a?(Array)
          @default_hidden_fields = {}
          @default_hidden_fields[:edit] = fields
          @default_hidden_fields[:show] = fields
        else
          @default_hidden_fields = fields
        end
      end

      def parent_controller=(name)
        @parent_controller = name

        if defined?(RailsAdmin::ApplicationController) || defined?(RailsAdmin::MainController)
          RailsAdmin::Config::ConstLoadSuppressor.allowing do
            RailsAdmin.send(:remove_const, :ApplicationController)
            RailsAdmin.send(:remove_const, :MainController)
            load RailsAdmin::Engine.root.join('app/controllers/rails_admin/application_controller.rb')
            load RailsAdmin::Engine.root.join('app/controllers/rails_admin/main_controller.rb')
          end
        end
      end

      def total_columns_width=(_)
        ActiveSupport::Deprecation.warn('The total_columns_width configuration option is deprecated and has no effect.')
      end

      def sidescroll=(_)
        ActiveSupport::Deprecation.warn('The sidescroll configuration option was removed, it is always enabled now.')
      end

      # Setup actions to be used.
      def actions(&block)
        return unless block

        RailsAdmin::Config::Actions.reset
        RailsAdmin::Config::Actions.instance_eval(&block)
      end

      # Returns all model configurations
      #
      # @see RailsAdmin::Config.registry
      def models
        RailsAdmin::AbstractModel.all.collect { |m| model(m) }
      end

      # Reset all configurations to defaults.
      #
      # @see RailsAdmin::Config.registry
      def reset
        @compact_show_view = true
        @browser_validations = true
        @authenticate = nil
        @authorize = nil
        @audit = nil
        @current_user = nil
        @default_hidden_fields = {}
        @default_hidden_fields[:base] = [:_type]
        @default_hidden_fields[:edit] = %i[id _id created_at created_on deleted_at updated_at updated_on deleted_on]
        @default_hidden_fields[:show] = %i[id _id created_at created_on deleted_at updated_at updated_on deleted_on]
        @default_items_per_page = 20
        @default_associated_collection_limit = 100
        @default_search_operator = 'default'
        @excluded_models = []
        @included_models = []
        @label_methods = %i[name title]
        @main_app_name = proc { [Rails.application.engine_name.titleize.chomp(' Application'), 'Admin'] }
        @registry = {}
        @navbar_css_classes = %w[navbar-dark bg-primary border-bottom]
        @show_gravatar = true
        @navigation_static_links = {}
        @navigation_static_label = nil
        @asset_source = nil
        @parent_controller = '::ActionController::Base'
        @forgery_protection_settings = {with: :exception}
        RailsAdmin::Config::Actions.reset
        RailsAdmin::AbstractModel.reset
      end

      # Reset a provided model's configuration.
      #
      # @see RailsAdmin::Config.registry
      def reset_model(model)
        key = model.is_a?(Class) ? model.name.to_sym : model.to_sym
        @registry.delete(key)
      end

      # Perform reset, then load RailsAdmin initializer again
      def reload!
        reset
        load RailsAdmin::Engine.config.initializer_path
      end

      # Get all models that are configured as visible sorted by their weight and label.
      #
      # @see RailsAdmin::Config::Hideable
      def visible_models(bindings)
        visible_models_with_bindings(bindings).sort do |a, b|
          if (weight_order = a.weight <=> b.weight) == 0
            a.label.casecmp(b.label)
          else
            weight_order
          end
        end
      end

    private

      def viable_models
        included_models.collect(&:to_s).presence || begin
          @@system_models ||= # memoization for tests
            ([Rails.application] + Rails::Engine.subclasses.collect(&:instance)).flat_map do |app|
              (app.paths['app/models'].to_a + app.config.eager_load_paths).collect do |load_path|
                Dir.glob(app.root.join(load_path)).collect do |load_dir|
                  path_prefix = "#{app.root.join(load_dir)}/"
                  Dir.glob("#{load_dir}/**/*.rb").collect do |filename|
                    # app/models/module/class.rb => module/class.rb => module/class => Module::Class
                    filename.delete_prefix(path_prefix).chomp('.rb').camelize
                  end
                end
              end
            end.flatten.reject { |m| m.starts_with?('Concerns::') } # rubocop:disable Style/MultilineBlockChain

          @@system_models + @registry.keys.collect(&:to_s)
        end
      end

      def visible_models_with_bindings(bindings)
        models.collect { |m| m.with(bindings) }.select do |m|
          m.visible? &&
            RailsAdmin::Config::Actions.find(:index, bindings.merge(abstract_model: m.abstract_model)).try(:authorized?) &&
            (!m.abstract_model.embedded? || m.abstract_model.cyclic?)
        end
      end
    end

    # Set default values for configuration options on load
    reset
  end
end