fluid_table 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +3 -0
- data/Rakefile +13 -0
- data/lib/fluid_table.rb +19 -0
- data/lib/fluid_table/class_methods.rb +22 -0
- data/lib/fluid_table/column.rb +200 -0
- data/lib/fluid_table/context.rb +28 -0
- data/lib/fluid_table/instance_methods.rb +86 -0
- data/lib/fluid_table/version.rb +3 -0
- data/test/support/user.rb +14 -0
- data/test/support/users.html.erb +1 -0
- data/test/support/users_table.rb +27 -0
- data/test/test_helper.rb +10 -0
- data/test/units/column_test.rb +92 -0
- data/test/units/configuration_test.rb +80 -0
- data/test/units/render_test.rb +29 -0
- data/test/units/table_test.rb +45 -0
- metadata +174 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            Copyright (c) 2010-2012 Decisiv, Inc.
         | 
| 2 | 
            +
            Copyright (c) 2009 Brennan Dunn
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 5 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 6 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 7 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 8 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 9 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 10 | 
            +
            the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 13 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 16 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 17 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 18 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 19 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 20 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 21 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require 'bundler'
         | 
| 2 | 
            +
            require 'rake/testtask'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Bundler::GemHelper.install_tasks
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            desc 'Test fluid_table'
         | 
| 7 | 
            +
            Rake::TestTask.new(:test) do |t|
         | 
| 8 | 
            +
              t.libs << 'lib' << 'test'
         | 
| 9 | 
            +
              t.pattern = 'test/**/*_test.rb'
         | 
| 10 | 
            +
              t.verbose = true
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            task :default => [:test]
         | 
    
        data/lib/fluid_table.rb
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            require 'rails'
         | 
| 2 | 
            +
            require 'action_controller'
         | 
| 3 | 
            +
            require 'active_support/core_ext/proc'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'fluid_table/class_methods'
         | 
| 6 | 
            +
            require 'fluid_table/instance_methods'
         | 
| 7 | 
            +
            require 'fluid_table/column'
         | 
| 8 | 
            +
            require 'fluid_table/context'
         | 
| 9 | 
            +
            require 'fluid_table/version'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            class FluidTable
         | 
| 12 | 
            +
              include ActionView::Helpers::TagHelper
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              class_attribute :row_options, :class_columns, :table_options
         | 
| 15 | 
            +
              attr_accessor :view, :records, :cloned_columns, :render_options, :cache_rendered_rows
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              extend ClassMethods
         | 
| 18 | 
            +
              include InstanceMethods
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            class FluidTable
         | 
| 2 | 
            +
              module ClassMethods
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                def define_column(*args, &proc)
         | 
| 5 | 
            +
                  options = [Hash,Proc].include?(args.last.class) ? args.pop : {}
         | 
| 6 | 
            +
                  identity, alt_name = *args
         | 
| 7 | 
            +
                  Column.new(self, identity, alt_name, options, &proc).tap do |column|
         | 
| 8 | 
            +
                    (self.class_columns ||= Array.new).push(column)
         | 
| 9 | 
            +
                    column.default_position = class_columns.index(column)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                def render(view,records = nil)
         | 
| 14 | 
            +
                  new(view).render(records)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
                
         | 
| 17 | 
            +
                def valid?
         | 
| 18 | 
            +
                  !class_columns.empty?
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,200 @@ | |
| 1 | 
            +
            class FluidTable
         | 
| 2 | 
            +
              class Column
         | 
| 3 | 
            +
                include Comparable
         | 
| 4 | 
            +
                include ActionView::Helpers::TagHelper
         | 
| 5 | 
            +
                
         | 
| 6 | 
            +
                attr_accessor :table, :identity, :alt_name, :options, :html_options, :proc, :forced_options # basic init
         | 
| 7 | 
            +
                attr_accessor :is_visible, :default_position, :configured_position  # overrides
         | 
| 8 | 
            +
                attr_accessor :table_instance # duped context 
         | 
| 9 | 
            +
                
         | 
| 10 | 
            +
                DefaultOptions = { :default => true, :cacheable => true }
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                def initialize(table, identity, alt_name = nil, options = {}, &proc)
         | 
| 13 | 
            +
                  self.table          = table
         | 
| 14 | 
            +
                  self.identity       = identity
         | 
| 15 | 
            +
                  self.alt_name       = alt_name
         | 
| 16 | 
            +
                  self.options        = options.reverse_merge(DefaultOptions)
         | 
| 17 | 
            +
                  self.html_options   = options.delete(:html) || {}
         | 
| 18 | 
            +
                  self.proc           = proc
         | 
| 19 | 
            +
                  self.forced_options = {}
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                def <=>(other_column)
         | 
| 23 | 
            +
                  if position == other_column.position
         | 
| 24 | 
            +
                    return -1 if positioned? && !other_column.positioned?
         | 
