module ActiveAdmin::NestedNamespace

Constants

VERSION

Public Class Methods

find_for_resource_in_namespace(resource, name) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 123
def self.find_for_resource_in_namespace(resource, name)
  where(
      resource_type: resource_type(resource),
      resource_id: resource,
      namespace: name.to_s
  ).order(ActiveAdmin.application.namespaces[ActiveAdmin.application.build_name_path(name)].comments_order)
end
new(application, name) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 58
def initialize(application, name)
  @application = application
  @name_path = ActiveAdmin.application.build_name_path(name)
  @resources = ResourceCollection.new
  register_module unless root?
  build_menu_collection
end
setup() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 5
def self.setup
  ActiveAdmin::Application.class_eval do

    def namespace(name)
      name ||= :root

      namespace = namespaces[build_name_path(name)] ||= begin
        namespace = Namespace.new(self, name)
        ActiveSupport::Notifications.publish ActiveAdmin::Namespace::RegisterEvent, namespace
        namespace
      end

      yield(namespace) if block_given?

      namespace
    end

    def build_name_path(name)
      Array(name).map(&:to_s).map(&:underscore).map(&:to_sym)
    end

  end

  ActiveAdmin::BaseController.class_eval do
    def authenticate_active_admin_user
      if active_admin_namespace.authentication_method
        auth_method = active_admin_namespace.authentication_method
        if auth_method.is_a?(Proc)
          namespace = active_admin_namespace.name_path
          send(auth_method.call(namespace))
        else
          send(auth_method)
        end
      end
    end

    def current_active_admin_user
      if active_admin_namespace.current_user_method
        user_method = active_admin_namespace.current_user_method
        if user_method.is_a?(Proc)
          namespace = active_admin_namespace.name_path
          send(user_method.call(namespace))
        else
          send(user_method)
        end
      end
    end
  end

  ActiveAdmin::Namespace.class_eval do

    attr_reader :name_path

    def initialize(application, name)
      @application = application
      @name_path = ActiveAdmin.application.build_name_path(name)
      @resources = ResourceCollection.new
      register_module unless root?
      build_menu_collection
    end

    def name
      Deprecation.warn "name replaced by name_path now that namespaces can be nested."
      name_path.first
    end

    def root?
      name_path.first == :root
    end

    def module_name
      root? ? nil : name_path.map(&:to_s).map(&:camelize).join('::')
    end

    def route_prefix
      root? ? nil : name_path.map(&:to_s).join('_').underscore
    end

    def add_logout_button_to_menu(menu, priority = 20, html_options = {})
      computed_logout_link_path = logout_link_path.is_a?(Proc) ? logout_link_path.call(name_path) : logout_link_path

      if computed_logout_link_path
        html_options = html_options.reverse_merge(method: logout_link_method || :get)
        menu.add id: 'logout', priority: priority, html_options: html_options,
                 label: -> {I18n.t 'active_admin.logout'},
                 url: computed_logout_link_path,
                 if: :current_active_admin_user?
      end
    end

    # dynamically create nested modules
    def register_module
      module_names = module_name.split("::").inject([]) {|n, c| n << (n.empty? ? [c] : [n.last] + [c]).flatten}
      module_names.each do |module_name_array|
        eval "module ::#{module_name_array.join("::")}; end"
      end
    end

  end

  ActiveAdmin::Namespace::Store.class_eval do

    def [](key)
      @namespaces[Array(key)]
    end

    def names
      Deprecation.warn "names replaced by name_paths now that namespaces can be nested."
      @namespaces.keys.first
    end

    def name_paths
      @namespaces.keys
    end

  end
  
  ActiveAdmin::Comment.class_eval do
    def self.find_for_resource_in_namespace(resource, name)
      where(
          resource_type: resource_type(resource),
          resource_id: resource,
          namespace: name.to_s
      ).order(ActiveAdmin.application.namespaces[ActiveAdmin.application.build_name_path(name)].comments_order)
    end
  end

  ActiveAdmin::Comments::Views::Comments.class_eval do

    def build(resource)
      @resource = resource
      @comments = ActiveAdmin::Comment.find_for_resource_in_namespace(resource, active_admin_namespace.name_path).includes(:author).page(params[:page])
      super(title, for: resource)
      build_comments
    end

    def comments_url(*args)
      parts = []
      parts << active_admin_namespace.name_path unless active_admin_namespace.root?
      parts << active_admin_namespace.comments_registration_name.underscore
      parts << 'path'
      send parts.join('_'), *args
    end

    def comment_form_url
      parts = []
      parts << active_admin_namespace.name_path unless active_admin_namespace.root?
      parts << active_admin_namespace.comments_registration_name.underscore.pluralize
      parts << 'path'
      send parts.join '_'
    end

  end

  ActiveAdmin::Page.class_eval do
    def namespace_name
      Deprecation.warn "namespace_name replaced by namespace_name now that namespaces can be nested."
      namespace.name.to_s
    end

    def namespace_name_path
      namespace.name_path
    end
  end

  ActiveAdmin::Resource::BelongsTo::TargetNotFound.class_eval do
    def initialize(key, namespace)
      super "Could not find #{key} in #{namespace.name_path} " +
                "with #{namespace.resources.map(&:resource_name)}"
    end
  end

  ActiveAdmin::Router.class_eval do

    def define_root_routes(router)
      router.instance_exec @application.namespaces do |namespaces|
        namespaces.each do |namespace|
          if namespace.root?
            root namespace.root_to_options.merge(to: namespace.root_to)
          else
            proc = Proc.new do
              root namespace.root_to_options.merge(to: namespace.root_to, as: :root)
            end
            (namespace.name_path.reverse.inject(proc) {|n, c| Proc.new {namespace c, namespace.route_options.dup, &n}}).call
          end
        end
      end
    end

    # Defines the routes for each resource
    def define_resource_routes(router)
      router.instance_exec @application.namespaces, self do |namespaces, aa_router|
        resources = namespaces.flat_map {|n| n.resources.values}
        resources.each do |config|
          routes = aa_router.resource_routes(config)

          # Add in the parent if it exists
          if config.belongs_to?
            belongs_to = routes
            routes = Proc.new do
              # If it's optional, make the normal resource routes
              instance_exec &belongs_to if config.belongs_to_config.optional?

              # Make the nested belongs_to routes
              # :only is set to nothing so that we don't clobber any existing routes on the resource
              resources config.belongs_to_config.target.resource_name.plural, only: [] do
                instance_exec &belongs_to
              end
            end
          end

          # Add on the namespace if required
          unless config.namespace.root?
            nested = routes
            routes = Proc.new do

              proc = Proc.new do
                instance_exec &nested
              end
              (config.namespace.name_path.reverse.inject(proc) {|n, c| Proc.new {namespace c, config.namespace.route_options.dup, &n}}).call
            end
          end

          instance_exec &routes
        end
      end
    end

  end

  ActiveAdmin::Scope.class_eval do
    def initialize(name, method = nil, options = {}, &block)
      @name, @scope_method = name, method.try(:to_sym)

      if name.is_a? Proc
        raise "A string/symbol is required as the second argument if your label is a proc." unless method
        @id = ActiveAdmin::Dependency.rails.parameterize method.to_s
      else
        @scope_method ||= (name.is_a?(Array) ? name.join('_').underscore : name).to_sym
        @id = ActiveAdmin::Dependency.rails.parameterize name.to_s
      end

      @scope_method = nil if @scope_method == :all
      @scope_method, @scope_block = nil, block if block_given?

      @localizer = options[:localizer]
      @show_count = options.fetch(:show_count, true)
      @display_if_block = options[:if] || proc {true}
      @default_block = options[:default] || proc {false}
    end
  end

  ActiveAdmin::Views::Pages::Base.class_eval do
    def add_classes_to_body
      @body.add_class(params[:action])
      @body.add_class(params[:controller].tr('/', '_'))
      @body.add_class("active_admin")
      @body.add_class("logged_in")
      @body.add_class(active_admin_namespace.name_path.map(&:to_s).join('_') + '_namespace')
    end
  end

  # patch - active_admin/orm/active_record/comments.rb
  ActiveAdmin.after_load do |app|
    app.namespaces.each do |namespace|

      # Un-register default comment pages
      namespace.resources.instance_variable_get(:@collection).delete_if {|k,v| v.instance_variable_get(:@resource_class_name) == '::ActiveAdmin::Comment' }

      # Re-register comments pages with nested namespaces
      namespace.register ActiveAdmin::Comment, as: namespace.comments_registration_name do
        actions :index, :show, :create, :destroy

        menu namespace.comments ? namespace.comments_menu : false

        config.comments = false # Don't allow comments on comments
        config.batch_actions = false # The default destroy batch action isn't showing up anyway...

        scope :all, show_count: false
        # Register a scope for every namespace that exists.
        # The current namespace will be the default scope.
        app.namespaces.map(&:name_path).sort { |x,y| x[0] <=> y[0] }.each do |name_path|
          scope "/#{name_path.join('/')}", default: namespace.name_path == name_path do |scope|
            scope.where namespace: name_path.to_s
          end
        end

        # Store the author and namespace
        before_save do |comment|
          comment.namespace = active_admin_config.namespace.name_path
          comment.author = current_active_admin_user
        end

        controller do
          # Prevent N+1 queries
          def scoped_collection
            super.includes(:author, :resource)
          end

          # Redirect to the resource show page after comment creation
          def create
            create! do |success, failure|
              success.html do
                ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
              end
              failure.html do
                flash[:error] = I18n.t 'active_admin.comments.errors.empty_text'
                ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
              end
            end

            def destroy
              destroy! do |success, failure|
                success.html do
                  ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
                end
                failure.html do
                  ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
                end
              end
            end
          end
        end

        permit_params :body, :namespace, :resource_id, :resource_type

        index do
          column I18n.t('active_admin.comments.resource_type'), :resource_type
          column I18n.t('active_admin.comments.author_type'), :author_type
          column I18n.t('active_admin.comments.resource'), :resource
          column I18n.t('active_admin.comments.author'), :author
          column I18n.t('active_admin.comments.body'), :body
          column I18n.t('active_admin.comments.created_at'), :created_at
          actions
        end
      end
    end
  end
  
