Datagrid Ruby Library

repository·main·Indexed 22 days ago

https://github.com/bogdan/datagrid

A flexible Ruby library for generating reports, admin panels, analytics, and data browsers. It uses a DSL to define scopes, filters, and columns over data from various ORMs. It includes a Rails generator for scaffolding grids, controllers, and views, and supports custom ORM drivers via AbstractDriver and custom filters via BaseFilter. Key features include support for ActiveRecord, CSV export, and dynamic filtering.

Tokens
7.4K
Snippets
25
Records
39
Agent score
77%

What's inside Datagrid

  1. Define columns in Datagrid

    main

    Columns define the data displayed in the grid. Each column has a name and a block to calculate its value. Columns are sortable by default.

    Options for columns:

    • header: The display name for the column.
    • order: Specifies the attribute or logic used for sorting.
    • after: Positions the column after a specific existing column.

    Example:

    column(:activated, header: "Active", order: "activated", after: :name) do
      self.activated?
    end
    column(:activated, header: "Active", order: "activated", after: :name) do
      self.activated?
    end
  2. Handle range filters with Ruby endless ranges

    main

    In Datagrid v2, range filters use Ruby's native range objects. This is a breaking change for custom filter blocks that expect an Array.

    Comparison:

    • V1 value: [1, nil]
    • V2 value: 1..nil

    Example Filter Definition:

    class UsersGrid < Datagrid::Base
      filter(:id, :integer, range: true) do |value, scope|
        # value is now a Range (e.g., 1..5 or 1..nil)
        scope.where(id: value)
      end
    end

    Datagrid provides automatic conversion for backward compatibility:

    • grid.id = [1, nil] becomes 1..nil
    • grid.id = [nil, 5] becomes ..5
    • grid.id = "1..5" becomes 1..5
    class UsersGrid < Datagrid::Base
      filter(:id, :integer, range: true) do |value, scope|
        scope.where(id: value)
      end
    end
  3. Define filters in Datagrid

    main

    Filters allow users to narrow down the data. A filter definition includes a name, a type for value typecasting, and an optional block to apply custom conditions to the scope.

    Supported filter types:

    • text
    • integer
    • float
    • date
    • datetime
    • boolean
    • xboolean (selects between "yes", "no", and "any")
    • enum (selection of provided values)
    • string
    • dynamic (builds dynamic SQL conditions)

    Example of a dynamic filter with a custom condition block:

    filter(:group_name, :string, header: "Group") do |value|
      self.joins(:group).where(groups: {name: value})
    end
  4. Use HTML5 data attributes for Datagrid styling

    main

    In Version 2, Datagrid has moved from using CSS classes to HTML5 data-* attributes for metadata to improve semantics and prevent collisions. When styling your grid or filters, use the following attributes:

    • Filters: Use data-filter for the filter name and data-type for the filter type (e.g., .datagrid-filter[data-filter] and .datagrid-filter[data-type]).
    • Columns: Use data-column on both <th> and <td> elements (e.g., td[data-column]).
    • Tables: The specific grid class on the <table> element (e.g., .datagrid.users_grid) has been removed in favor of a generic .datagrid-table class.

    If you need to revert to the old class-based behavior, you must Modify built-in partials.

    <!-- Version 2 Filter Example -->
    <div class="datagrid-filter" data-filter="category" data-type="string">
      <label for="form_for_grid_category">Category</label>
      <input type="text" name="form_for_grid[category]" id="form_for_grid_category" />
    </div>
    
    <!-- Version 2 Column Example -->
    <table class="datagrid-table">
        <tr>
            <th data-column="name">Name</th>
            <th data-column="category">Category</th>
        </tr>
        <tr>
            <td data-column="name">John</td>
            <td data-column="category">Worker</td>
        </tr>
    </table>
  5. Scaffold a Datagrid in Rails

    main

    Datagrid provides a Rails generator to quickly set up a grid, controller, and views.

    To scaffold a new grid for a model (e.g., skills):

    rails g datagrid:scaffold skills

    This creates:

    • app/grids/skills_grid.rb (The grid definition)
    • app/controllers/skills_controller.rb (The controller)
    • app/views/skills/index.html.erb (The view)
    • A route for resources :skills
    • Assets for styling
    rails g datagrid:scaffold skills
  6. Inherit from ApplicationGrid for custom grids

    main

    In Version 2, the previously recommended BaseGrid has been renamed to ApplicationGrid to better align with Rails naming conventions. You should inherit your specific grids from ApplicationGrid to define shared logic or custom column helpers.

    # app/grids/application_grid.rb
    class ApplicationGrid < Datagrid::Base
      def self.timestamp_column(name, *args, &block)
        column(name, *args) do |model|
          value = block ? block.call(model) : model.public_send(name)
          value&.strftime("%Y-%m-%d")
        end
      end
    end
    
    # app/grids/users_grid.rb
    class UsersGrid < ApplicationGrid
      scope { User }
    
      column(:name)
      timestamp_column(:created_at)
    end
  7. Create a Datagrid report

    main

    To create a report, inherit from Datagrid::Base and define a scope, filters, and columns. The scope defines the base collection of objects (e.g., an ActiveRecord relation), filters define how to narrow down that collection, and columns define what data to display and how to sort it.

    class UsersGrid < Datagrid::Base
      # Define the base collection
      scope do
        User.includes(:group)
      end
    
      # Define filters
      filter(:category, :enum, select: ["first", "second"])
      filter(:disabled, :xboolean)
      filter(:group_id, :integer, multiple: true)
      filter(:logins_count, :integer, range: true)
      filter(:group_name, :string, header: "Group") do |value|
        self.joins(:group).where(groups: {name: value})
      end
    
      # Define columns
      column(:name)
      column(:group, order: -> { joins(:group).order(groups: :name) }) do |user|
        user.name
      end
      column(:active, header: "Activated") do |user|
        !user.disabled
      end
    end
    class UsersGrid < Datagrid::Base
    
      scope do
        User.includes(:group)
      end
    
      filter(:category, :enum, select: ["first", "second"])
      filter(:disabled, :xboolean)
      filter(:group_id, :integer, multiple: true)
      filter(:logins_count, :integer, range: true)
      filter(:group_name, :string, header: "Group") do |value|
        self.joins(:group).where(groups: {name: value})
      end
    
      column(:name)
      column(:group, order: -> { joins(:group).order(groups: :name) }) do |user|
        user.name
      end
      column(:active, header: "Activated") do |user|
        !user.disabled
      end
    
    end
  8. Use datagrid_form_with instead of datagrid_form_for

    main

    Rails has deprecated form_for in favor of form_with. Consequently, datagrid_form_for is deprecated in Datagrid v2. Use datagrid_form_with instead.

    Version 1 (Deprecated):

    datagrid_form_for(@users_grid, url: users_path)

    Version 2 (Modern):

    datagrid_form_with(model: @users_grid, url: users_path)

    Note: The built-in datagrid/form view uses form_with internally regardless of which helper you call.

    datagrid_form_with(model: @users_grid, url: users_path)
  9. Migrate from Datagrid v1 to v2

    main

    Datagrid v2 introduces significant changes to align with modern Ruby and Rails best practices. Key migration areas include:

    API Changes

    • Inheritance: Inherit from Datagrid::Base instead of using include Datagrid.
    • Base Class: Use ApplicationGrid (a subclass of Datagrid::Base) as your recommended base class.
    • Range Filters: Range filters now use Ruby's native endless ranges (e.g., 1..nil) instead of Arrays (e.g., [1, nil]).
    • Multiparameter Attributes: Use Hash instead of Array for attributes with multiple parameters (like ranges).
    • Column Options: The column[url] option has been removed in favor of the format method.

    Frontend Changes

    • Form Helpers: Use datagrid_form_with instead of the deprecated datagrid_form_for.
    • Ordering: datagrid_order_for is deprecated. Include ordering code directly in the datagrid/head partial.
    • CSS: Built-in CSS classes have been renamed to follow modern modular naming conventions (e.g., filter is now datagrid-filter).
    • Views: Replace rake datagrid:copy_partials with rails g datagrid:views to update views.
  10. Use RangedFilter for range-based filtering

    main

    The Datagrid::Filters::RangedFilter module allows you to implement range-based filtering in your datagrids. When the range option is enabled in the filter configuration, the filter can accept several input formats and convert them into a valid range.

    Supported input formats for values:

    • String: Uses .. (inclusive) or ... (exclusive) as separators (e.g., "1..10" or "1...10").
    • Hash: Keys :from and :to (or string keys "from" and "to").
    • Array: An array containing the start and end values (e.g., [1, 10]).
    • Range: A standard Ruby Range object.

    Note: If a range is provided where the start value is greater than the end value, the module automatically reverses them to ensure a valid range.

    # Example of how a ranged filter might be used conceptually
    # (Requires the filter to have `options[:range] = true`)
    
    # String input
    filter_value = "10..20"
    
    # Hash input
    filter_value = { from: 10, to: 20 }
    
    # Array input
    filter_value = [10, 20]
    
    # Range input
    filter_value = 10..20
  11. Define mandatory columns in a Datagrid

    main

    When defining columns in a Datagrid, you can use the mandatory: true option. Mandatory columns have the following behaviors:

    • They are always present in the grid table.
    • They are excluded from the column_names_filter selection list.
    • They are returned by the mandatory_columns method.

    If no columns are explicitly marked as mandatory, the behavior of columns_enabled_by_default may vary based on whether visibility has been explicitly set.

    # Example of defining a mandatory column
    column :id, mandatory: true