# frozen_string_literal: true require_relative "router" require_relative "application_settings" require_relative "namespace_settings" module ActiveAdmin class Application class << self def setting(name, default) ApplicationSettings.register name, default end def inheritable_setting(name, default) NamespaceSettings.register name, default end end def settings @settings ||= SettingsNode.build(ApplicationSettings) end def namespace_settings @namespace_settings ||= SettingsNode.build(NamespaceSettings) end def respond_to_missing?(method, include_private = false) [settings, namespace_settings].any? { |sets| sets.respond_to?(method) } || super end def method_missing(method, *args) if settings.respond_to?(method) settings.send(method, *args) elsif namespace_settings.respond_to?(method) namespace_settings.send(method, *args) else super end end attr_reader :namespaces def initialize @namespaces = Namespace::Store.new end # Event that gets triggered on load of Active Admin BeforeLoadEvent = "active_admin.application.before_load".freeze AfterLoadEvent = "active_admin.application.after_load".freeze # Runs before the app's AA initializer def setup! end # Runs after the app's AA initializer def prepare! remove_active_admin_load_paths_from_rails_autoload_and_eager_load attach_reloader end # Registers a brand new configuration for the given resource. def register(resource, options = {}, &block) ns = options.fetch(:namespace) { default_namespace } namespace(ns).register resource, options, &block end # Creates a namespace for the given name # # Yields the namespace if a block is given # # @return [Namespace] the new or existing namespace def namespace(name) name ||= :root namespace = namespaces[name.to_sym] ||= begin namespace = Namespace.new(self, name) ActiveSupport::Notifications.instrument ActiveAdmin::Namespace::RegisterEvent, { active_admin_namespace: namespace } namespace end yield(namespace) if block_given? namespace end # Register a page # # @param name [String] The page name # @option [Hash] Accepts option :namespace. # @&block The registration block. # def register_page(name, options = {}, &block) ns = options.fetch(:namespace) { default_namespace } namespace(ns).register_page name, options, &block end # Whether all configuration files have been loaded def loaded? @@loaded ||= false end # Removes all defined controllers from memory. Useful in # development, where they are reloaded on each request. def unload! namespaces.each &:unload! @@loaded = false end # Loads all ruby files that are within the load_paths setting. # To reload everything simply call `ActiveAdmin.unload!` def load! unless loaded? ActiveSupport::Notifications.instrument BeforeLoadEvent, { active_admin_application: self } # before_load hook files.each { |file| load file } # load files namespace(default_namespace) # init AA resources ActiveSupport::Notifications.instrument AfterLoadEvent, { active_admin_application: self } # after_load hook @@loaded = true end end def load(file) DatabaseHitDuringLoad.capture { super } end # Returns ALL the files to be loaded def files load_paths.flatten.compact.uniq.flat_map { |path| Dir["#{path}/**/*.rb"].sort } end # Creates all the necessary routes for the ActiveAdmin configurations # # Use this within the routes.rb file: # # Application.routes.draw do |map| # ActiveAdmin.routes(self) # end # # @param rails_router [ActionDispatch::Routing::Mapper] def routes(rails_router) load! Router.new(router: rails_router, namespaces: namespaces).apply end # Adds before, around and after filters to all controllers. # Example usage: # ActiveAdmin.before_action :authenticate_admin! # AbstractController::Callbacks::ClassMethods.public_instance_methods. select { |m| m.end_with?('_action') }.each do |name| define_method name do |*args, &block| ActiveSupport.on_load(:active_admin_controller) do public_send name, *args, &block end end end def controllers_for_filters controllers = [BaseController] controllers.push *Devise.controllers_for_filters if Dependency.devise? controllers end private # Since app/admin is alphabetically before app/models, we have to remove it # from the host app's +autoload_paths+ to prevent missing constant errors. # # As well, we have to remove it from +eager_load_paths+ to prevent the # files from being loaded twice in production. def remove_active_admin_load_paths_from_rails_autoload_and_eager_load ActiveSupport::Dependencies.autoload_paths -= load_paths Rails.application.config.eager_load_paths -= load_paths end # Hook into the Rails code reloading mechanism so that things are reloaded # properly in development mode. # # If any of the app files (e.g. models) has changed, we need to reload all # the admin files. If the admin files themselves has changed, we need to # regenerate the routes as well. def attach_reloader Rails.application.config.after_initialize do |app| unload_active_admin = -> { ActiveAdmin.application.unload! } if app.config.reload_classes_only_on_change # Rails is about to unload all the app files (e.g. models), so we # should first unload the classes generated by Active Admin, otherwise # they will contain references to the stale (unloaded) classes. ActiveSupport::Reloader.to_prepare(prepend: true, &unload_active_admin) else # If the user has configured the app to always reload app files after # each request, so we should unload the generated classes too. ActiveSupport::Reloader.to_complete(&unload_active_admin) end admin_dirs = {} load_paths.each do |path| admin_dirs[path] = [:rb] end routes_reloader = app.config.file_watcher.new([], admin_dirs) do app.reload_routes! end app.reloaders << routes_reloader ActiveSupport::Reloader.to_prepare do # Rails might have reloaded the routes for other reasons (e.g. # routes.rb has changed), in which case Active Admin would have been # loaded via the `ActiveAdmin.routes` call in `routes.rb`. # # Otherwise, we should check if any of the admin files are changed # and force the routes to reload if necessary. This would again causes # Active Admin to load via `ActiveAdmin.routes`. # # Finally, if Active Admin is still not loaded at this point, then we # would need to load it manually. unless ActiveAdmin.application.loaded? routes_reloader.execute_if_updated ActiveAdmin.application.load! end end end end end end