Working with Resources

Every Active Admin resource corresponds to a Rails model. So before creating a resource you must first create a Rails model for it.

Create a Resource

The basic command for creating a resource is rails g active_admin:resource Post. The generator will produce an empty app/admin/posts.rb file like so:

ActiveAdmin.configure_resource Post do |config|
  # ...
end

TODO: and an empty app/controllers/admin/posts_controller.rb file like so:

class Admin::PostsController < ActiveAdmin::ResourceController
  # ...
end

Setting up Strong Parameters

Override the permitted_attr_names method to define which attributes may be changed:

class Admin::PostsController < ActiveAdmin::ResourceController
  def permitted_attr_names
    [:title, :content, :publisher_id]
  end
end

or more directly, override permitted_params:

class Admin::PostsController < ActiveAdmin::ResourceController
  def permitted_params
    params.permit(post: [:title, :content, :publisher_id])
  end
end

Any form field that sends multiple values (such as a HABTM association, or an array attribute) needs to pass an empty array:

If your HABTM is roles, you should permit role_ids: []

class Admin::PostsController < ActiveAdmin::ResourceController
  def permitted_attr_names
    [:title, :content, :publisher_id, role_ids: []]
  end
end

Nested associations in the same form also require an array, but it needs to be filled with any attributes used.

class Admin::PostsController < ActiveAdmin::ResourceController
  def permitted_attr_names
    [:title, :content, :publisher_id,
    tags_attributes: [:id, :name, :description, :_destroy]]
  end
end

# Note that `accepts_nested_attributes_for` is still required:
class Post < ActiveRecord::Base
  accepts_nested_attributes_for :tags, allow_destroy: true
end

If you want to dynamically choose which attributes can be set:

class Admin::PostsController < ActiveAdmin::ResourceController
  def permitted_attr_names
    params = [:title, :content, :publisher_id]
    params.push :author_id if current_user.admin?
    params
  end
end

permitted_attr_names is called by a method called permitted_params. You should use this method when overriding create or update actions:

class Admin::PostsController < ActiveAdmin::ResourceController
  def create
    # Good
    @post = Post.new(permitted_params[:post])
    # Bad
    @post = Post.new(params[:post])

    if @post.save
      # ...
    end
  end
end

Disabling Actions on a Resource

All CRUD actions are enabled by default. These can be disabled for a given resource:

class Admin::PostsController < ActiveAdmin::ResourceController
  actions :all, except: [:update, :destroy]
end

Renaming Action Items

You can use translations to override labels and page titles for actions such as new, edit, and destroy by providing a resource specific translation. For example, to change ‘New Offer’ to ‘Make an Offer’ add the following in config/locales/[en].yml:

en:
  active_admin:
    resources:
      offer: # Registered resource
        new_model: 'Make an Offer' # new action item
        edit_model: 'Change Offer' # edit action item
        delete_model: 'Cancel Offer' # delete action item

See the default en.yml locale file for existing translations and examples.

Rename the Resource

By default, any references to the resource (menu, routes, buttons, etc) in the interface will use the name of the class. You can rename the resource by using the :as option.

ActiveAdmin.configure_resource Post, as: "Article"

The resource will then be available at /admin/articles. The controller and view names should be updated also.

Customize the Namespace

We use the admin namespace by default, but you can use anything:

# Available at /today/posts
ActiveAdmin.configure_resource Post, namespace: :today

# Available at /posts
ActiveAdmin.configure_resource Post, namespace: false

Customize the Menu

The resource will be displayed in the global navigation by default. To disable the resource from being displayed in the global navigation:

ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = false
end

Menu item options include:

  • :label - The string or proc label to display in the menu. If it’s a proc, it will be called each time the menu is rendered.
  • :parent - The string id (or label) of the parent used for this menu
  • :if - A block or a symbol of a method to call to decide if the menu item should be displayed
  • :priority - The integer value of the priority, which defaults to 10

Labels

To change the name of the label in the menu:

ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { label: "My Posts" }
end

If you want something more dynamic, pass a proc instead:

ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { label: proc{ I18n.t "mypost" } }
end

Menu items are sorted first by their numeric priority, then alphabetically. Since every menu by default has a priority of 10, the menu is normally alphabetical.

You can easily customize this:

ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { priority: 1 } # so it's on the very left
end

Conditionally Showing / Hiding Menu Items

Menu items can be shown or hidden at runtime using the :if option.

ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { if: proc{ current_user.can_edit_posts? } }
end

The proc will be called in the context of the view, so you have access to all your helpers and current user session information.

In many cases, a single level navigation will not be enough to manage a large application. In that case, you can group your menu items under a parent menu item.

ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { parent: "Blog" }
end

Note that the “Blog” parent menu item doesn’t even have to exist yet; it can be dynamically generated for you.

Customizing Parent Menu Items

All of the options given to a standard menu item are also available to parent menu items. In the case of complex parent menu items, you should configure them in the Active Admin initializer.

# config/initializers/active_admin.rb
config.namespace :admin do |admin|
  admin.build_menu do |menu|
    menu.add label: 'Blog', priority: 0
  end
end

# app/admin/post.rb
ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { parent: 'Blog' }
end

