Skip to content
Benjamin Guez edited this page May 2, 2015 · 66 revisions

Defines column that will be used to display data.

Example:

  class UserGrid 
    include Datagrid

    scope do
      User.order("users.created_at desc").includes(:group)
    end

    column(:name)
    column(:group, :order => "groups.name") do
      self.group.name
    end
    column(:active, :header => "Activated") do |user|
      !user.disabled
    end

  end

Each column will be used to generate data. In order to create grid that display all users:

  grid = UserGrid.new
  grid.header # => ["Group", "Name", "Disabled"]
  grid.rows   # => [
              #      ["Steve", "Spammers", true],
              #      [ "John", "Spoilers", true],
              #      ["Berry", "Good people", false]
              #    ]
  grid.data   # => Header & Rows

Column value

Column value can be defined by passing a block to Datagrid.column method.

Basic column value

If no block given column it is generated automatically by sending column name method to model.

  column(:name) # => asset.name

The block could have no arguments(instance_eval for each asset will be used).

  column(:completed) { completed? }

If you don't like instance_eval you can use asset as first argument:

  column(:completed) { |asset| asset.completed? }

Advanced column value

For the most complicated columns you can also pass datagrid object itself:

  filter(:category) do |value|
    where("category LIKE '%#{value}%'")
  end

  column(:exactly_matches_category) do |asset, grid|
    asset.category == grid.category
  end

Another advanced use case (version >= 0.9.3):

class MerchantsGrid
 scope { Merchant }
  
  # Dummy filter is not automatically applied to scope
  filter(:period, :date, :range => true, :dummy => true)

  column(:number_of_purchases) do |merchant, grid|
    merchant.purchases.where(:created_at => grid.period).count
  end
end

Sometimes you can even combine previously defined columns into new ones:

column(:total_sales) do |merchant|
  merchant.purchases.sum(:subtotal)
end
column(:number_of_sales) do |merchant|
  merchant.purchases.count
end
column(:average_order_value) do |_, _, row|
  row.total_sales / row.number_of_sales
end

Using database expression (version >= 1.1.0)

This allows to determine how a column should be selected. Right now it only supports ActiveRecord. If specified, it will add the string to the select. This is useful for data aggregation or to make transformations of the data directly in the database.

column(:count_of_users, 'count(user_id)')
column(:uppercase_name, 'upper(name)')

Note, that you should never specify the AS part, since it's added automatically.

HTML Columns (version >= 0.9.1)

Sometimes you might need a different formatting for column value in CSV and HTML. In this case you can use the following construction:

column(:name) do |asset|
  format(asset.name) do |value|
    content_tag(:strong, value)
  end
end

Now when you render an HTML table you will see <strong>NAME</strong> While in CSV (or any other non-HTML representation) it won't be wrapped with <strong> tag.

You can specify if given column should only appear in html view (via the :html option) :

column(:completed, :html => true) do |asset| 
  asset.completed? ? image_tag("green.gif") : image_tag("red.gif")
end
# or do it in partial
column(:actions, :html => true) do |asset|
  render :partial => "admin/assets/actions", :object => asset
end
# if you want to hide a column only from html view and have it only in csv export.
column(:id, :html => false)

Column Value cache (>= 1.3.0)

You can enable grid level cache for column values:

self.cached = true

In this way column values will be cached based on models primary keys You can define other cache key when model don't have primary key:

self.cached = proc {|model| model.identifier }

It is also helpful when aggregation queries are made.

Ordering

Each column supports the following options that is used to specify SQL to sort data by the given column:

  • :order - an order SQL that should be used to sort by this column. Default: report column name if there is database column with this name. Passing false will disable the order for this column.
  • :order_desc - descending order expression from this column. Default: "#{order} desc".
# Basic use case
column(:group_name, :order => "groups.name") { self.group.name }

# Advanced use case
column(
  :priority, 
  # suppose that models with null priority will be always on bottom
  :order => "priority is not null desc, priority", 
  :order_desc => "prioritty is not null desc, priority desc"
) 
# Disable order
column(:title, :order => false)

# Order by joined table
# Allows to join specified table only when order is enabled
# for performance
column(:profile_updated_at, :order => proc { |scope|
  scope.joins(:profile).order("profiles.updated_at")
}) do |model|
  model.profile.updated_at.to_date