| 25 | 
            +
                    return 1 if !positioned? && other_column.positioned?
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                  position <=> other_column.position
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                def current
         | 
| 31 | 
            +
                  table_instance.view.current
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                def position
         | 
| 35 | 
            +
                  configured_position || default_position
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                def cacheable?
         | 
| 39 | 
            +
                  options[:cacheable]
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                
         | 
| 42 | 
            +
                def visible?
         | 
| 43 | 
            +
                  return @visible unless @visible.nil?
         | 
| 44 | 
            +
                  @visible = options[:visible] ? options[:visible].bind(table_instance.view).call : true
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                
         | 
| 47 | 
            +
                def config_filter_key
         | 
| 48 | 
            +
                  :"filter_for_#{sort.field}"
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                def filter
         | 
| 52 | 
            +
                  options[:filter]
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
                
         | 
| 55 | 
            +
                def filter_distinct?
         | 
| 56 | 
            +
                  filter == :distinct
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
                def filterable?
         | 
| 60 | 
            +
                  filter.present?
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
                def filter_data
         | 
| 64 | 
            +
                  return [] unless filterable?
         | 
| 65 | 
            +
                  if filter_distinct?
         | 
| 66 | 
            +
                    filter_data_for_distinct
         | 
| 67 | 
            +
                  else
         | 
| 68 | 
            +
                    filter.first.is_a?(Array) ? filter.map(&:first) : filter
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
                
         | 
| 72 | 
            +
                def filter_data_for_current
         | 
| 73 | 
            +
                  return [] unless filterable?
         | 
| 74 | 
            +
                  data = filter_data
         | 
| 75 | 
            +
                  values = current_filter_values
         | 
| 76 | 
            +
                  data.sort.inject(ActiveSupport::OrderedHash.new) do |hsh, key|
         | 
| 77 | 
            +
                    checked = values.include?(key)
         | 
| 78 | 
            +
                    value = if !filter_distinct? && filter.first.is_a?(Array)
         | 
| 79 | 
            +
                              filter.detect{ |arr| arr.first == key }.try(:last)
         | 
| 80 | 
            +
                            else
         | 
| 81 | 
            +
                              checked ? 1 : 0
         | 
| 82 | 
            +
                            end
         | 
| 83 | 
            +
                    hsh[key] = [checked ? 1 : 0, key].join('|')
         | 
| 84 | 
            +
                    hsh
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def filter_condition
         | 
| 89 | 
            +
                  filter_values = current_filter_values.dup
         | 
| 90 | 
            +
                  conditions = []
         | 
| 91 | 
            +
                  if filter_values.delete('none...')
         | 
| 92 | 
            +
                    conditions << "#{sort.table}.#{sort.field} IS NULL"
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                  unless filter_values.empty?
         | 
| 95 | 
            +
                    conditions << "#{sort.table}.#{sort.field} IN (?)"
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                  return nil if conditions.empty?
         | 
| 98 | 
            +
                  condition = "(#{conditions.join(' OR ')})"
         | 
| 99 | 
            +
                  unless filter_values.empty?
         | 
| 100 | 
            +
                    condition = [condition, filter_values]
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
                  condition
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                def positioned?
         | 
| 106 | 
            +
                  !!configured_position
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
                
         | 
| 109 | 
            +
                def name
         | 
| 110 | 
            +
                  alt_name || identity.to_s.humanize
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def sort_on
         | 
| 114 | 
            +
                  options[:sort_on]
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
                
         | 
| 117 | 
            +
                def sort
         | 
| 118 | 
            +
                  return nil unless sort_on
         | 
| 119 | 
            +
                  @sort ||= begin
         | 
| 120 | 
            +
                    stable, sfield = sort_on.split('.')
         | 
| 121 | 
            +
                    Struct.new(:table, :field).new(stable, sfield)
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                def html(scope)
         | 
| 126 | 
            +
                  attributes = html_options.is_a?(Proc) ? html_options.call(Context.new(table_instance, scope)) : html_options
         | 
| 127 | 
            +
                  merge_options = forced_options.is_a?(Proc) ? forced_options.call(Context.new(table_instance, scope)) : forced_options
         | 
| 128 | 
            +
                  content_tag(:td, interior_content(scope).to_s.html_safe, attributes.merge(merge_options))
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
                
         | 
| 131 | 
            +
                def display?
         | 
| 132 | 
            +
                  is_visible.nil? ? options[:default] : is_visible
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
                
         | 
| 135 | 
            +
                def reset!
         | 
| 136 | 
            +
                  self.is_visible = self.configured_position = nil
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
                
         | 
| 139 | 
            +
                
         | 
| 140 | 
            +
                private
         | 
| 141 | 
            +
                
         | 
| 142 | 
            +
                def current_user_filters
         | 
| 143 | 
            +
                  table_instance.current_user_filters
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
                
         | 
| 146 | 
            +
                def current_filter_values
         | 
