module Datagrid::Columns
Public Class Methods
@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
@!visibility private
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
@!visibility private
Datagrid::Core#assets
# File lib/datagrid/columns.rb, line 199 def assets append_column_preload( driver.append_column_queries( super, columns.select(&:query) ) ) end
@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
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
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
@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
@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
@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
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
@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
@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
@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
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
# 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
@!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
@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
@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
@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
@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
@!visibility private
Datagrid::Core#reset
# File lib/datagrid/columns.rb, line 445 def reset super @cache = {} end
@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
@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
@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
# File lib/datagrid/columns.rb, line 452 def append_column_preload(relation) columns.inject(relation) do |current, column| column.append_preload(current) end end
# 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
# 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
# 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
# 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
# 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