Module: Datagrid::Columns

Included in:
Base
Defined in:
lib/datagrid/columns.rb,
lib/datagrid/columns/column.rb

Overview

Defines a column to be used for displaying data in a Datagrid.

class UserGrid < ApplicationGrid
  scope do
    User.order("users.created_at desc").joins(: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 is used to generate data for the grid.

To create a grid displaying 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
grid.data_hash # => [
               #      { name: "Steve", group: "Spammers", active: true },
               #      { name: "John", group: "Spoilers", active: true },
               #      { name: "Berry", group: "Good people", active: false },
               #    ]
}

Column Value

The value of a column can be defined by passing a block to Datagrid.column.

Basic Column Value

If no block is provided, the column value is generated automatically by sending the column name method to the model.

column(:name) # => asset.name

Using instance_eval:

column(:completed) { completed? }

Using the asset as an argument:

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

Advanced Column Value

You can also pass the Datagrid object itself to define more complex column values.

Using filters with columns:

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

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

Combining columns:

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 Expressions

Columns can use database expressions to directly manipulate data in the database.

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

HTML Columns

Columns can have different formats for HTML and non-HTML representations.

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

Column Value Cache

Enables grid-level caching for column values.

self.cached = true

Ordering

Columns can specify SQL ordering expressions using the :order and :order_desc options.

Basic ordering:

column(:group_name, order: "groups.name") { self.group.name }

In example above descending order is automatically inherited from ascending order specified. When such default is not enough, specify both options together:

column(
  :priority,
  # models with null priority are always on bottom
  # no matter if sorting ascending or descending
  order: "priority is not null desc, priority",
  order_desc: "priority is not null desc, priority desc"
)

Disable order like this:

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

Order by a calculated value

column(
  :duration_request,
  order: "(requests.finished_at - requests.accepted_at)"
) do |model|
  Time.at(model.finished_at - model.accepted_at).strftime("%H:%M:%S")
end

Default Column Options

Default options for all columns in a grid can be set using default_column_options.

self.default_column_options = { order: false }

Columns Visibility

Columns can be dynamically shown or hidden based on the grid's column_names accessor.

grid.column_names = [:id, :name]

Dynamic Columns

Columns can be defined dynamically on a grid instance or based on data.

Adding a dynamic column:

grid.column(:extra_data) do |model|
  model.extra_data
end

Localization

Column headers can be localized using the :header option or through i18n files.

column(:active, header: Proc.new { I18n.t("activated") })

Preloading Associations

Preload database associations for better performance.

Automatic association preloading:

column(:group) do |user|
  user.group.name
end

Custom preloading:

column(:account_name, preload: { |s| s.includes(:account) })

Decorator

A decorator or presenter class can be used around each object in the scope.

decorate { UserPresenter }
column(:created_at) do |presenter|
  presenter.user.created_at
end

Defined Under Namespace

Modules: ClassMethods Classes: Column, DataRow

Instance Method Summary collapse

Instance Method Details

#available_columnsArray<Datagrid::Columns::Column>

Returns all columns that are possible to be displayed for the current grid object.

Examples:

class MyGrid
  filter(:search) {|scope, value| scope.full_text_search(value)}
  column(:id)
  column(:name, mandatory: true)
  column(:search_match, if: proc {|grid| grid.search.present? }) do |model, grid|
    search_match_line(model.searchable_content, grid.search)
  end
end

grid = MyGrid.new
grid.columns # => [ #<Column:name> ]
grid.available_columns # => [ #<Column:id>, #<Column:name> ]
grid.search = "keyword"
grid.available_columns # => [ #<Column:id>, #<Column:name>, #<Column:search_match> ]

Returns:



592
593
594
595
596
# File 'lib/datagrid/columns.rb', line 592

def available_columns
  columns_array.select do |column|
    column.enabled?(self)
  end
end

#batch_sizeInteger

Returns:

  • (Integer)

See Also:



# File 'lib/datagrid/columns.rb', line 224

#batch_size=(value) ⇒ void

This method returns an undefined value.

Specify a default batch size when generating CSV or just data.

Examples:

Set batch size to 500

self.batch_size = 500

Disable batches

self.batch_size = nil

Parameters:

  • value (Integer)

    a batch size when generating CSV or just data. Default: 1000



# File 'lib/datagrid/columns.rb', line 215

#column(name, query = nil, **options, &block) ⇒ Object

Defines a column at instance level



565
566
567
# File 'lib/datagrid/columns.rb', line 565

def column(name, query = nil, **options, &block)
  self.class.define_column(columns_array, name, query, **options, &block)
end

#column_by_name(name) ⇒ Datagrid::Columns::Column?

Finds a column by name

