# This is extended as class level into Datatable
module Effective
module EffectiveDatatable
module Rendering
BLANK = ''.freeze
protected
# So the idea here is that we want to do as much as possible on the database in ActiveRecord
# And then run any array_columns through in post-processed results
def table_data
col = collection
if active_record_collection?
col = table_tool.order(col)
col = table_tool.search(col)
if table_tool.search_terms.present? && array_tool.search_terms.blank?
self.display_records = active_record_collection_size(col)
end
end
if array_tool.search_terms.present?
col = self.arrayize(col)
col = array_tool.search(col)
self.display_records = col.size
end
if array_tool.order_by_column.present?
col = self.arrayize(col)
col = array_tool.order(col)
end
self.display_records ||= total_records
if col.kind_of?(Array)
col = array_tool.paginate(col)
else
col = table_tool.paginate(col)
col = self.arrayize(col)
end
self.format(col)
col = self.finalize(col)
end
def arrayize(collection)
return collection if @arrayized # Prevent the collection from being arrayized more than once
@arrayized = true
# We want to use the render :collection for each column that renders partials
rendered = {}
(display_table_columns || table_columns).each do |name, opts|
if opts[:partial] && opts[:visible]
locals = HashWithIndifferentAccess.new(
datatable: self,
table_column: table_columns[name],
controller_namespace: controller_namespace,
show_action: (opts[:partial_locals] || {})[:show_action],
edit_action: (opts[:partial_locals] || {})[:edit_action],
destroy_action: (opts[:partial_locals] || {})[:destroy_action],
unarchive_action: (opts[:partial_locals] || {})[:unarchive_action]
)
locals.merge!(opts[:partial_locals]) if opts[:partial_locals]
if active_record_collection?
if locals[:show_action] == :authorize
locals[:show_action] = (EffectiveDatatables.authorized?(controller, :show, collection_class) rescue false)
end
if locals[:edit_action] == :authorize
locals[:edit_action] = (EffectiveDatatables.authorized?(controller, :edit, collection_class) rescue false)
end
if locals[:destroy_action] == :authorize
locals[:destroy_action] = (EffectiveDatatables.authorized?(controller, :destroy, collection_class) rescue false)
end
if locals[:unarchive_action] == :authorize
locals[:unarchive_action] = (EffectiveDatatables.authorized?(controller, :unarchive, collection_class) rescue false)
end
end
# If locals[:show_action] == :authorize_each, this will get run again.
rendered[name] = (render(
:partial => opts[:partial],
:as => opts[:partial_local],
:collection => collection,
:formats => :html,
:locals => locals,
:spacer_template => '/effective/datatables/spacer_template',
) || '').split('EFFECTIVEDATATABLESSPACER')
end
end
collection.each_with_index.map do |obj, index|
(display_table_columns || table_columns).map do |name, opts|
begin
if opts[:visible] == false
BLANK
elsif opts[:partial]
rendered[name][index]
elsif opts[:block]
view.instance_exec(obj, collection, self, &opts[:block])
elsif opts[:proc]
view.instance_exec(obj, collection, self, &opts[:proc])
elsif opts[:type] == :belongs_to
(obj.send(name) rescue nil)
elsif opts[:type] == :belongs_to_polymorphic
(obj.send(name) rescue nil)
elsif opts[:type] == :has_many
(obj.send(name).map { |obj| obj.to_s }.join('
') rescue BLANK)
elsif opts[:type] == :has_and_belongs_to_many
(obj.send(name).map { |obj| obj.to_s }.join('
') rescue BLANK)
elsif opts[:type] == :bulk_actions_column
BLANK
elsif opts[:type] == :year
obj.send(name).try(:year)
elsif opts[:type] == :obfuscated_id
(obj.send(:to_param) rescue nil).to_s
elsif opts[:type] == :effective_address
(Array(obj.send(name)) rescue [])
elsif opts[:type] == :effective_roles
(obj.send(:roles) rescue [])
elsif obj.kind_of?(Array) # Array backed collection
obj[opts[:array_index]]
elsif opts[:sql_as_column]
obj[name]
else
obj.send(name)
end
rescue => e
Rails.env.production? ? obj.try(:[], name) : raise(e)
end
end
end
end
def format(collection)
collection.each do |row|
(display_table_columns || table_columns).each_with_index do |(name, opts), index|
value = row[index]
next if value == nil || value == BLANK || opts[:visible] == false
next if opts[:block] || opts[:partial] || opts[:proc]
if opts[:sql_as_column]
row[index] = value.to_s
end
case (opts[:format] || opts[:type])
when :belongs_to, :belongs_to_polymorphic
row[index] = value.to_s
when :has_many
if value.kind_of?(Array)
if value.length == 0
row[index] = BLANK
elsif value.length == 1
row[index] = value.first.to_s
elsif opts[:sentence]
row[index] = value.map { |v| v.to_s }.to_sentence
else
row[index] = value.map { |v| v.to_s }.join('
')
end
end
when :effective_address
row[index] = value.map { |addr| addr.to_html }.join('
')
when :effective_roles
row[index] = value.join(', ')
when :datetime
row[index] = value.strftime(EffectiveDatatables.datetime_format) rescue BLANK
when :date
row[index] = value.strftime(EffectiveDatatables.date_format) rescue BLANK
when :price
# This is an integer value, "number of cents"
raise 'column type: price expects an Integer representing the number of cents' unless value.kind_of?(Integer)
row[index] = number_to_currency(value / 100.0)
when :currency
row[index] = number_to_currency(value || 0)
when :percentage
row[index] = number_to_percentage(value || 0)
when :integer
if EffectiveDatatables.integer_format.kind_of?(Symbol)
row[index] = view.instance_exec { public_send(EffectiveDatatables.integer_format, value) }
elsif EffectiveDatatables.integer_format.respond_to?(:call)
row[index] = view.instance_exec { EffectiveDatatables.integer_format.call(value) }
end
when :boolean
if EffectiveDatatables.boolean_format == :yes_no && value == true
row[index] = 'Yes'
elsif EffectiveDatatables.boolean_format == :yes_no && value == false
row[index] = 'No'
end
when :string
row[index] = mail_to(value) if name == 'email'
else
; # Nothing
end
end
end
collection
end
# This should return an Array of values the same length as table_data
def aggregate_data(table_data)
return false unless aggregates.present?
values = table_data.transpose
aggregates.map do |name, options|
(display_table_columns || table_columns).map.with_index do |(name, column), index|
if column[:visible] != true
''
elsif (options[:block] || options[:proc]).respond_to?(:call)
view.instance_exec(column, (values[index] || []), values, &(options[:block] || options[:proc]))
else
''
end
end
end
end
private
def controller_namespace
@controller_namespace ||= (
path = if attributes[:referer].present?
URI(attributes[:referer]).path
else
view.controller_path
end
path.split('/')[0...-1].map { |path| path.downcase.to_sym if path.present? }.compact
)
end
end # / Rendering
end
end