end

Public Instance Methods

[](key) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 107
def [](key)
  @namespaces[Array(key)]
end
add_classes_to_body() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 258
def add_classes_to_body
  @body.add_class(params[:action])
  @body.add_class(params[:controller].tr('/', '_'))
  @body.add_class("active_admin")
  @body.add_class("logged_in")
  @body.add_class(active_admin_namespace.name_path.map(&:to_s).join('_') + '_namespace')
end
add_logout_button_to_menu(menu, priority = 20, html_options = {}) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 83
def add_logout_button_to_menu(menu, priority = 20, html_options = {})
  computed_logout_link_path = logout_link_path.is_a?(Proc) ? logout_link_path.call(name_path) : logout_link_path

  if computed_logout_link_path
    html_options = html_options.reverse_merge(method: logout_link_method || :get)
    menu.add id: 'logout', priority: priority, html_options: html_options,
             label: -> {I18n.t 'active_admin.logout'},
             url: computed_logout_link_path,
             if: :current_active_admin_user?
  end
end
authenticate_active_admin_user() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 29
def authenticate_active_admin_user
  if active_admin_namespace.authentication_method
    auth_method = active_admin_namespace.authentication_method
    if auth_method.is_a?(Proc)
      namespace = active_admin_namespace.name_path
      send(auth_method.call(namespace))
    else
      send(auth_method)
    end
  end