end 

In order to specify order the following attributes are used for Datagrid instance:

  • :order - column name to sort with as Symbol. Default: nil.
  • :descending - if true descending suffix is added to specified order. Default: false.
  UserGrid.new(:order => :group, :descending => true).assets # => assets ordered by :group column descending

Default column options

You can specify default options for entire grid by using default_column_options accessor methods. They still can be overwritten at column level.

# Disable default order                           
self.default_column_options = { :order => false } 
# Makes entire report HTML                        
self.default_column_options = { :html => true }   

Columns visibility

Instance API

You are able to show only specific columns in certain context. The column_names instance accessor can be used for that:

grid = UsersGrid.new
grid.data # => [["Id", "Name" "Disabled"], [1, "Allan", true], [2, "Bogdan", false]]
grid.column_names = [:id, :name]
grid.data # => [["Id", "Name], [1, "Allan"], [2, "Bogdan"]]
grid.column_names = nil # Reset to default
grid.data # => [["Id", "Name" "Disabled"], [1, "Allan", true], [2, "Bogdan", false]]

There is several column options that helps to control column names filter content

  • :mandatory - makes column impossible to disable. Hides it from column_names_filter selection

When you specify at least one mandatory column in a grid the column visibility mechanism become different:

  • only mandatory columns are displayed by default
  • non-mandatory columns need to be explicitly enabled by column_names attribute

Example:

class Grid
  include Datagrid
  scope { User }
  column(:id, :mandatory => true)
  column(:name, :mandatory => true)
  column(:category)
  [:posts, :comments].each do |association|
    column(:"number_of_#{association}") do |model|
      model.send(association).count
    end
  end
end

grid = Grid.new
grid.data # => [["Id", "Name"], [1, "Bogdan Gusiev"], [2, "Dominic Coryell"]]
grid.column_names = ["category", "number_of_posts"]
grid.data # => [ ["Id", "Name", "Category", "Number of posts], 
          #      [1, "Bogdan Gusiev", "developer", 5], 
          #      [2, "Dominic Coryell", "manager", 3] ]

If and Unless options (version >= 1.1.0)

You can specify :if and :unless options to a column, to determine if the column should be shown or not. If a symbol is given, it will call that method on the grid, if a proc is given, it will be called with the grid as an argument.

Example:

column(:name, :if => :show_name?)
#equivalent:
column(:name, :unless => proc {|grid| !grid.show_name? })
# More realistic
filter(:category) do |value|
  where("category like '%#{value}%'")
end
column(:exactly_match_category, :if => proc {|grid| grid.category.present?}) do |model, grid|
  model.category == grid.category
end

Dynamic Columns

In some cases you can not define columns at class level. So you can define columns on instance level

grid = MyGrid.new
grid.column(:extra_data) do |model|
  model.extra_data
end

In this example extra_data column will not be defined for any other MyGrid instance in a project. Only for current instance stored in grid local variable.

Same behaviour can be achieved by using dynamic inside of grid class. More live example:

class CampaignsGrid
  scope { Campaign }

  filter(:account_id, :integer)
  
  def account
    Account.find(account_id)
  end

  dynamic do 
    account.sales_categories.each do |category|
      column(:"sales_in_#{category.name}") do |campaign|
        campaign.sales.where(category_id: category.id).sum(:subtotal)
      end
    end
  end
end

Frontend (version >= 1.0)

Column selection can be available as select[multiple] or several input[type=checkbox] in datagrid form. Use column_names_filter to reach that behavior. column_names_filter accepts same options as :enum filter.

column_names_filter(:header => "Column", :checkboxes => true)

In this case column names select will only contain counter columns. id name category columns will be always present.

You can manually specify which columns should be selectable by end user:

column_names_filter(:select => [:metric_one, :metric_two, :metric_three])

In this way you can hide columns from end user.

Localization

Column header can be specified with :header option:

column(:active, :header => "Activated")

By default it is generated from column name. Also you can use localization file if you have multilanguage application.

Example: In order to localize column :name in SimpleReport use the key datagrid.simple_report.columns.name

Clone this wiki locally