TrestleAdmin/trestle

View on GitHub
lib/trestle/table/column.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module Trestle
  class Table
    class Column
      attr_reader :field, :options, :block

      def initialize(field, options={}, &block)
        @field, @options = field, options
        @block = block if block_given?
      end

      def renderer(table:, template:)
        Renderer.new(self, table: table, template: template)
      end

      def sortable?
        options[:sort] != false && (!@block || options.has_key?(:sort))
      end

      def sort_field
        if options[:sort].is_a?(Hash)
          options[:sort][:field] || field
        else
          options[:sort] || field
        end
      end

      def sort_options
        options[:sort].is_a?(Hash) ? options[:sort] : {}
      end

      class Renderer
        delegate :options, to: :@column

        def initialize(column, table:, template:)
          @column, @table, @template = column, table, template
        end

        def render(instance)
          @template.content_tag(:td, content(instance), class: classes, data: data)
        end

        def render?
          if options.key?(:if)
            if options[:if].respond_to?(:call)
              @template.instance_exec(&options[:if])
            else
              options[:if]
            end
          elsif options.key?(:unless)
            if options[:unless].respond_to?(:call)
              !@template.instance_exec(&options[:unless])
            else
              !options[:unless]
            end
          else
            true
          end
        end

        def header
          return if options.key?(:header) && options[:header].in?([nil, false])

          if @table.sortable? && @column.sortable?
            @template.sort_link(header_text, @column.sort_field, @column.sort_options)
          else
            header_text
          end
        end

        def content(instance)
          value = column_value(instance)
          content = @template.format_value(value, options)

          if value.respond_to?(:id) && options[:link] != false
            # Column value was a model instance (e.g. from an association).
            # Automatically link to instance's admin if available
            content = @template.admin_link_to(content, value)
          elsif options[:link]
            # Explicitly link to the specified admin, or the table's admin
            content = @template.admin_link_to(content, instance, admin: options[:admin] || @table.admin)
          end

          content
        end

        def classes
          [options[:class], ("text-#{options[:align]}" if options[:align])].compact
        end

        def data
          options[:data]
        end

      private
        def header_text
          if header = options[:header]
            if header.respond_to?(:call)
              @template.instance_exec(&header)
            else
              header
            end
          elsif @table.admin
            @table.admin.t("table.headers.#{@column.field}", default: @table.admin.human_attribute_name(@column.field))
          else
            I18n.t("admin.table.headers.#{@column.field}", default: @column.field.to_s.humanize.titleize)
          end
        end

        def column_value(instance)
          if @column.block
            if block_is_legacy_haml?
              # In order for table column blocks to work properly within Haml templates,
              # the _hamlout local variable needs to be defined in the scope of the block,
              # so that the Haml version of the capture method is used. Because we
              # evaluate the block using instance_exec, we need to set this up manually.
              -> {
                _hamlout = eval('_hamlout', @column.block.binding)
                value = nil
                buffer = @template.capture { value = @template.instance_exec(instance, &@column.block) }
                value.is_a?(String) ? buffer : value
              }.call
            else
              # Capture both the immediate value and captured output of the block.
              # If the result of the block is a string, then use the contents of the buffer.
              # Otherwise return the result of the block as a raw value (for auto-formatting).
              value = nil
              buffer = @template.capture { value = @template.instance_exec(instance, &@column.block) }
              value.is_a?(String) ? buffer : value
            end
          else
            instance.send(@column.field)
          end
        end

        def block_is_legacy_haml?
          defined?(Haml) && Haml::Helpers.respond_to?(:block_is_haml?) && Haml::Helpers.block_is_haml?(@column.block)
        end
      end
    end
  end
end