ActiveScaffold Sortable adds AJAX drag-and-drop ordering to Active Scaffold lists and association subforms. It supports ordinary ordered lists, automatic configuration for acts_as_list models, and sibling ordering in nested-set and Ancestry trees.

Requirements

  • Ruby 2.0 or newer
  • Active Scaffold 4.4.0 or newer
  • A Rails version supported by the selected Active Scaffold version
  • jQuery UI Sortable on pages that use the plugin

For Rails 3 applications, use an active_scaffold_sortable 3.2.x release.

Installation

Add the following line to your Gemfile:

gem 'active_scaffold_sortable'

Then run:

bundle install

The engine automatically registers the Active Scaffold action, route, JavaScript, stylesheet, and view overrides.

Ordered lists

With acts_as_list

Add a position column and configure the model with acts_as_list:

class AddPositionToEntries < ActiveRecord::Migration[6.0]
  def change
    add_column :entries, :position, :integer
    add_index :entries, :position
  end
end
class Entry < ApplicationRecord
  acts_as_list
end

The plugin detects acts_as_list, enables the :sortable action, and uses the model’s configured position column. No Active Scaffold controller configuration is required.

Scopes configured in acts_as_list still define separate logical lists. A rendered Active Scaffold list should contain records from only one scope, so dragging cannot mix independent lists.

Without acts_as_list

Sorting can use any persisted column that accepts one-based integer positions. Enable the action and select the column explicitly:

class EntriesController < ApplicationController
  active_scaffold :entry do |config|
    config.actions << :sortable
    config.sortable.column = :position
  end
end

After every drop, the visible records receive consecutive positions beginning at 1. The plugin updates records directly with update_all, so model validations and callbacks do not run. It also updates updated_at when that column exists. The reorder action uses Active Scaffold’s update authorization check.

Sortable association subforms

A collection association is draggable in a create or update form when the associated model’s Active Scaffold configuration includes :sortable. This works whether sorting was enabled automatically by acts_as_list or configured manually:

class TasksController < ApplicationController
  active_scaffold :task do |config|
    config.actions << :sortable
    config.sortable.column = :position
  end
end

For example, a Project form that renders a has_many :tasks subform will allow its task rows to be reordered. Dragging changes the hidden position fields in the form; the new order is persisted when the parent form is submitted, rather than through a separate reorder request. New associated records without a position are placed after the highest existing position.

Only collection associations are sortable. The associated scaffold may omit the :list action when sorting is needed only in a subform.

Tree models

Tree support reorders nodes only among their current siblings. It does not move a node to a different parent.

Nested sets (awesome_nested_set)

Models using awesome_nested_set are detected automatically:

class Category < ApplicationRecord
  acts_as_nested_set
end

The plugin enables :sortable, uses the model’s configured left column, and calls the nested-set movement methods to preserve valid left and right bounds. Root nodes can also be reordered as siblings.

Detection is API-based. Another nested-set implementation may work if it provides nested_set_scope, left_column_name, self_and_siblings, move_left, and move_right, but awesome_nested_set is the supported integration.

Ancestry

Ancestry models are recognized during reordering, but sorting is not enabled automatically. Add a separate position column and configure it explicitly:

class Category < ApplicationRecord
  has_ancestry
end
class CategoriesController < ApplicationController
  active_scaffold :category do |config|
    config.actions << :sortable
    config.sortable.column = :position
  end
end

Both root nodes and children can be reordered within the first submitted record’s sibling set. Do not use the ancestry column as the sortable column: it stores a path, not a sibling position.

Configuration

Drag handle

The whole row is the drag handle by default. To add a dedicated handle column at the beginning or end of the list:

config.sortable.add_handle_column = :first
# or
config.sortable.add_handle_column = :last

The accepted values are :first, :last, and false/nil.

Refresh after reordering

The default response only reapplies alternating row styles. Refresh the entire list after each reorder when other displayed values depend on the order:

config.sortable.refresh_list = true

Advanced DOM options

config.sortable.options is merged into the sortable container’s data-* attributes. The supported overrides are integration hooks for custom Active Scaffold markup:

config.sortable.options = {
  tag: "> tr",                     # selector for draggable items
  handle: ".my-drag-handle",       # selector that starts dragging
  content_selector: ".my-records", # sortable content in the container
  key: "as_entries-tbody",         # serialized parameter base
  format: "^[^_-].*-(.*)-row$",    # extracts the record ID from a row ID
  with: "scope_id=42"               # extra reorder request parameters
}

These options are not a general pass-through for every jQuery UI Sortable setting. If you override key, keep it aligned with the parameter name expected by the reorder action.

You can also define defaults for every sortable scaffold in an initializer, before Active Scaffold configurations are built:

ActiveScaffold::Config::Sortable.add_handle_column = :first
ActiveScaffold::Config::Sortable.refresh_list = true
ActiveScaffold::Config::Sortable.options = { handle: ".my-drag-handle" }

UI adapter support

ActiveScaffold Sortable 3.7 exposes the wrapper around sortable lists and association subforms as the sortable_container UI element. The same adapter configuration applies in both contexts:

ActiveScaffold.add_element_attributes :sortable_container,
                                      class: "overflow-auto"

The default tag is div; an adapter can change it when its markup requires a different container:

ActiveScaffold.set_element_tag :sortable_container, :section

Adapter attributes are merged with the sortable-container class and the data-* attributes used for drag-and-drop behavior. See Customizing UI Markup and Integrating CSS Frameworks for the complete UI adapter API.

Effects on the Active Scaffold list

Enabling :sortable also:

  • Disables pagination so every draggable record is present in one list
  • Orders the list by the sortable column in ascending order
  • Disables sorting by other columns
  • Hides the sortable column from normal action columns, while retaining it as a hidden field in subforms

The browser submits only rendered rows. Filters and nested views therefore update only the visible subset, while records not shown retain their stored positions. In an ordinary list this can produce duplicate positions. Keep each sortable view within one logical list or sibling set, and use filtering with care.

Support

For help, use the Active Scaffold discussion group or open an issue.

Get Plugin