module Datagrid::Columns

Public Class Methods

included(base) click to toggle source

@visibility private

# File lib/datagrid/columns.rb, line 36
def self.included(base)
  base.extend         ClassMethods
  base.class_eval do
    include Datagrid::Core

    class_attribute :default_column_options, instance_writer: false, default: {}
    class_attribute :batch_size, default: 1000
    class_attribute :columns_array, default: []
    class_attribute :cached, default: false
    class_attribute :decorator, instance_writer: false
  end
end
new(*) click to toggle source

@!visibility private

Calls superclass method Datagrid::Core::new
# File lib/datagrid/columns.rb, line 372
def initialize(*)
  self.columns_array = self.class.columns_array.clone
  super
end

Public Instance Methods

assets() click to toggle source

@!visibility private

Calls superclass method Datagrid::Core#assets
# File lib/datagrid/columns.rb, line 199
def assets
  append_column_preload(
    driver.append_column_queries(
      super, columns.select(&:query)
    )
  )
end
available_columns() click to toggle source

@return [Array<Datagrid::Columns::Column>] all columns that are possible to be displayed for the current grid object

@example

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> ]
# File lib/datagrid/columns.rb, line 394
def available_columns
  columns_array.select do |column|
    column.enabled?(self)
  end
end
column(name, query = nil, **options, &block) click to toggle source

Defines a column at instance level

@see Datagrid::Columns::ClassMethods#column

# File lib/datagrid/columns.rb, line 367
def column(name, query = nil, **options, &block)
  self.class.define_column(columns_array, name, query, **options, &block)
end
column_by_name(name) click to toggle source

Finds a column definition by name @param name [String, Symbol] column name to be found @return [Datagrid::Columns::Column, nil]

# File lib/datagrid/columns.rb, line 318
def column_by_name(name)
  self.class.find_column_by_name(columns_array, name)
end
columns(*column_names, data: false, html: false) click to toggle source

@param column_names [Array<Symbol, String>] @return [Array<Datagrid::Columns::Column>] all columns selected in grid instance @example

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
# File lib/datagrid/columns.rb, line 295
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) click to toggle source

@param column_names [Array<String>] list of column names if you want to limit data only to specified columns @return [Array<Array<Object>>] data for each row in datagrid assets with header.

# File lib/datagrid/columns.rb, line 242
def data(*column_names)
  self.rows(*column_names).unshift(self.header(*column_names))
end
data_columns(*column_names, **options) click to toggle source

@param column_names [Array<String, Symbol>] list of column names if you want to limit data only to specified columns @return columns that can be represented in plain data(non-html) way

# File lib/datagrid/columns.rb, line 305
def data_columns(*column_names, **options)
  self.columns(*column_names, **options, data: true)
end
data_hash() click to toggle source

Return Array of Hashes where keys are column names and values are column values for each row in filtered datagrid relation.

@example

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"}]
# File lib/datagrid/columns.rb, line 260
def data_hash
  map_with_batches do |asset|
    hash_for(asset)
  end
end
data_row(asset) click to toggle source

@return [Datagrid::Columns::DataRow] an object representing a grid row. @example

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
# File lib/datagrid/columns.rb, line 360
def data_row(asset)
  ::Datagrid::Columns::DataRow.new(self, asset)
end
data_value(column_name, asset) click to toggle source

@return [Object] a cell data value for given column name and asset

# File lib/datagrid/columns.rb, line 401
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) click to toggle source

@return [Object] a decorated version of given model if decorator is specified or the model otherwise.

# File lib/datagrid/columns.rb, line 424
def decorate(model)
  self.class.decorate(model)
end
format(value, &block) click to toggle source

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

@example

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

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

@return [Datagrid::Columns::Column::ResponseFormat] Format object

Calls superclass method
# File lib/datagrid/columns.rb, line 337
def format(value, &block)
  if block_given?
    self.class.format(value, &block)
  else
    # don't override Object#format method
    super
  end
end
generic_value(column, model) click to toggle source

@!visibility private

# File lib/datagrid/columns.rb, line 429
def generic_value(column, model)
  cache(column, model, :generic_value) do
    presenter = decorate(model)
    unless column.enabled?(self)
      raise Datagrid::ColumnUnavailableError, "Column #{column.name} disabled for #{inspect}"
    end

    if column.data_block.arity >= 1
      Datagrid::Utils.apply_args(presenter, self, data_row(model), &column.data_block)
    else
      presenter.instance_eval(&column.data_block)
    end
  end
end
hash_for(asset) click to toggle source

@param asset [Object] asset from datagrid scope @return [Hash] A mapping where keys are column names and values are column values for the given asset