end
build(resource) click to toggle source
Calls superclass method
# File lib/active_admin/nested_namespace.rb, line 134
def build(resource)
  @resource = resource
  @comments = ActiveAdmin::Comment.find_for_resource_in_namespace(resource, active_admin_namespace.name_path).includes(:author).page(params[:page])
  super(title, for: resource)
  build_comments
end
build_name_path(name) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 22
def build_name_path(name)
  Array(name).map(&:to_s).map(&:underscore).map(&:to_sym)
end
comment_form_url() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 149
def comment_form_url
  parts = []
  parts << active_admin_namespace.name_path unless active_admin_namespace.root?
  parts << active_admin_namespace.comments_registration_name.underscore.pluralize
  parts << 'path'
  send parts.join '_'
end
comments_url(*args) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 141
def comments_url(*args)
  parts = []
  parts << active_admin_namespace.name_path unless active_admin_namespace.root?
  parts << active_admin_namespace.comments_registration_name.underscore
  parts << 'path'
  send parts.join('_'), *args
end
create() click to toggle source

Redirect to the resource show page after comment creation

# File lib/active_admin/nested_namespace.rb, line 305
def create
  create! do |success, failure|
    success.html do
      ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
    end
    failure.html do
      flash[:error] = I18n.t 'active_admin.comments.errors.empty_text'
      ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
    end
  end

  def destroy
    destroy! do |success, failure|
      success.html do
        ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
      end
      failure.html do
        ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
      end
    end
  end