| 147 | 
            +
                  return [] unless filterable?
         | 
| 148 | 
            +
                  value = current_user_filters[config_filter_key] || ''
         | 
| 149 | 
            +
                  value.split(',').map(&:strip)
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
                
         | 
| 152 | 
            +
                # FIXME this absolutely does not belong here
         | 
| 153 | 
            +
                def filter_data_for_distinct
         | 
| 154 | 
            +
                  base_finders = {:select => sort.field, :group => sort.field}
         | 
| 155 | 
            +
                  set = case table.klass_name
         | 
| 156 | 
            +
                        when 'FleetRecentEstimates', 'FleetRequestedEstimates'
         | 
| 157 | 
            +
                          case sort_on
         | 
| 158 | 
            +
                          when 'vehicles.fleet_info'
         | 
| 159 | 
            +
                            current.dealer.fleet_vehicles.all(base_finders)
         | 
| 160 | 
            +
                          when 'estimates.fleet_status'
         | 
| 161 | 
            +
                            current.dealer.fleet_estimates.all(base_finders)
         | 
| 162 | 
            +
                          when 'dealer_info.dealer_name'
         | 
| 163 | 
            +
                            Dealer.find_by_sql(%|
         | 
| 164 | 
            +
                              SELECT [dealer_name] 
         | 
| 165 | 
            +
                              FROM [dealer_info] 
         | 
| 166 | 
            +
                              WHERE [dealer_id] IN (SELECT [dealer_id] FROM [estimates] WHERE [vehicle_owner] = #{Dealer.connection.quote(current.dealer.id)})
         | 
| 167 | 
            +
                              ORDER BY [dealer_name] ASC|.squish)
         | 
| 168 | 
            +
                          end
         | 
| 169 | 
            +
                        when 'SvcRecentEstimates', 'SvcAssignedEstimates'
         | 
| 170 | 
            +
                          case sort_on
         | 
| 171 | 
            +
                          when 'estimates.assigned_to', 'estimates.customer_company_name'
         | 
| 172 | 
            +
                            table_instance.scoped_records.all(base_finders)
         | 
| 173 | 
            +
                          end
         | 
| 174 | 
            +
                        end || []
         | 
| 175 | 
            +
                  fields = set.map { |o| o.send(sort.field) } + current_filter_values
         | 
| 176 | 
            +
                  fields.uniq.reject { |o| o.blank? }.sort
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
                
         | 
| 179 | 
            +
                def interior_content(scope)
         | 
| 180 | 
            +
                  if proc && table_instance
         | 
| 181 | 
            +
                    call_by_arity(scope)
         | 
| 182 | 
            +
                  elsif scope.respond_to?(identity)
         | 
| 183 | 
            +
                    scope.send identity
         | 
| 184 | 
            +
                  else
         | 
| 185 | 
            +
                    ''
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
                end
         | 
| 188 | 
            +
                
         | 
| 189 | 
            +
                def call_by_arity(scope)
         | 
| 190 | 
            +
                  call_arguments = case proc.arity
         | 
| 191 | 
            +
                    when 1      then [scope]
         | 
| 192 | 
            +
                    when 2      then [scope, table_instance]
         | 
| 193 | 
            +
                    when 3      then [scope, table_instance, self]
         | 
| 194 | 
            +
                    else        []
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
                  proc.bind(table_instance.view).call(*call_arguments)
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
                
         | 
| 199 | 
            +
              end
         | 
| 200 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            class FluidTable
         | 
| 2 | 
            +
              class Context < Struct.new(:table,:record)
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                def view
         | 
| 5 | 
            +
                  table.view if table
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                def respond_to?(method)
         | 
| 9 | 
            +
                  view && view.respond_to?(method)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                private
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                def method_missing(method, *args)
         | 
| 16 | 
            +
                  if table.view
         | 
| 17 | 
            +
                    if block_given?
         | 
| 18 | 
            +
                      table.view.send(method, *args) { |*block_args| yield(*block_args) }
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      table.view.send(method, *args)
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  else
         | 
| 23 | 
            +
                    block_given? ? super { |*block_args| yield(*block_args) } : super
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
                
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            class FluidTable
         | 
| 2 | 
            +
              module InstanceMethods
         | 
| 3 | 
            +
                
         | 
| 4 | 
            +
                def initialize(view,render_options={})
         | 
| 5 | 
            +
                  self.view = view
         | 
| 6 | 
            +
                  load_cloned_columns
         | 
| 7 | 
            +
                  self.render_options = render_options
         | 
| 8 | 
            +
                  load_customizations
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
                
         | 
| 11 | 
            +
                def displayed_columns
         | 
| 12 | 
            +
                  cloned_columns.select(&:display?).sort
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                def hidden_columns
         | 
| 16 | 
            +
                  cloned_columns - displayed_columns
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                # Overwrite this method in your implementation to add table headers, etc.
         | 
| 20 | 
            +
                def render(records = find_records)
         | 
| 21 | 
            +
                  self.records = records
         | 
| 22 | 
            +
                  rendered_table = content_tag :table, content_tag(:tbody,render_table_body), table_options || {}
         | 
| 23 | 
            +
                  render_header + rendered_table + render_footer
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                # Stub methods
         | 
| 27 | 
            +
                def find_records ; [] ; end
         | 
| 28 | 
            +
                def customize_column(column) ; column ; end
         | 
| 29 | 
            +
                def render_header ; ActiveSupport::SafeBuffer.new ; end
         | 
| 30 | 
            +
                def render_footer ; ActiveSupport::SafeBuffer.new ; end
         | 
| 31 | 
            +
                def if_array_empty?
         | 
| 32 | 
            +
                  content_tag :td, %|There's nothing here|, :colspan => displayed_columns.size
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
                private
         | 
| 36 | 
            +
              
         | 
| 37 | 
            +
                def load_cloned_columns
         | 
| 38 | 
            +
                  self.cloned_columns = class_columns.map { |cc| cloned = cc.dup ; cloned.table_instance = self ; cloned }
         | 
| 39 | 
            +
                  cloned_columns.reject! { |c| !c.visible? }
         | 
| 40 | 
            +
                  cloned_columns.sort!
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                
         | 
| 43 | 
            +
                def load_customizations
         | 
| 44 | 
            +
                  cloned_columns.each { |c| customize_column(c) }
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                
         | 
| 47 | 
            +
                def render_table_body
         | 
| 48 | 
            +
                  return render_empty_table if records.blank?
         | 
| 49 | 
            +
                  records.map do |record|
         | 
| 50 | 
            +
                    content_tag(:tr,render_row(record),render_tr_options(record))
         | 
| 51 | 
            +
                  end.join.html_safe
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                
         | 
| 54 | 
            +
                def render_empty_table
         | 
| 55 | 
            +
                  content_tag(:tr,if_array_empty?)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                
         | 
| 58 | 
            +
                def render_row(record)
         | 
| 59 | 
            +
                  # The goal is to have one cache hit for the row, per record. The challenge is that different users can have some 
         | 
| 60 | 
            +
                  # columns on/off in each row. The solution is to use a hash for the entire row. If the users displayed_columns has 
         | 
| 61 | 
            +
                  # all the columns from the row cache hash (write_col) then it will not re-write the cache with more data.
         | 
| 62 | 
            +
                  if cache_rendered_rows
         | 
| 63 | 
            +
                    cache_key = ActiveSupport::Cache.expand_cache_key ['views',configurator_id,record]
         | 
| 64 | 
            +
                    row_cache = Rails.cache.fetch(cache_key) { HashWithIndifferentAccess.new }
         | 
| 65 | 
            +
                    write_col = displayed_columns.select(&:cacheable?).map(&:identity).any? { |id| !row_cache.keys.include?(id) }
         | 
| 66 | 
            +
                    row_cache = row_cache.dup if write_col
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                  # Rendering the row like normal FluidTable but building up the hash cache if needed.
         | 
| 69 | 
            +
                  rendered_row = displayed_columns.map do |c|
         | 
| 70 | 
            +
                    cache_rendered_rows && c.cacheable? ? (row_cache[c.identity] ||= c.html(record)) : c.html(record)
         | 
| 71 | 
            +
                  end.join
         | 
| 72 | 
            +
                  # Write the cache back if caching and any cols were not in the orig cache. Return rendered row.
         | 
| 73 | 
            +
                  Rails.cache.write(cache_key,row_cache) if cache_rendered_rows && write_col
         | 
| 74 | 
            +
                  return rendered_row.html_safe
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
                
         | 
| 77 | 
            +
                def render_tr_options(record)
         | 
| 78 | 
            +
                  case opt = row_options
         | 
| 79 | 
            +
                    when Proc   then opt.call(Context.new(self,record))
         | 
| 80 | 
            +
                    when Hash   then opt
         | 
| 81 | 
            +
                    else        {}
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
                
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            <%= UsersTable.render(self,User.all_users) %>
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            class UsersTable < FluidTable
         | 
| 2 | 
            +
              
         | 
| 3 | 
            +
              self.table_options = { :class => 'dataTable' }
         | 
| 4 | 
            +
              
         | 
| 5 | 
            +
              define_column :id, 'User #'
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              define_column :name do |user|
         | 
| 8 | 
            +
                user.name.upcase
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
              define_column :age, :html => Proc.new { |c| { :class => "table_id_#{c.table.object_id}" } }
         | 
| 12 | 
            +
              define_column :gender
         | 
| 13 | 
            +
              
         | 
| 14 | 
            +
              UsersTable.define_column :displayed_column
         | 
| 15 | 
            +
              UsersTable.define_column :hidden_column, :default => false
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
              # overwrites
         | 
| 18 | 
            +
              
         | 
| 19 | 
            +
              def render_header
         | 
| 20 | 
            +
                content_tag(:div, 'This is the header')
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
              
         | 
| 23 | 
            +
              def render_footer
         | 
| 24 | 
            +
                content_tag(:div, 'This is the footer')
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
              
         | 
| 27 | 
            +
            end
         | 
    
        data/test/test_helper.rb
    ADDED
    
    
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TestTable < FluidTable
         | 
| 4 | 
            +
              define_column :id, 'User #'
         | 
| 5 | 
            +
              define_column :name, 'User Name'
         | 
| 6 | 
            +
              define_column(:google){ link_to_google }
         | 
| 7 | 
            +
            end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            describe FluidTable::Column do
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              def self.test_order
         | 
| 12 | 
            +
                :alpha
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              describe 'Constructing column objects' do
         | 
| 16 | 
            +
                
         | 
| 17 | 
            +
                before do
         | 
| 18 | 
            +
                  @test_table = TestTable.new(nil,nil)
         | 
| 19 | 
            +
                  @id_column = @test_table.class_columns.detect { |c| c.identity == :id }
         | 
| 20 | 
            +
                  @name_column = @test_table.class_columns.detect { |c| c.identity == :name }
         | 
| 21 | 
            +
                  @google_column = @test_table.class_columns.detect { |c| c.identity == :google }
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                it "should respond to it's identity" do
         | 
| 25 | 
            +
                  assert_equal :id, @id_column.identity
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                it 'should attempt to create a humanized string from the identity if an alt_name is not present' do
         | 
| 29 | 
            +
                  assert_equal 'User #', @id_column.name
         | 
| 30 | 
            +
                  assert_equal 'User Name', @name_column.name
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                it "should be able to parse its sort on table and field" do
         | 
| 34 | 
            +
                  @name_column.options[:sort_on] = 'foo.bar'
         | 
| 35 | 
            +
                  assert @name_column.sort
         | 
| 36 | 
            +
                  assert_equal 'foo', @name_column.sort.table
         | 
| 37 | 
            +
                  assert_equal 'bar', @name_column.sort.field
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
                
         | 
| 40 | 
            +
                describe "Outputting a column's HTML" do
         | 
| 41 | 
            +
                  
         | 
| 42 | 
            +
                  it 'should wrap output within a table cell' do
         | 
| 43 | 
            +
                    assert_equal_html '<td></td>', @id_column.html(mock)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                  
         | 
| 46 | 
            +
                  # FIXME - I suck and my tests are order-dependent?
         | 
| 47 | 
            +
                  it 'should attempt to get cell content from scope object when proc is not available' do
         | 
| 48 | 
            +
                    assert_equal_html '<td>John Doe</td>', @name_column.html(mock({ :name => 'John Doe' }))
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                  
         | 
| 51 | 
            +
                  it "should call a column's proc (if exists) against the scope of the current view" do
         | 
| 52 | 
            +
                    view = mock.tap { |m| m.stubs :link_to_google => 'http://google.com' } 
         | 
| 53 | 
            +
                    table = TestTable.new(view)
         | 
| 54 | 
            +
                    google_column = table.cloned_columns.detect { |c| c.identity == :google }
         | 
| 55 | 
            +
                    assert_equal_html '<td>http://google.com</td>', google_column.html(User.all_users.first)
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                  
         | 
| 58 | 
            +
                  it 'should be able to set custom HTML attributes when supplied a HASH' do
         | 
| 59 | 
            +
                    @name_column.html_options = { :class => 'foo' }
         | 
| 60 | 
            +
                    assert_match /class="foo"/, @name_column.html(mock)
         | 
| 61 | 
            +
                    @name_column.html_options = { :id => 'bar' }
         | 
| 62 | 
            +
                    assert_match /id="bar"/, @name_column.html(mock)
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                
         | 
| 65 | 
            +
                  it 'should be able to set custom HTML attributes when supplied a PROC' do
         | 
| 66 | 
            +
                    @name_column.html_options = Proc.new { |context| { :id => "name_#{context.record.id}" } }
         | 
| 67 | 
            +
                    assert_match /id="name_5"/, @name_column.html(mock(:id => '5'))
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
                
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
              
         | 
| 74 | 
            +
              describe 'Columns within a FluidTable' do
         | 
| 75 | 
            +
                
         | 
| 76 | 
            +
                it 'should not be valid? unless at least one column is defined' do
         | 
| 77 | 
            +
                  assert ! TestTable.class_columns.empty?
         | 
| 78 | 
            +
                  assert TestTable.valid?
         | 
| 79 | 
            +
                  TestTable.stubs(:class_columns).returns([])
         | 
| 80 | 
            +
                  assert ! TestTable.valid?
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
                
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
             | 
| 86 | 
            +
              private
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              def assert_equal_html(expected, actual)
         | 
| 89 | 
            +
                assert_equal expected.strip.gsub(/\n\s*/, ''), actual.strip.gsub(/\n\s*/, '')
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            end
         | 
| @@ -0,0 +1,80 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "Fluid table configuration" do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:table) { UsersTable.new(nil, nil) }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              it 'should have an array of cloned class_columns for each table instance' do
         | 