# File lib/datagrid/columns.rb, line 224
def hash_for(asset)
  result = {}
  self.data_columns.each do |column|
    result[column.name] = data_value(column, asset)
  end
  result
end
header(*column_names) click to toggle source

@param column_names [Array<String>] list of column names if you want to limit data only to specified columns @return [Array<String>] human readable column names. See also “Localization” section

# File lib/datagrid/columns.rb, line 209
def header(*column_names)
  data_columns(*column_names).map(&:header)
end
html_columns(*column_names, **options) click to toggle source

@param column_names [Array<String>] list of column names if you want to limit data only to specified columns @return all columns that can be represented in HTML table

# File lib/datagrid/columns.rb, line 311
def html_columns(*column_names, **options)
  self.columns(*column_names, **options, html: true)
end
html_value(column_name, context, asset) click to toggle source

@return [Object] a cell HTML value for given column name and asset and view context

# File lib/datagrid/columns.rb, line 411
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
reset() click to toggle source

@!visibility private

Calls superclass method Datagrid::Core#reset
# File lib/datagrid/columns.rb, line 445
def reset
  super
  @cache = {}
end
row_for(asset, *column_names) click to toggle source

@param asset [Object] asset from datagrid scope @param column_names [Array<String>] list of column names if you want to limit data only to specified columns @return [Array<Object>] column values for given asset

# File lib/datagrid/columns.rb, line 216
def row_for(asset, *column_names)
  data_columns(*column_names).map do |column|
    data_value(column, asset)
  end
end
rows(*column_names) click to toggle source

@param column_names [Array<String>] list of column names if you want to limit data only to specified columns @return [Array<Array<Object>>] with data for each row in datagrid assets without header

# File lib/datagrid/columns.rb, line 234
def rows(*column_names)
  map_with_batches do |asset|
    self.row_for(asset, *column_names)
  end
end
to_csv(*column_names, **options) click to toggle source

@param column_names [Array<String>] @param options [Hash] CSV generation options @return [String] a CSV representation of the data in the grid

@example

grid.to_csv
grid.to_csv(:id, :name)
grid.to_csv(col_sep: ';')
# File lib/datagrid/columns.rb, line 274
def to_csv(*column_names, **options)
  require "csv"
  CSV.generate(
    headers: self.header(*column_names),
    write_headers: true,
    **options
  ) do |csv|
    each_with_batches do |asset|
      csv << row_for(asset, *column_names)
    end
  end
end

Protected Instance Methods

append_column_preload(relation) click to toggle source
# File lib/datagrid/columns.rb, line 452
def append_column_preload(relation)
  columns.inject(relation) do |current, column|
    column.append_preload(current)
  end
end
cache(column, asset, type) { || ... } click to toggle source
# File lib/datagrid/columns.rb, line 458
def cache(column, asset, type)
  @cache ||= {}
  unless cached?
    @cache.clear
    return yield
  end
  key = cache_key(asset)
  unless key
    raise(Datagrid::CacheKeyError, "Datagrid Cache key is #{key.inspect} for #{asset.inspect}.")
  end
  @cache[column.name] ||= {}
  @cache[column.name][key] ||= {}
  @cache[column.name][key][type] ||= yield
end
cache_key(asset) click to toggle source
# File lib/datagrid/columns.rb, line 473
def cache_key(asset)
  if cached.respond_to?(:call)
    cached.call(asset)
  else
    driver.default_cache_key(asset)
  end
rescue NotImplementedError
  raise Datagrid::ConfigurationError, "#{self} is setup to use cache. But there was appropriate cache key found for #{asset.inspect}. Please set cached option to block with asset as argument and cache key as returning value to resolve the issue."
end
each_with_batches(&block) click to toggle source
# File lib/datagrid/columns.rb, line 492
def each_with_batches(&block)
  if batch_size && batch_size > 0
    driver.batch_each(assets, batch_size, &block)
  else
    assets.each(&block)
  end
end
map_with_batches(&block) click to toggle source
# File lib/datagrid/columns.rb, line 484
def map_with_batches(&block)
  result = []
  each_with_batches do |asset|
    result << block.call(asset)
  end
  result
end
value_from_html_block(context, asset, column) click to toggle source
# File lib/datagrid/columns.rb, line 500
def value_from_html_block(context, asset, column)
  args = []
  remaining_arity = column.html_block.arity
  remaining_arity = 1 if remaining_arity < 0

  asset = decorate(asset)

  if column.data?
    args << data_value(column, asset)
    remaining_arity -= 1
  end

  args << asset if remaining_arity > 0
  args << self if remaining_arity > 1

  context.instance_exec(*args, &column.html_block)
end