Parameters:

  • name (String, Symbol)

    column name to be found

Returns:



517
518
519
# File 'lib/datagrid/columns.rb', line 517

def column_by_name(name)
  self.class.find_column_by_name(columns_array, name)
end

#columns(*column_names, data: false, html: false) ⇒ Array<Datagrid::Columns::Column>

Returns all columns selected in grid instance.

Examples:

MyGrid.new.columns # => all defined columns
grid = MyGrid.new(column_names: [:id, :name])
grid.columns # => id and name columns
grid.columns(:id, :category) # => id and category column

Parameters:

  • column_names (Array<Symbol, String>)
  • data (Boolean) (defaults to: false)

    return only data columns

  • html (Boolean) (defaults to: false)

    return only HTML columns

Returns:



491
492
493
494
495
496
497
# File 'lib/datagrid/columns.rb', line 491

def columns(*column_names, data: false, html: false)
  self.class.filter_columns(
    columns_array, *column_names, data: data, html: html,
  ).select do |column|
    column.enabled?(self)
  end
end

#data(*column_names) ⇒ Array<Array<Object>>

Returns data for each row in datagrid assets with header.

Parameters:

  • column_names (Array<String, Symbol>)

    list of column names if you want to limit data only to specified columns.

Returns:

  • (Array<Array<Object>>)

    data for each row in datagrid assets with header.



438
439
440
# File 'lib/datagrid/columns.rb', line 438

def data(*column_names)
  rows(*column_names).unshift(header(*column_names))
end

#data_columns(*column_names, html: false) ⇒ Array<Datagrid::Columns::Column>

Returns columns that can be represented in plain data(non-html) way.

Parameters:

  • column_names (Array<String, Symbol>)

    list of column names if you want to limit data only to specified columns

  • html (Boolean) (defaults to: false)

    return only HTML columns

Returns:



503
504
505
# File 'lib/datagrid/columns.rb', line 503

def data_columns(*column_names, html: false)
  columns(*column_names, html: html, data: true)
end

#data_hashArray<Hash{Symbol => Object}>

Returns an array of hashes representing the rows in the filtered datagrid relation.

Examples:

class MyGrid
  scope { Model }
  column(:id)
  column(:name)
end

Model.create!(name: "One")
Model.create!(name: "Two")

MyGrid.new.data_hash # => [{name: "One"}, {name: "Two"}]

Returns:

  • (Array<Hash{Symbol => Object}>)

    an array of hashes representing the rows in the filtered datagrid relation



455
456
457
458
459
# File 'lib/datagrid/columns.rb', line 455

def data_hash
  map_with_batches do |asset|
    hash_for(asset)
  end
end

#data_row(asset) ⇒ Datagrid::Columns::DataRow

Returns an object representing a grid row.

Examples:

class MyGrid
  scope { User }
  column(:id)
  column(:name)
  column(:number_of_purchases) do |user|
    user.purchases.count
  end
end

row = MyGrid.new.data_row(User.last)
row.id # => user.id
row.number_of_purchases # => user.purchases.count

Parameters:

  • asset (Object)

    one of the assets from grid scope

Returns:



559
560
561
# File 'lib/datagrid/columns.rb', line 559

def data_row(asset)
  ::Datagrid::Columns::DataRow.new(self, asset)
end

#data_value(column_name, asset) ⇒ Object

Returns a cell data value for given column name and asset.

Parameters:

  • column_name (String, Symbol)

    column name

  • asset (Object)

    one of the assets from grid scope

Returns:

  • (Object)

    a cell data value for given column name and asset



601
602
603
604
605
606
607
608
609
# File 'lib/datagrid/columns.rb', line 601

def data_value(column_name, asset)
  column = column_by_name(column_name)
  cache(column, asset, :data_value) do
    raise "no data value for #{column.name} column" unless column.data?

    result = generic_value(column, asset)
    result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_data : result
  end
end

#decorate(model) ⇒ Object

Returns a decorated version of given model if decorator is specified or the model otherwise.

Parameters:

  • model (Object)

    one of the assets from grid scope

Returns:

  • (Object)

    a decorated version of given model if decorator is specified or the model otherwise.



629
630
631
# File 'lib/datagrid/columns.rb', line 629

def decorate(model)
  self.class.decorate(model)
end

#default_column_optionsHash

Returns default options passed to #column method call.

Returns:

  • (Hash)

    default options passed to #column method call

See Also:



# File 'lib/datagrid/columns.rb', line 211

#default_column_options=(value) ⇒ Hash

Returns default options passed to #column method call.

Examples:

Disable default order

self.default_column_options = { order: false }

Makes entire report HTML

self.default_column_options = { html: true }

Parameters:

  • value (Hash)

    default options passed to #column method call