end
current_active_admin_user() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 41
def current_active_admin_user
  if active_admin_namespace.current_user_method
    user_method = active_admin_namespace.current_user_method
    if user_method.is_a?(Proc)
      namespace = active_admin_namespace.name_path
      send(user_method.call(namespace))
    else
      send(user_method)
    end
  end
end
define_resource_routes(router) click to toggle source

Defines the routes for each resource

# File lib/active_admin/nested_namespace.rb, line 195
def define_resource_routes(router)
  router.instance_exec @application.namespaces, self do |namespaces, aa_router|
    resources = namespaces.flat_map {|n| n.resources.values}
    resources.each do |config|
      routes = aa_router.resource_routes(config)

      # Add in the parent if it exists
      if config.belongs_to?
        belongs_to = routes
        routes = Proc.new do
          # If it's optional, make the normal resource routes
          instance_exec &belongs_to if config.belongs_to_config.optional?

          # Make the nested belongs_to routes
          # :only is set to nothing so that we don't clobber any existing routes on the resource
          resources config.belongs_to_config.target.resource_name.plural, only: [] do
            instance_exec &belongs_to
          end
        end
      end

      # Add on the namespace if required
      unless config.namespace.root?
        nested = routes
        routes = Proc.new do

          proc = Proc.new do
            instance_exec &nested
          end
          (config.namespace.name_path.reverse.inject(proc) {|n, c| Proc.new {namespace c, config.namespace.route_options.dup, &n}}).call
        end
      end

      instance_exec &routes
    end
  end
end
define_root_routes(router) click to toggle source
# File lib/active_admin/nested_namespace.rb, line 179
def define_root_routes(router)
  router.instance_exec @application.namespaces do |namespaces|
    namespaces.each do |namespace|
      if namespace.root?
        root namespace.root_to_options.merge(to: namespace.root_to)
      else
        proc = Proc.new do
          root namespace.root_to_options.merge(to: namespace.root_to, as: :root)
        end
        (namespace.name_path.reverse.inject(proc) {|n, c| Proc.new {namespace c, namespace.route_options.dup, &n}}).call
      end
    end
  end
end
destroy() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 316
def destroy
  destroy! do |success, failure|
    success.html do
      ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
    end
    failure.html do
      ActiveAdmin::Dependency.rails.redirect_back self, active_admin_root
    end
  end
end
module_name() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 75
def module_name
  root? ? nil : name_path.map(&:to_s).map(&:camelize).join('::')
end
name() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 66
def name
  Deprecation.warn "name replaced by name_path now that namespaces can be nested."
  name_path.first
end
name_paths() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 116
def name_paths
  @namespaces.keys
end
names() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 111
def names
  Deprecation.warn "names replaced by name_paths now that namespaces can be nested."
  @namespaces.keys.first
end
namespace(name) { |namespace| ... } click to toggle source
# File lib/active_admin/nested_namespace.rb, line 8
def namespace(name)
  name ||= :root

  namespace = namespaces[build_name_path(name)] ||= begin
    namespace = Namespace.new(self, name)
    ActiveSupport::Notifications.publish ActiveAdmin::Namespace::RegisterEvent, namespace
    namespace
  end

  yield(namespace) if block_given?

  namespace
end
namespace_name() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 160
def namespace_name
  Deprecation.warn "namespace_name replaced by namespace_name now that namespaces can be nested."
  namespace.name.to_s
end
namespace_name_path() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 165
def namespace_name_path
  namespace.name_path
end
register_module() click to toggle source

dynamically create nested modules

# File lib/active_admin/nested_namespace.rb, line 96
def register_module
  module_names = module_name.split("::").inject([]) {|n, c| n << (n.empty? ? [c] : [n.last] + [c]).flatten}
  module_names.each do |module_name_array|
    eval "module ::#{module_name_array.join("::")}; end"
  end
end
root?() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 71
def root?
  name_path.first == :root
end
route_prefix() click to toggle source
# File lib/active_admin/nested_namespace.rb, line 79
def route_prefix
  root? ? nil : name_path.map(&:to_s).join('_').underscore
end
scoped_collection() click to toggle source

Prevent N+1 queries

Calls superclass method
# File lib/active_admin/nested_namespace.rb, line 300
def scoped_collection
  super.includes(:author, :resource)
end