| 8 | 
            +
                table.cloned_columns.map(&:object_id).wont_equal UsersTable.class_columns.map(&:object_id)
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              it 'should not affect a class column when modifying an instance column' do
         | 
| 12 | 
            +
                instance_column = get_column(:age)
         | 
| 13 | 
            +
                transform_column(instance_column, false)
         | 
| 14 | 
            +
                assert_equal false, instance_column.display?
         | 
| 15 | 
            +
                assert_equal true, get_table_column(:age).display?
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              describe "Determining a column's default visibility" do
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                let(:displayed_column) { get_column(:displayed_column) }
         | 
| 21 | 
            +
                let(:hidden_column)    { get_column(:hidden_column) }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it 'should default to TRUE for a column display' do
         | 
| 24 | 
            +
                  assert displayed_column.display?
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                it "should allow for a column's definition to default display to FALSE" do
         | 
| 28 | 
            +
                  assert !hidden_column.display?
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                it 'should include correct columns when returning from #displayed_columns or #hidden_columns' do
         | 
| 32 | 
            +
                  assert table.displayed_columns.include?(displayed_column)
         | 
| 33 | 
            +
                  assert !table.displayed_columns.include?(hidden_column)
         | 
| 34 | 
            +
                  assert table.hidden_columns.include?(hidden_column)
         | 
