tablesmith 0.4.1 → 0.5.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.
- checksums.yaml +5 -5
- data/.gitignore +2 -1
- data/.rubocop.yml +112 -2
- data/.ruby-version +1 -1
- data/.travis.yml +21 -0
- data/Appraisals +19 -0
- data/Gemfile +2 -0
- data/README.md +112 -11
- data/Rakefile +4 -2
- data/gemfiles/activerecord_4.gemfile +8 -0
- data/gemfiles/activerecord_5.1.gemfile +8 -0
- data/gemfiles/activerecord_5.2.gemfile +8 -0
- data/gemfiles/activerecord_6.0.gemfile +8 -0
- data/lib/tablesmith.rb +2 -0
- data/lib/tablesmith/active_record_source.rb +4 -2
- data/lib/tablesmith/array_rows_source.rb +3 -1
- data/lib/tablesmith/hash_rows_base.rb +4 -1
- data/lib/tablesmith/hash_rows_source.rb +8 -8
- data/lib/tablesmith/html_formatter.rb +1 -0
- data/lib/tablesmith/table.rb +34 -32
- data/lib/tablesmith/version.rb +3 -1
- data/spec/active_record_table_spec.rb +51 -49
- data/spec/array_table_spec.rb +61 -2
- data/spec/fixtures.rb +2 -0
- data/spec/hash_rows_table_spec.rb +15 -12
- data/spec/hash_table_spec.rb +23 -1
- data/spec/spec_helper.rb +6 -2
- data/spec/table_spec.rb +5 -3
- data/tablesmith.gemspec +15 -10
- metadata +77 -15
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Tablesmith::HashRowsSource
         | 
| 2 4 | 
             
              include Tablesmith::HashRowsBase
         | 
| 3 5 |  | 
| @@ -11,7 +13,7 @@ module Tablesmith::HashRowsSource | |
| 11 13 | 
             
              end
         | 
| 12 14 |  | 
| 13 15 | 
             
              def flatten_hash_to_row(deep_hash, columns)
         | 
| 14 | 
            -
                row =  | 
| 16 | 
            +
                row = {}
         | 
| 15 17 | 
             
                columns.each do |col_or_hash|
         | 
| 16 18 | 
             
                  value_from_hash(row, deep_hash, col_or_hash)
         | 
| 17 19 | 
             
                end
         | 
| @@ -21,9 +23,9 @@ module Tablesmith::HashRowsSource | |
| 21 23 | 
             
              # TODO: no support for deep
         | 
| 22 24 | 
             
              def build_columns
         | 
| 23 25 | 
             
                @columns ||= []
         | 
| 24 | 
            -
                 | 
| 26 | 
            +
                map do |hash_row|
         | 
| 25 27 | 
             
                  @columns << hash_row.keys.map { |k| Tablesmith::Column.new(name: k) }
         | 
| 26 | 
            -
                end | 
| 28 | 
            +
                end
         | 
| 27 29 | 
             
                @columns.flatten!
         | 
| 28 30 | 
             
              end
         | 
| 29 31 |  | 
| @@ -37,11 +39,9 @@ module Tablesmith::HashRowsSource | |
| 37 39 | 
             
                      value_from_hash(row, deep_hash[sub_hash_key], inner_col_or_hash)
         | 
| 38 40 | 
             
                    end
         | 
| 39 41 | 
             
                  end
         | 
| 40 | 
            -
                else
         | 
| 41 | 
            -
                  nil
         | 
| 42 42 | 
             
                end
         | 
| 43 | 
            -
              rescue => e
         | 
| 44 | 
            -
                 | 
| 43 | 
            +
              rescue StandardError => e
         | 
| 44 | 
            +
                warn "#{e.message}: #{col_or_hash}" if @debug
         | 
| 45 45 | 
             
              end
         | 
| 46 46 |  | 
| 47 47 | 
             
              def hash_rows_to_text_table(hash_rows)
         | 
| @@ -60,6 +60,6 @@ module Tablesmith::HashRowsSource | |
| 60 60 | 
             
                end
         | 
| 61 61 |  | 
| 62 62 | 
             
                # Array addition from text-table
         | 
