lib/active_admin/application.rb
# frozen_string_literal: true
require "active_admin/router"
require "active_admin/application_settings"
require "active_admin/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