Dynamic Parent Menu Items

While the above works fine, what if you want a parent menu item with a dynamic name? Well, you have to refer to it by its :id.

# config/initializers/active_admin.rb
config.namespace :admin do |admin|
  admin.build_menu do |menu|
    menu.add id: 'blog', label: proc{"Something dynamic"}, priority: 0
  end
end

# app/admin/post.rb
ActiveAdmin.configure_resource Post do |config|
  config.menu_item_options = { parent: 'blog' }
end

Adding Custom Menu Items

Sometimes it’s not enough to just customize the menu label. In this case, you can customize the menu for the namespace within the Active Admin initializer.

# config/initializers/active_admin.rb
config.namespace :admin do |admin|
  admin.build_menu do |menu|
    menu.add label: "The Application", url: "/", priority: 0

    menu.add label: "Sites" do |sites|
      sites.add label: "Google",
                url: "http://google.com",
                html_options: { target: :blank }

      sites.add label: "Facebook",
                url: "http://facebook.com"

      sites.add label: "Github",
                url: "http://github.com"
    end
  end
end

This will be registered on application start before your resources are loaded.

Scoping the queries

If your administrators have different access levels, you may sometimes want to scope what they have access to. Assuming your User model has the proper has_many relationships, you can simply scope the listings and finders like so:

ActiveAdmin.configure_resource Post do |config|
  config.scope_to :current_user # limits the accessible posts to `current_user.posts`

  # Or if the association doesn't have the default name:
  config.scope_to :current_user, association_method: :blog_posts

  # Finally, you can pass a block to be called:
  config.scope_to do
    User.most_popular_posts
  end
end

You can also conditionally apply the scope:

ActiveAdmin.configure_resource Post do |config|
  config.scope_to :current_user, if:     proc{ current_user.limited_access? }
  config.scope_to :current_user, unless: proc{ current_user.admin? }
end

Eager loading

A common way to increase page performance is to eliminate N+1 queries by eager loading associations:

class Admin::PostsController < ActiveAdmin::ResourceController
  def apply_includes(collection)
    collection.includes :author, :categories
  end
end

Customizing resource retrieval

Our controllers are built on Inherited Resources, so you can use all of its features.

If you need to customize the collection properties, you can overwrite the scoped_collection method.

class Admin::PostsController < ActiveAdmin::ResourceController
  def scoped_collection
    end_of_association_chain.where(visibility: true)
  end
end

If you need to completely replace the record retrieving code (e.g., you have a custom to_param implementation in your models), override the resource method on the controller:

class Admin::PostsController < ActiveAdmin::ResourceController
  def find_resource
    scoped_collection.where(id: params[:id]).first!
  end
end

Note that if you use an authorization library like CanCan, you should be careful to not write code like this, otherwise your authorization rules won’t be applied:

class Admin::PostsController < ActiveAdmin::ResourceController
  def find_resource
    Post.where(id: params[:id]).first!
  end
end

Belongs To

It’s common to want to scope a series of resources to a relationship. For example a Project may have many Milestones and Tickets. To nest the resource within another, you can use the belongs_to method:

ActiveAdmin.configure_resource Project
ActiveAdmin.configure_resource Ticket do |config|
  config.belongs_to :project
end

Projects will be available as usual and tickets will be available by visiting /admin/projects/1/tickets assuming that a Project with the id of 1 exists. Active Admin does not add “Tickets” to the global navigation because the routes can only be generated when there is a project id.

To create links to the resource, you can add them to a sidebar (one of the many possibilities for how you may with to handle your user interface):

ActiveAdmin.register Project do

  sidebar "Project Details", only: [:show, :edit] do
    ul do
      li link_to "Tickets",    admin_project_tickets_path(resource)
      li link_to "Milestones", admin_project_milestones_path(resource)
    end
  end
end

ActiveAdmin.configure_resource Ticket do |config|
  config.belongs_to :project
end

ActiveAdmin.configure_resource Milestone do |config|
  config.belongs_to :project
end

In some cases (like Projects), there are many sub resources and you would actually like the global navigation to switch when the user navigates “into” a project. To accomplish this, Active Admin stores the belongs_to resources in a separate menu which you can use if you so wish. To use:

ActiveAdmin.configure_resource Ticket do |config|
  config.belongs_to :project
  config.navigation_menu_name = :project
end

ActiveAdmin.configure_resource Milestone do |config|
  config.belongs_to :project
  config.navigation_menu_name = :project
end

Now, when you navigate to the tickets section, the global navigation will only display “Tickets” and “Milestones”. When you navigate back to a non-belongs_to resource, it will switch back to the default menu.

You can also defer the menu lookup until runtime so that you can dynamically show different menus, say perhaps based on user permissions. For example:

ActiveAdmin.configure_resource Ticket do |config|
  config.belongs_to :project
  config.navigation_menu_name = -> { authorized?(:manage, SomeResource) ? :project : :restricted_menu }
end

If you still want your belongs_to resources to be available in the default menu and through non-nested routes, you can use the :optional option. For example:

ActiveAdmin.configure_resource Ticket do |config|
  config.belongs_to :project, optional: true
end