| 35 | 
            +
                  assert !table.hidden_columns.include?(displayed_column)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              describe 'When overriding position or visibility' do
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                it 'should call #customize_column with each column object on table instantiation' do
         | 
| 43 | 
            +
                  table.class_columns.each do |column|
         | 
| 44 | 
            +
                    table.expects(:customize_column).with(column)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                  table.send :load_customizations
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                it 'should override default column visibility settings when instructed' do
         | 
| 50 | 
            +
                  column = get_column(:age)
         | 
| 51 | 
            +
                  assert table.displayed_columns.include?(column)
         | 
| 52 | 
            +
                  transform_column(get_column(:age), false)
         | 
| 53 | 
            +
                  table.send :load_customizations
         | 
| 54 | 
            +
                  assert ! table.displayed_columns.include?(column)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                it "should be able to set a column's position which trumps the default" do
         | 
| 58 | 
            +
                  age_column = get_column(:age)
         | 
| 59 | 
            +
                  transform_column(age_column, true, 0)
         | 
| 60 | 
            +
                  assert_equal age_column.identity, table.displayed_columns.first.identity
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              private
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              def get_column(identity)
         | 
| 68 | 
            +
                table.cloned_columns.detect { |c| c.identity == identity }
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              def get_table_column(identity)
         | 