| 63 | 
            -
                table.to_table(: | 
| 63 | 
            +
                table.to_table(first_row_is_head: true)
         | 
| 64 64 | 
             
              end
         | 
| 65 65 | 
             
            end
         | 
    
        data/lib/tablesmith/table.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'text-table'
         | 
| 2 4 | 
             
            require 'csv'
         | 
| 3 5 |  | 
| @@ -5,8 +7,8 @@ module Tablesmith | |
| 5 7 | 
             
              class Table < Array
         | 
| 6 8 | 
             
                def method_missing(meth_id, *args)
         | 
| 7 9 | 
             
                  count = 1
         | 
| 8 | 
            -
                   | 
| 9 | 
            -
                    $stderr.print '.' if count.divmod(100)[1] | 
| 10 | 
            +
                  map do |t|
         | 
| 11 | 
            +
                    $stderr.print '.' if (count.divmod(100)[1]).zero?
         | 
| 10 12 | 
             
                    count += 1
         | 
| 11 13 | 
             
                    t.send(meth_id, *args)
         | 
| 12 14 | 
             
                  end
         | 
| @@ -16,6 +18,10 @@ module Tablesmith | |
| 16 18 | 
             
                  super
         | 
| 17 19 | 
             
                end
         | 
| 18 20 |  | 
| 21 | 
            +
                def to_s
         | 
| 22 | 
            +
                  text_table.to_s
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 19 25 | 
             
                # irb
         | 
| 20 26 | 
             
                def inspect
         | 
| 21 27 | 
             
                  pretty_inspect
         | 
| @@ -32,9 +38,9 @@ module Tablesmith | |
| 32 38 | 
             
                end
         | 
| 33 39 |  | 
| 34 40 | 
             
                def text_table
         | 
| 35 | 
            -
                  return ['(empty)'].to_text_table if  | 
| 41 | 
            +
                  return ['(empty)'].to_text_table if empty?
         | 
| 36 42 |  | 
| 37 | 
            -
                  rows =  | 
| 43 | 
            +
                  rows = map { |item| convert_item_to_hash_row(item) }.compact
         | 
| 38 44 |  | 
| 39 45 | 
             
                  normalize_keys(rows)
         | 
| 40 46 |  | 
| @@ -48,12 +54,13 @@ module Tablesmith | |
| 48 54 | 
             
                  CSV.generate do |csv|
         | 
| 49 55 | 
             
                    text_table.rows.each do |row|
         | 
| 50 56 | 
             
                      next if row == :separator
         | 
| 57 | 
            +
             | 
| 51 58 | 
             
                      csv << row.map do |cell|
         | 
| 52 59 | 
             
                        case cell
         | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 60 | 
            +
                        when Hash
         | 
| 61 | 
            +
                          cell[:value]
         | 
| 62 | 
            +
                        else
         | 
| 63 | 
            +
                          cell
         | 
| 57 64 | 
             
                        end
         | 
| 58 65 | 
             
                      end
         | 
| 59 66 | 
             
                    end
         | 
| @@ -70,8 +77,7 @@ module Tablesmith | |
| 70 77 | 
             
                end
         | 
| 71 78 |  | 
| 72 79 | 
             
                # override in subclass or mixin
         | 
| 73 | 
            -
                def sort_columns(rows)
         | 
| 74 | 
            -
                end
         | 
| 80 | 
            +
                def sort_columns(rows); end
         | 
| 75 81 |  | 
| 76 82 | 
             
                # override in subclass or mixin
         | 
| 77 83 | 
             
                def convert_item_to_hash_row(item)
         | 
| @@ -79,22 +85,24 @@ module Tablesmith | |
| 79 85 | 
             
                end
         | 
| 80 86 |  | 
| 81 87 | 
             
                # override in subclass or mixin
         | 
| 82 | 
            -
                def normalize_keys(rows)
         | 
| 83 | 
            -
                end
         | 
| 88 | 
            +
                def normalize_keys(rows); end
         | 
| 84 89 |  | 
| 85 90 | 
             
                # override in subclass or mixin
         | 
| 86 91 | 
             
                def column_order
         | 
| 87 92 | 
             
                  []
         | 
| 88 93 | 
             
                end
         | 
| 89 94 |  | 
| 90 | 
            -
                 | 
| 91 | 
            -
                  @columns
         | 
| 92 | 
            -
                end
         | 
| 95 | 
            +
                attr_reader :columns
         | 
| 93 96 |  | 
| 94 97 | 
             
                def create_headers(rows)
         | 
| 95 | 
            -
                   | 
| 96 | 
            -
                   | 
| 97 | 
            -
             | 
| 98 | 
            +
                  first_element = rows.first
         | 
| 99 | 
            +
                  if first_element.is_a?(Array)
         | 
| 100 | 
            +
                    top_row = first_element
         | 
| 101 | 
            +
                    column_names = top_row.first.is_a?(Array) ? top_row.map(&:first) : top_row
         | 
| 102 | 
            +
                    grouped_headers(column_names) + [apply_column_aliases(column_names), :separator]
         | 
| 103 | 
            +
                  else
         | 
| 104 | 
            +
                    []
         | 
| 105 | 
            +
                  end
         | 
| 98 106 | 
             
                end
         | 
| 99 107 |  | 
| 100 108 | 
             
                def grouped_headers(column_names)
         | 
| @@ -111,7 +119,7 @@ module Tablesmith | |
| 111 119 | 
             
                    row = []
         | 
| 112 120 | 
             
                    # this relies on Ruby versions where hash retains add order
         | 
| 113 121 | 
             
                    groups.each_pair do |name, span|
         | 
| 114 | 
            -
                      row << {value: name, align: :center, colspan: span}
         | 
| 122 | 
            +
                      row << { value: name, align: :center, colspan: span }
         | 
| 115 123 | 
             
                    end
         | 
| 116 124 | 
             
                    [row, :separator]
         | 
| 117 125 | 
             
                  end
         | 
| @@ -121,7 +129,7 @@ module Tablesmith | |
| 121 129 | 
             
                  column_names.map do |name|
         | 
| 122 130 | 
             
                    instance = columns.detect { |ca| ca.name.to_s == name.to_s }
         | 
| 123 131 | 
             
                    value = instance ? instance.display_name : name
         | 
| 124 | 
            -
                    {: | 
| 132 | 
            +
                    { value: value, align: :center }
         | 
| 125 133 | 
             
                  end
         | 
| 126 134 | 
             
                end
         | 
| 127 135 | 
             
              end
         | 
| @@ -129,14 +137,14 @@ module Tablesmith | |
| 129 137 | 
             
              class Column
         | 
| 130 138 | 
             
                attr_accessor :source, :name, :alias
         | 
| 131 139 |  | 
| 132 | 
            -
                def initialize(attributes={})
         | 
| 140 | 
            +
                def initialize(attributes = {})
         | 
| 133 141 | 
             
                  @source = attributes.delete(:source)
         | 
| 134 142 | 
             
                  @name = attributes.delete(:name)
         | 
| 135 143 | 
             
                  @alias = attributes.delete(:alias)
         | 
| 136 144 | 
             
                end
         | 
| 137 145 |  | 
| 138 146 | 
             
                def display_name
         | 
| 139 | 
            -
                   | 
| 147 | 
            +
                  (@alias || @name).to_s
         | 
| 140 148 | 
             
                end
         | 
| 141 149 |  | 
| 142 150 | 
             
                def full_unaliased_name
         | 
| @@ -157,18 +165,12 @@ class Array | |
| 157 165 | 
             
                # so mixed content could be supported. Maybe every cell could be
         | 
| 158 166 | 
             
                # rendered appropriately, with nested tables.
         | 
| 159 167 | 
             
                if defined?(ActiveRecord) && defined?(ActiveRecord::Base)
         | 
| 160 | 
            -
                   | 
| 161 | 
            -
                    b.extend Tablesmith::ActiveRecordSource
         | 
| 162 | 
            -
                  end
         | 
| 168 | 
            +
                  b.extend Tablesmith::ActiveRecordSource if b.first&.is_a?(ActiveRecord::Base)
         | 
| 163 169 | 
             
                end
         | 
| 164 170 |  | 
| 165 | 
            -
                 | 
| 166 | 
            -
                  b.extend Tablesmith::HashRowsSource
         | 
| 167 | 
            -
                end
         | 
| 171 | 
            +
                b.extend Tablesmith::HashRowsSource if b.first&.is_a?(Hash)
         | 
| 168 172 |  | 
| 169 | 
            -
                 | 
| 170 | 
            -
                  b.extend Tablesmith::ArrayRowsSource
         | 
| 171 | 
            -
                end
         | 
| 173 | 
            +
                b.extend Tablesmith::ArrayRowsSource if b.first&.is_a?(Array)
         | 
| 172 174 |  | 
| 173 175 | 
             
                b
         | 
| 174 176 | 
             
              end
         | 
| @@ -179,4 +181,4 @@ class Hash | |
| 179 181 | 
             
                b = Tablesmith::Table.new([self])
         | 
| 180 182 | 
             
                b.extend Tablesmith::HashRowsSource
         | 
| 181 183 | 
             
              end
         | 
| 182 | 
            -
            end
         | 
| 184 | 
            +
            end
         | 
    
        data/lib/tablesmith/version.rb
    CHANGED
    
    
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'spec_helper'
         | 
| 2 4 |  | 
| 3 5 | 
             
            describe 'ActiveRecordSource' do
         | 
| @@ -12,11 +14,11 @@ describe 'ActiveRecordSource' do | |
| 12 14 | 
             
                  |    | B          |           |     |                   |
         | 
| 13 15 | 
             
                  +----+------------+-----------+-----+-------------------+
         | 
| 14 16 | 
             
                TABLE
         | 
| 15 | 
            -
                [a, b].to_table. | 
| 17 | 
            +
                [a, b].to_table.to_s.should == expected
         | 
| 16 18 | 
             
              end
         | 
| 17 19 |  | 
| 18 20 | 
             
              it 'outputs ActiveRecord in column order' do
         | 
| 19 | 
            -
                p = Person.create(: | 
| 21 | 
            +
                p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
         | 
| 20 22 | 
             
                expected = <<~TABLE
         | 
| 21 23 | 
             
                  +----+------------+-----------+-----+-------------------+
         | 
| 22 24 | 
             
                  | id | first_name | last_name | age | custom_attributes |
         | 
| @@ -24,11 +26,11 @@ describe 'ActiveRecordSource' do | |
| 24 26 | 
             
                  | 1  | chris      | mo        | 43  |                   |
         | 
| 25 27 | 
             
                  +----+------------+-----------+-----+-------------------+
         | 
| 26 28 | 
             
                TABLE
         | 
| 27 | 
            -
                [p].to_table. | 
| 29 | 
            +
                [p].to_table.to_s.should == expected
         | 
| 28 30 | 
             
              end
         | 
| 29 31 |  | 
| 30 32 | 
             
              it 'handles custom serialization options in batch' do
         | 
| 31 | 
            -
                p = Person.create(: | 
| 33 | 
            +
                p = Person.create(first_name: 'chrismo', age: 43)
         | 
| 32 34 |  | 
| 33 35 | 
             
                expected = <<~TABLE
         | 
| 34 36 | 
             
                  +------------+-----+-----------+
         | 
| @@ -40,14 +42,14 @@ describe 'ActiveRecordSource' do | |
| 40 42 | 
             
                b = [p].to_table
         | 
| 41 43 |  | 
| 42 44 | 
             
                def b.serializable_options
         | 
| 43 | 
            -
                  {: | 
| 45 | 
            +
                  { only: %i[first_name age], methods: [:year_born] }
         | 
| 44 46 | 
             
                end
         | 
| 45 47 |  | 
| 46 | 
            -
                b. | 
| 48 | 
            +
                b.to_s.should == expected
         | 
| 47 49 | 
             
              end
         | 
| 48 50 |  | 
| 49 51 | 
             
              it 'auto reloads records' do
         | 
| 50 | 
            -
                p = Person.create(: | 
| 52 | 
            +
                p = Person.create(first_name: 'chrismo', age: 43)
         | 
| 51 53 |  | 
| 52 54 | 
             
                expected = <<~TABLE
         | 
| 53 55 | 
             
                  +------------+-----+-----------+
         | 
| @@ -59,9 +61,9 @@ describe 'ActiveRecordSource' do | |
| 59 61 | 
             
                b = [p].to_table
         | 
| 60 62 |  | 
| 61 63 | 
             
                def b.serializable_options
         | 
| 62 | 
            -
                  {: | 
| 64 | 
            +
                  { only: %i[first_name age], methods: [:year_born] }
         | 
| 63 65 | 
             
                end
         | 
| 64 | 
            -
                b. | 
| 66 | 
            +
                b.to_s.should == expected
         | 
| 65 67 |  | 
| 66 68 | 
             
                # update the value through another instance.
         | 
| 67 69 | 
             
                Person.last.update_column(:age, 46)
         | 
| @@ -73,11 +75,11 @@ describe 'ActiveRecordSource' do | |
| 73 75 | 
             
                  | chrismo    | 46  | 1968      |
         | 
| 74 76 | 
             
                  +------------+-----+-----------+
         | 
| 75 77 | 
             
                TABLE
         | 
| 76 | 
            -
                b. | 
| 78 | 
            +
                b.to_s.should == expected
         | 
| 77 79 | 
             
              end
         | 
| 78 80 |  | 
| 79 81 | 
             
              it 'handles column name partials' do
         | 
| 80 | 
            -
                p = Person.create(: | 
| 82 | 
            +
                p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
         | 
| 81 83 | 
             
                expected = <<~TABLE
         | 
| 82 84 | 
             
                  +-------+------+-----+
         | 
| 83 85 | 
             
                  | first | last | age |
         | 
| @@ -88,14 +90,14 @@ describe 'ActiveRecordSource' do | |
| 88 90 | 
             
                b = [p].to_table
         | 
| 89 91 |  | 
| 90 92 | 
             
                def b.serializable_options
         | 
| 91 | 
            -
                  {: | 
| 93 | 
            +
                  { only: %i[first last age] }
         | 
| 92 94 | 
             
                end
         | 
| 93 95 |  | 
| 94 | 
            -
                b. | 
| 96 | 
            +
                b.to_s.should == expected
         | 
| 95 97 | 
             
              end
         | 
| 96 98 |  | 
| 97 99 | 
             
              it 'handles column name partials across words' do
         | 
| 98 | 
            -
                p = Person.create(: | 
| 100 | 
            +
                p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
         | 
| 99 101 | 
             
                expected = <<~TABLE
         | 
| 100 102 | 
             
                  +--------+--------+-----+
         | 
| 101 103 | 
             
                  | f_name | l_name | age |
         | 
| @@ -106,14 +108,14 @@ describe 'ActiveRecordSource' do | |
| 106 108 | 
             
                b = [p].to_table
         | 
| 107 109 |  | 
| 108 110 | 
             
                def b.serializable_options
         | 
| 109 | 
            -
                  {: | 
| 111 | 
            +
                  { only: %i[f_name l_name age] }
         | 
| 110 112 | 
             
                end
         | 
| 111 113 |  | 
| 112 | 
            -
                b. | 
| 114 | 
            +
                b.to_s.should == expected
         | 
| 113 115 | 
             
              end
         | 
| 114 116 |  | 
| 115 117 | 
             
              it 'handles explicit column aliases' do
         | 
| 116 | 
            -
                p = Person.create(: | 
| 118 | 
            +
                p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
         | 
| 117 119 | 
             
                expected = <<~TABLE
         | 
| 118 120 | 
             
                  +---------------+----------+-----+
         | 
| 119 121 | 
             
                  | primer_nombre | apellido | age |
         | 
| @@ -129,10 +131,10 @@ describe 'ActiveRecordSource' do | |
| 129 131 | 
             
                end
         | 
| 130 132 |  | 
| 131 133 | 
             
                def b.serializable_options
         | 
| 132 | 
            -
                  {: | 
| 134 | 
            +
                  { only: %i[first_name last_name age] }
         | 
| 133 135 | 
             
                end
         | 
| 134 136 |  | 
| 135 | 
            -
                b. | 
| 137 | 
            +
                b.to_s.should == expected
         | 
| 136 138 | 
             
              end
         | 
| 137 139 |  | 
| 138 140 | 
             
              it 'handles associations without aliases' do
         | 
| @@ -141,7 +143,7 @@ describe 'ActiveRecordSource' do | |
| 141 143 | 
             
                b = [s].to_table
         | 
| 142 144 |  | 
| 143 145 | 
             
                def b.serializable_options
         | 
| 144 | 
            -
                  {: | 
| 146 | 
            +
                  { only: [:name], include: { account: { only: %i[name tax_identification_number] } } }
         | 
| 145 147 | 
             
                end
         | 
| 146 148 |  | 
| 147 149 | 
             
                expected = <<~TABLE
         | 
| @@ -154,7 +156,7 @@ describe 'ActiveRecordSource' do | |
| 154 156 | 
             
                  +----------+---------+---------------------------+
         | 
| 155 157 | 
             
                TABLE
         | 
| 156 158 |  | 
| 157 | 
            -
                b. | 
| 159 | 
            +
                b.to_s.should == expected
         | 
| 158 160 | 
             
              end
         | 
| 159 161 |  | 
| 160 162 | 
             
              it 'handles associations with aliases' do
         | 
| @@ -163,7 +165,7 @@ describe 'ActiveRecordSource' do | |
| 163 165 | 
             
                b = [s].to_table
         | 
| 164 166 |  | 
| 165 167 | 
             
                def b.serializable_options
         | 
| 166 | 
            -
                  {: | 
| 168 | 
            +
                  { only: [:name], include: { account: { only: %i[name tax_id] } } }
         | 
| 167 169 | 
             
                end
         | 
| 168 170 |  | 
| 169 171 | 
             
                expected = <<~TABLE
         | 
| @@ -176,7 +178,7 @@ describe 'ActiveRecordSource' do | |
| 176 178 | 
             
                  +----------+---------+--------+
         | 
| 177 179 | 
             
                TABLE
         | 
| 178 180 |  | 
| 179 | 
            -
                b. | 
| 181 | 
            +
                b.to_s.should == expected
         | 
| 180 182 | 
             
              end
         | 
| 181 183 |  | 
| 182 184 | 
             
              it 'retains serializable_options ordering'
         | 
| @@ -187,7 +189,7 @@ describe 'ActiveRecordSource' do | |
| 187 189 |  | 
| 188 190 | 
             
              # may need/want to handle the hash resulting from an association differently from the hash resulting from a method/attr
         | 
| 189 191 | 
             
              it 'supports field with hash contents' do
         | 
| 190 | 
            -
                p = Person.create(first_name: 'chrismo', custom_attributes: {skills: {instrument: 'piano', style: 'jazz'}})
         | 
| 192 | 
            +
                p = Person.create(first_name: 'chrismo', custom_attributes: { skills: { instrument: 'piano', style: 'jazz' } })
         | 
| 191 193 | 
             
                b = [p].to_table
         | 
| 192 194 |  | 
| 193 195 | 
             
                a = format_ids([p.id])[0]
         | 
| @@ -201,12 +203,12 @@ describe 'ActiveRecordSource' do | |
| 201 203 | 
             
                  +----+------------+-----------+-----+----------------------------------------+
         | 
| 202 204 | 
             
                TABLE
         | 
| 203 205 |  | 
| 204 | 
            -
                b. | 
| 206 | 
            +
                b.to_s.should == expected
         | 
| 205 207 | 
             
              end
         | 
| 206 208 |  | 
| 207 209 | 
             
              it 'supports multiple rows with different column counts' do
         | 
| 208 | 
            -
                p2 = Person.create(first_name: 'romer', custom_attributes: {instrument: 'kazoo'})
         | 
| 209 | 
            -
                p1 = Person.create(first_name: 'chrismo', custom_attributes: {instrument: 'piano', style: 'jazz'})
         | 
| 210 | 
            +
                p2 = Person.create(first_name: 'romer', custom_attributes: { instrument: 'kazoo' })
         | 
| 211 | 
            +
                p1 = Person.create(first_name: 'chrismo', custom_attributes: { instrument: 'piano', style: 'jazz' })
         | 
| 210 212 | 
             
                p3 = Person.create(first_name: 'glv', custom_attributes: {})
         | 
| 211 213 | 
             
                batch = [p2, p1, p3].to_table
         | 
| 212 214 |  | 
| @@ -224,12 +226,12 @@ describe 'ActiveRecordSource' do | |
| 224 226 | 
             
                  +----+------------+-----------+-----+------------+-----------+
         | 
| 225 227 | 
             
                TABLE
         | 
| 226 228 |  | 
| 227 | 
            -
                batch. | 
| 229 | 
            +
                batch.to_s.should == expected
         | 
| 228 230 | 
             
              end
         | 
| 229 231 |  | 
| 230 232 | 
             
              it 'supports consistent ordering of dynamic columns' do
         | 
| 231 | 
            -
                p1 = Person.create(first_name: 'chrismo', custom_attributes: {instrument: 'piano', style: 'jazz'})
         | 
| 232 | 
            -
                p2 = Person.create(first_name: 'romer', custom_attributes: {hobby: 'games'})
         | 
| 233 | 
            +
                p1 = Person.create(first_name: 'chrismo', custom_attributes: { instrument: 'piano', style: 'jazz' })
         | 
| 234 | 
            +
                p2 = Person.create(first_name: 'romer', custom_attributes: { hobby: 'games' })
         | 
| 233 235 | 
             
                batch = [p1, p2].to_table
         | 
| 234 236 |  | 
| 235 237 | 
             
                a, b = format_ids([p1.id, p2.id])
         | 
| @@ -245,7 +247,7 @@ describe 'ActiveRecordSource' do | |
| 245 247 | 
             
                  +----+------------+-----------+-----+--------+------------+--------+
         | 
| 246 248 | 
             
                TABLE
         | 
| 247 249 |  | 
| 248 | 
            -
                batch. | 
| 250 | 
            +
                batch.to_s.should == expected
         | 
| 249 251 | 
             
              end
         | 
| 250 252 |  | 
| 251 253 | 
             
              it 'handles AR instance without an association present' do
         | 
| @@ -253,7 +255,7 @@ describe 'ActiveRecordSource' do | |
| 253 255 | 
             
                b = [s].to_table
         | 
| 254 256 |  | 
| 255 257 | 
             
                def b.serializable_options
         | 
| 256 | 
            -
                  {: | 
| 258 | 
            +
                  { only: [:name], include: { account: { only: %i[name tax_id] } } }
         | 
| 257 259 | 
             
                end
         | 
| 258 260 |  | 
| 259 261 | 
             
                expected = <<~TABLE
         | 
| @@ -264,11 +266,11 @@ describe 'ActiveRecordSource' do | |
| 264 266 | 
             
                  +----------+
         | 
| 265 267 | 
             
                TABLE
         | 
| 266 268 |  | 
| 267 | 
            -
                b. | 
| 269 | 
            +
                b.to_s.should == expected
         | 
| 268 270 | 
             
              end
         | 
| 269 271 |  | 
| 270 272 | 
             
              it 'properly groups when original columns not sequential' do
         | 
| 271 | 
            -
                s2 = Supplier.create(name: 'sup. two', custom_attributes: {a: 1})
         | 
| 273 | 
            +
                s2 = Supplier.create(name: 'sup. two', custom_attributes: { a: 1 })
         | 
| 272 274 |  | 
| 273 275 | 
             
                def s2.foo
         | 
| 274 276 | 
             
                  ''
         | 
| @@ -278,7 +280,7 @@ describe 'ActiveRecordSource' do | |
| 278 280 |  | 
| 279 281 | 
             
                # methods need Columns as well
         | 
| 280 282 | 
             
                def b.serializable_options
         | 
| 281 | 
            -
                  {: | 
| 283 | 
            +
                  { only: %i[name custom_attributes], methods: [:foo] }
         | 
| 282 284 | 
             
                end
         | 
| 283 285 |  | 
| 284 286 | 
             
                expected = <<~TABLE
         | 
| @@ -291,12 +293,12 @@ describe 'ActiveRecordSource' do | |
| 291 293 | 
             
                  +----------+------+-------------------+
         | 
| 292 294 | 
             
                TABLE
         | 
| 293 295 |  | 
| 294 | 
            -
                b. | 
| 296 | 
            +
                b.to_s.should == expected
         | 
| 295 297 | 
             
              end
         | 
| 296 298 |  | 
| 297 299 | 
             
              it 'supports one to many association' do
         | 
| 298 300 | 
             
                p = Parent.create(name: 'parent')
         | 
| 299 | 
            -
                 | 
| 301 | 
            +
                Child.create(name: 'child', parent: p)
         | 
| 300 302 |  | 
| 301 303 | 
             
                b = [p].to_table
         | 
| 302 304 |  | 
| @@ -310,32 +312,32 @@ describe 'ActiveRecordSource' do | |
| 310 312 | 
             
                TABLE
         | 
| 311 313 |  | 
| 312 314 | 
             
                def b.serializable_options
         | 
| 313 | 
            -
                  {: | 
| 315 | 
            +
                  { include: { children: { only: [:name] } } }
         | 
| 314 316 | 
             
                end
         | 
| 315 317 |  | 
| 316 | 
            -
                b. | 
| 318 | 
            +
                b.to_s.should == expected
         | 
| 317 319 | 
             
              end
         | 
| 318 320 |  | 
| 319 321 | 
             
              def format_ids(ary)
         | 
| 320 | 
            -
                ary.map {|value| " #{value.to_s.ljust(3)}" }
         | 
| 322 | 
            +
                ary.map { |value| " #{value.to_s.ljust(3)}" }
         | 
| 321 323 | 
             
              end
         | 
| 322 324 |  | 
| 323 325 | 
             
              describe 'fold un-sourced attributes into source hash' do
         | 
| 324 326 | 
             
                let(:obj) { Object.new.extend Tablesmith::ActiveRecordSource }
         | 
| 325 327 |  | 
| 326 328 | 
             
                it 'should handle simple hash' do
         | 
| 327 | 
            -
                  obj.fold_un_sourced_attributes_into_source_hash(:foo,  | 
| 329 | 
            +
                  obj.fold_un_sourced_attributes_into_source_hash(:foo, a: 1, b: 2).should == { foo: { a: 1, b: 2 } }
         | 
| 328 330 | 
             
                end
         | 
| 329 331 |  | 
| 330 332 | 
             
                it 'should handle nested hashes' do
         | 
| 331 | 
            -
                  before = {'name' => 'chris', account: {'name' => 'account_name'}}
         | 
| 332 | 
            -
                  expected = {foo: {'name' => 'chris'}, account: {'name' => 'account_name'}}
         | 
| 333 | 
            +
                  before = { 'name' => 'chris', account: { 'name' => 'account_name' } }
         | 
| 334 | 
            +
                  expected = { foo: { 'name' => 'chris' }, account: { 'name' => 'account_name' } }
         | 
| 333 335 | 
             
                  obj.fold_un_sourced_attributes_into_source_hash(:foo, before).should == expected
         | 
| 334 336 | 
             
                end
         | 
| 335 337 |  | 
| 336 338 | 
             
                it 'should handle deep nested hashes' do
         | 
| 337 | 
            -
                  before = {'name' => 'chris', account: {'id' => {'name' => 'account_name', 'number' =>  | 
| 338 | 
            -
                  expected = {foo: {'name' => 'chris'}, account: {'id' => {'name' => 'account_name', 'number' =>  | 
| 339 | 
            +
                  before = { 'name' => 'chris', account: { 'id' => { 'name' => 'account_name', 'number' => 123_456 } } }
         | 
| 340 | 
            +
                  expected = { foo: { 'name' => 'chris' }, account: { 'id' => { 'name' => 'account_name', 'number' => 123_456 } } }
         | 
| 339 341 | 
             
                  obj.fold_un_sourced_attributes_into_source_hash(:foo, before).should == expected
         | 
| 340 342 | 
             
                end
         | 
| 341 343 | 
             
              end
         | 
| @@ -344,15 +346,15 @@ describe 'ActiveRecordSource' do | |
| 344 346 | 
             
                let(:obj) { Object.new.extend Tablesmith::ActiveRecordSource }
         | 
| 345 347 |  | 
| 346 348 | 
             
                it 'should flatten inner hash' do
         | 
| 347 | 
            -
                  before = {foo: {'name' => 'chris'}, account: {'name' => 'account_name'}}
         | 
| 348 | 
            -
                  expected = {'foo.name' => 'chris', 'account.name' => 'account_name'}
         | 
| 349 | 
            +
                  before = { foo: { 'name' => 'chris' }, account: { 'name' => 'account_name' } }
         | 
| 350 | 
            +
                  expected = { 'foo.name' => 'chris', 'account.name' => 'account_name' }
         | 
| 349 351 |  | 
| 350 352 | 
             
                  obj.flatten_inner_hashes(before).should == expected
         | 
| 351 353 | 
             
                end
         | 
| 352 354 |  | 
| 353 355 | 
             
                it 'should to_s deep nested hashes' do
         | 
| 354 | 
            -
                  before = {foo: {'name' => 'chris'}, account: {'id' => {'name' => 'account_name', 'number' =>  | 
| 355 | 
            -
                  expected = {'foo.name' => 'chris',  | 
| 356 | 
            +
                  before = { foo: { 'name' => 'chris' }, account: { 'id' => { 'name' => 'account_name', 'number' => 123_456 } } }
         | 
| 357 | 
            +
                  expected = { 'foo.name' => 'chris', 'account.id' => '{"name"=>"account_name", "number"=>123456}' }
         | 
| 356 358 |  | 
| 357 359 | 
             
                  obj.flatten_inner_hashes(before).should == expected
         | 
| 358 360 | 
             
                end
         |