Returns:

  • (Hash)

    default options passed to #column method call



# File 'lib/datagrid/columns.rb', line 203

#format(value, &block) ⇒ Datagrid::Columns::Column::ResponseFormat

Gives ability to have a different formatting for CSV and HTML column value.

Examples:

Formating using Rails view helpers

column(:name) do |model|
  format(model.name) do |value|
    tag.strong(value)
  end
end

Formatting using a separated view template

column(:company) do |model|
  format(model.company.name) do
    render partial: "companies/company_with_logo", locals: {company: model.company }
  end
end

Returns:



535
536
537
538
539
540
541
542
# File 'lib/datagrid/columns.rb', line 535

def format(value, &block)
  if block
    self.class.format(value, &block)
  else
    # don't override Object#format method
    super
  end
end

#hash_for(asset) ⇒ Hash

Returns A mapping where keys are column names and values are column values for the given asset.

Parameters:

  • asset (Object)

    asset from datagrid scope

Returns:

  • (Hash)

    A mapping where keys are column names and values are column values for the given asset



418
419
420
421
422
423
424
# File 'lib/datagrid/columns.rb', line 418

def hash_for(asset)
  result = {}
  data_columns.each do |column|
    result[column.name] = data_value(column, asset)
  end
  result
end

#header(*column_names) ⇒ Array<String>

Returns human readable column names. See also "Localization" section.

Parameters:

  • column_names (Array<String, Symbol>)

    list of column names if you want to limit data only to specified columns

Returns:

  • (Array<String>)

    human readable column names. See also "Localization" section



401
402
403
# File 'lib/datagrid/columns.rb', line 401

def header(*column_names)
  data_columns(*column_names).map(&:header)
end

#html_columns(*column_names, data: false) ⇒ Object

Returns all columns that can be represented in HTML table.

Parameters:

  • column_names (Array<String>)

    list of column names if you want to limit data only to specified columns

  • data (Boolean) (defaults to: false)

    return only data columns

Returns:

  • all columns that can be represented in HTML table



510
511
512
# File 'lib/datagrid/columns.rb', line 510

def html_columns(*column_names, data: false)
  columns(*column_names, data: data, html: true)
end

#html_value(column_name, context, asset) ⇒ Object

Returns a cell HTML value for given column name and asset and view context.

Parameters:

  • column_name (String, Symbol)

    column name

  • asset (Object)

    one of the assets from grid scope

  • context (ActionView::Base)

    view context object

Returns:

  • (Object)

    a cell HTML value for given column name and asset and view context



615
616
617
618
619
620
621
622
623
624
625
# File 'lib/datagrid/columns.rb', line 615

def html_value(column_name, context, asset)
  column = column_by_name(column_name)
  cache(column, asset, :html_value) do
    if column.html? && column.html_block
      value_from_html_block(context, asset, column)
    else
      result = generic_value(column, asset)
      result.is_a?(Datagrid::Columns::Column::ResponseFormat) ? result.call_html(context) : result
    end
  end
end

#row_for(asset, *column_names) ⇒ Array<Object>

Returns column values for given asset.

Parameters:

  • asset (Object)

    asset from datagrid scope

  • column_names (Array<String, Symbol>)

    list of column names if you want to limit data only to specified columns

Returns:

  • (Array<Object>)

    column values for given asset



409
410
411
412
413
# File 'lib/datagrid/columns.rb', line 409

def row_for(asset, *column_names)
  data_columns(*column_names).map do |column|
    data_value(column, asset)
  end
end

#rows(*column_names) ⇒ Array<Array<Object>>

Returns with data for each row in datagrid assets without header.

Parameters:

  • column_names (Array<String,Symbol>)

    list of column names if you want to limit data only to specified columns

Returns:

  • (Array<Array<Object>>)

    with data for each row in datagrid assets without header



429
430
431
432
433
# File 'lib/datagrid/columns.rb', line 429

def rows(*column_names)
  map_with_batches do |asset|
    row_for(asset, *column_names)
  end
end

#to_csv(*column_names, **options) ⇒ String

Returns a CSV representation of the data in the grid.

Examples:

grid.to_csv
grid.to_csv(:id, :name)
grid.to_csv(col_sep: ';')

Parameters:

  • column_names (Array<String,Symbol>)
  • options (Hash)

    CSV generation options

Returns:

  • (String)

    a CSV representation of the data in the grid



469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/datagrid/columns.rb', line 469

def to_csv(*column_names, **options)
  require "csv"
  CSV.generate(
    headers: header(*column_names),
    write_headers: true,
    **options,
  ) do |csv|
    each_with_batches do |asset|
      csv << row_for(asset, *column_names)
    end
  end
end