| 72 | 
            +
                table.class_columns.detect { |c| c.identity == identity }
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              def transform_column(column, is_visible = true, position = nil)
         | 
| 76 | 
            +
                column.is_visible = is_visible
         | 
| 77 | 
            +
                column.configured_position = position
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe FluidTable do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              describe "#render" do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                let(:text) { UsersTable.render(stub(:view), User.all_users) }
         | 
| 8 | 
            +
                let(:html) { Nokogiri::HTML::DocumentFragment.parse(text) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                it "renders the header text in a header div" do
         | 
| 11 | 
            +
                  html.xpath(".//div").first.text.must_equal 'This is the header'
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it "renders the footer text in a footer div" do
         | 
| 15 | 
            +
                  html.xpath(".//div").last.text.must_equal 'This is the footer'
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it "renders a row for each user" do
         | 
| 19 | 
            +
                  html.xpath(".//tbody/tr").length.must_equal User.all_users.length
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                it "render 'Nothing here' if there are no users" do
         | 
| 23 | 
            +
                  User.stubs(:all_users).returns([])
         | 
| 24 | 
            +
                  html.xpath(".//td").map(&:text).must_equal ["There's nothing here"]
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'test_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe "FluidTable render" do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              describe 'A FluidTable object' do
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                it 'should have the option of setting attributes to the generated table (as opposed to overwriting #render)' do
         | 
| 8 | 
            +
                  UsersTable.table_options = { :class => 'dataTable' }
         | 
| 9 | 
            +
                  assert_match %r{table class="dataTable"}, render_users
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                describe 'Ordering of class_columns' do
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it 'should be defaultly positioned in the order of definition' do
         | 
| 15 | 
            +
                    assert_equal 0, UsersTable.class_columns.first.default_position
         | 
| 16 | 
            +
                    assert_equal UsersTable.class_columns.size-1, UsersTable.class_columns.last.default_position
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  it 'should have the #sort order be identical to the default positioning when there are no custom positions' do
         | 
| 20 | 
            +
                    assert_equal UsersTable.class_columns, UsersTable.class_columns.sort
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                describe 'Supplying row (<tr>) options' do
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  it 'should output standard hash options' do
         | 
| 28 | 
            +
                    UsersTable.row_options = { :class => 'foo' }
         | 
| 29 | 
            +
                    assert_match %r{tr class="foo"}, render_users
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  it 'should yield a context object when given a Proc' do
         | 
| 33 | 
            +
                    UsersTable.row_options = Proc.new { |context| { :id => "user_#{context.record.id}" } }
         | 
| 34 | 
            +
                    assert_match %r{tr id="user_#{User::ALL.size}"}, render_users
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def render_users
         | 
| 42 | 
            +
                UsersTable.render(mock, User::ALL)
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,174 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: fluid_table
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              hash: 15
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
              segments: 
         | 
| 7 | 
            +
              - 3
         | 
| 8 | 
            +
              - 2
         | 
| 9 | 
            +
              - 0
         | 
| 10 | 
            +
              version: 3.2.0
         | 
| 11 | 
            +
            platform: ruby
         | 
| 12 | 
            +
            authors: 
         | 
| 13 | 
            +
            - Brennan Dunn
         | 
| 14 | 
            +
            - Ken Collins
         | 
| 15 | 
            +
            - Donald Ball
         | 
| 16 | 
            +
            autorequire: 
         | 
| 17 | 
            +
            bindir: bin
         | 
| 18 | 
            +
            cert_chain: []
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            date: 2012-03-13 00:00:00 Z
         | 
| 21 | 
            +
            dependencies: 
         | 
| 22 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 23 | 
            +
              name: rails
         | 
| 24 | 
            +
              prerelease: false
         | 
| 25 | 
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         | 
| 26 | 
            +
                none: false
         | 
| 27 | 
            +
                requirements: 
         | 
| 28 | 
            +
                - - ~>
         | 
| 29 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 30 | 
            +
                    hash: 15
         | 
| 31 | 
            +
                    segments: 
         | 
| 32 | 
            +
                    - 3
         | 
| 33 | 
            +
                    - 2
         | 
| 34 | 
            +
                    - 0
         | 
| 35 | 
            +
                    version: 3.2.0
         | 
| 36 | 
            +
              type: :runtime
         | 
| 37 | 
            +
              version_requirements: *id001
         | 
| 38 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 39 | 
            +
              name: rake
         | 
| 40 | 
            +
              prerelease: false
         | 
| 41 | 
            +
              requirement: &id002 !ruby/object:Gem::Requirement 
         | 
| 42 | 
            +
                none: false
         | 
| 43 | 
            +
                requirements: 
         | 
| 44 | 
            +
                - - ~>
         | 
| 45 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 46 | 
            +
                    hash: 63
         | 
| 47 | 
            +
                    segments: 
         | 
| 48 | 
            +
                    - 0
         | 
| 49 | 
            +
                    - 9
         | 
| 50 | 
            +
                    - 2
         | 
| 51 | 
            +
                    version: 0.9.2
         | 
| 52 | 
            +
              type: :development
         | 
| 53 | 
            +
              version_requirements: *id002
         | 
| 54 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 55 | 
            +
              name: minitest
         | 
| 56 | 
            +
              prerelease: false
         | 
| 57 | 
            +
              requirement: &id003 !ruby/object:Gem::Requirement 
         | 
| 58 | 
            +
                none: false
         | 
| 59 | 
            +
                requirements: 
         | 
| 60 | 
            +
                - - ~>
         | 
| 61 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 62 | 
            +
                    hash: 45
         | 
| 63 | 
            +
                    segments: 
         | 
| 64 | 
            +
                    - 2
         | 
| 65 | 
            +
                    - 8
         | 
| 66 | 
            +
                    - 1
         | 
| 67 | 
            +
                    version: 2.8.1
         | 
| 68 | 
            +
              type: :development
         | 
| 69 | 
            +
              version_requirements: *id003
         | 
| 70 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 71 | 
            +
              name: mocha
         | 
| 72 | 
            +
              prerelease: false
         | 
| 73 | 
            +
              requirement: &id004 !ruby/object:Gem::Requirement 
         | 
| 74 | 
            +
                none: false
         | 
| 75 | 
            +
                requirements: 
         | 
| 76 | 
            +
                - - ~>
         | 
| 77 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 78 | 
            +
                    hash: 53
         | 
| 79 | 
            +
                    segments: 
         | 
| 80 | 
            +
                    - 0
         | 
| 81 | 
            +
                    - 10
         | 
| 82 | 
            +
                    - 1
         | 
| 83 | 
            +
                    version: 0.10.1
         | 
| 84 | 
            +
              type: :development
         | 
| 85 | 
            +
              version_requirements: *id004
         | 
| 86 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 87 | 
            +
              name: nokogiri
         | 
| 88 | 
            +
              prerelease: false
         | 
| 89 | 
            +
              requirement: &id005 !ruby/object:Gem::Requirement 
         | 
| 90 | 
            +
                none: false
         | 
| 91 | 
            +
                requirements: 
         | 
| 92 | 
            +
                - - ~>
         | 
| 93 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 94 | 
            +
                    hash: 3
         | 
| 95 | 
            +
                    segments: 
         | 
| 96 | 
            +
                    - 1
         | 
| 97 | 
            +
                    - 5
         | 
| 98 | 
            +
                    - 0
         | 
| 99 | 
            +
                    version: 1.5.0
         | 
| 100 | 
            +
              type: :development
         | 
| 101 | 
            +
              version_requirements: *id005
         | 
| 102 | 
            +
            description: Paginated data tables for rails
         | 
| 103 | 
            +
            email: 
         | 
| 104 | 
            +
            - me@brennandunn.com
         | 
| 105 | 
            +
            - ken@metaskills.net
         | 
| 106 | 
            +
            - donald.ball@gmail.com
         | 
| 107 | 
            +
            executables: []
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            extensions: []
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            extra_rdoc_files: []
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            files: 
         | 
| 114 | 
            +
            - .gitignore
         | 
| 115 | 
            +
            - Gemfile
         | 
| 116 | 
            +
            - LICENSE
         | 
| 117 | 
            +
            - README.md
         | 
| 118 | 
            +
            - Rakefile
         | 
| 119 | 
            +
            - lib/fluid_table.rb
         | 
| 120 | 
            +
            - lib/fluid_table/class_methods.rb
         | 
| 121 | 
            +
            - lib/fluid_table/column.rb
         | 
| 122 | 
            +
            - lib/fluid_table/context.rb
         | 
| 123 | 
            +
            - lib/fluid_table/instance_methods.rb
         | 
| 124 | 
            +
            - lib/fluid_table/version.rb
         | 
| 125 | 
            +
            - test/support/user.rb
         | 
| 126 | 
            +
            - test/support/users.html.erb
         | 
| 127 | 
            +
            - test/support/users_table.rb
         | 
| 128 | 
            +
            - test/test_helper.rb
         | 
| 129 | 
            +
            - test/units/column_test.rb
         | 
| 130 | 
            +
            - test/units/configuration_test.rb
         | 
| 131 | 
            +
            - test/units/render_test.rb
         | 
| 132 | 
            +
            - test/units/table_test.rb
         | 
| 133 | 
            +
            homepage: http://github.com/Decisiv/fluid_table/
         | 
| 134 | 
            +
            licenses: []
         | 
| 135 | 
            +
             | 
| 136 | 
            +
            post_install_message: 
         | 
| 137 | 
            +
            rdoc_options: 
         | 
| 138 | 
            +
            - --charset=UTF-8
         | 
| 139 | 
            +
            require_paths: 
         | 
| 140 | 
            +
            - lib
         | 
| 141 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 142 | 
            +
              none: false
         | 
| 143 | 
            +
              requirements: 
         | 
| 144 | 
            +
              - - ">="
         | 
| 145 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 146 | 
            +
                  hash: 3
         | 
| 147 | 
            +
                  segments: 
         | 
| 148 | 
            +
                  - 0
         | 
| 149 | 
            +
                  version: "0"
         | 
| 150 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 151 | 
            +
              none: false
         | 
| 152 | 
            +
              requirements: 
         | 
| 153 | 
            +
              - - ">="
         | 
| 154 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 155 | 
            +
                  hash: 3
         | 
| 156 | 
            +
                  segments: 
         | 
| 157 | 
            +
                  - 0
         | 
| 158 | 
            +
                  version: "0"
         | 
| 159 | 
            +
            requirements: []
         | 
| 160 | 
            +
             | 
| 161 | 
            +
            rubyforge_project: 
         | 
| 162 | 
            +
            rubygems_version: 1.8.17
         | 
| 163 | 
            +
            signing_key: 
         | 
| 164 | 
            +
            specification_version: 3
         | 
| 165 | 
            +
            summary: Paginated data tables for rails
         | 
| 166 | 
            +
            test_files: 
         | 
| 167 | 
            +
            - test/support/user.rb
         | 
| 168 | 
            +
            - test/support/users.html.erb
         | 
| 169 | 
            +
            - test/support/users_table.rb
         | 
| 170 | 
            +
            - test/test_helper.rb
         | 
| 171 | 
            +
            - test/units/column_test.rb
         | 
| 172 | 
            +
            - test/units/configuration_test.rb
         | 
| 173 | 
            +
            - test/units/render_test.rb
         | 
| 174 | 
            +
            - test/units/table_test.rb
         |