tablesmith 0.4.0 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/tablesmith.rb +3 -0
- data/lib/tablesmith/active_record_source.rb +4 -2
- data/lib/tablesmith/array_rows_source.rb +3 -1
- data/lib/tablesmith/delegated_array_class.rb +39 -0
- data/lib/tablesmith/hash_rows_base.rb +4 -1
- data/lib/tablesmith/hash_rows_source.rb +8 -8
- data/lib/tablesmith/html_formatter.rb +3 -5
- data/lib/tablesmith/table.rb +53 -40
- data/lib/tablesmith/version.rb +3 -1
- data/spec/active_record_table_spec.rb +53 -49
- data/spec/array_table_spec.rb +69 -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 +21 -9
- metadata +98 -24
- data/.gitignore +0 -4
- data/.rspec +0 -2
- data/.rubocop.yml +0 -11
- data/.ruby-version +0 -1
- data/.travis.yml +0 -1
- data/Gemfile +0 -4
- data/LICENSE +0 -19
- data/README.md +0 -35
- data/Rakefile +0 -11
- data/tablesmith.gemspec +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e087948276046a5d4ca9af901465ce4afe11c627acd172b1be29d6a46e018672
|
4
|
+
data.tar.gz: 1f366c3de38e86d801d38a8f1356fa76f11d6b71eb5b5d1c8df59331be3dafcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2f5bd5a0c206007be448a651aaf9de768615d526640c0bf3edd760b3e369303da06554aa18cc304faaf4d2940a9dfa824a25a0bbba06f4273e2b6680fb785a2
|
7
|
+
data.tar.gz: effe9b8dcfaa3d1f747006aa05af31d4eb92586af0ab358a17e36ece76b019cc2698178af52a98bd219b3366af1e8051c7c0a012b915fa7da33793bb314e82ad
|
data/lib/tablesmith.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Tablesmith::ActiveRecordSource
|
2
4
|
include Tablesmith::HashRowsBase
|
3
5
|
|
@@ -37,7 +39,7 @@ module Tablesmith::ActiveRecordSource
|
|
37
39
|
end
|
38
40
|
|
39
41
|
include.each do |association, opts|
|
40
|
-
ar_class = first.class.reflections[association].klass
|
42
|
+
ar_class = first.class.reflections[association.to_s].klass
|
41
43
|
process_columns(opts, ar_class)
|
42
44
|
end
|
43
45
|
end
|
@@ -73,7 +75,7 @@ module Tablesmith::ActiveRecordSource
|
|
73
75
|
def flatten_inner_hashes(hash)
|
74
76
|
new_hash = {}
|
75
77
|
stack = hash.each_pair.to_a
|
76
|
-
while ary = stack.shift
|
78
|
+
while (ary = stack.shift)
|
77
79
|
key, value = ary
|
78
80
|
if value.is_a?(Hash)
|
79
81
|
value.each_pair do |assoc_key, assoc_value|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Tablesmith::ArrayRowsSource
|
2
4
|
def text_table
|
3
5
|
build_columns if columns.nil?
|
@@ -11,7 +13,7 @@ module Tablesmith::ArrayRowsSource
|
|
11
13
|
# TODO: no support for deep
|
12
14
|
def build_columns
|
13
15
|
@columns ||= []
|
14
|
-
|
16
|
+
map do |array_row|
|
15
17
|
@columns << array_row.map { |item| Tablesmith::Column.new(name: item) }
|
16
18
|
end
|
17
19
|
@columns.flatten!
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tablesmith
|
4
|
+
# This adjustment to `DelegateClass(Array)` is necessary to allow calling puts
|
5
|
+
# on a `Tablesmith::Table` and still get the table output, rather than the
|
6
|
+
# default puts output of the underlying `Array`.
|
7
|
+
#
|
8
|
+
# Explaining why requires breaking some things down.
|
9
|
+
#
|
10
|
+
# The implementation of `Kernel::puts` has special code for an `Array`. The
|
11
|
+
# code inside `rb_io_puts` (in io.c) first checks to see if the object passed
|
12
|
+
# to it is a `String`. If not, it then calls `io_puts_ary`, which in turn
|
13
|
+
# calls `rb_check_array_type`. If `rb_check_array_type` confirms the passed
|
14
|
+
# object is an `Array`, then `io_puts_ary` loops over the elements of the
|
15
|
+
# `Array` and passes it to `rb_io_puts`. If the `Array` check fails in the
|
16
|
+
# original `rb_io_puts`, `rb_obj_as_string` is used.
|
17
|
+
#
|
18
|
+
# Early versions of `Tablesmith::Table` subclassed `Array`, but even after
|
19
|
+
# changing `Tablesmith::Table` to use any of the `Delegator` options, the code
|
20
|
+
# in `rb_check_array_type` still detected `Tablesmith::Table` as an `Array`.
|
21
|
+
# How does it do this?
|
22
|
+
#
|
23
|
+
# `rb_check_array_type` calls:
|
24
|
+
#
|
25
|
+
# `return rb_check_convert_type_with_id(ary, T_ARRAY, "Array", idTo_ary);`
|
26
|
+
#
|
27
|
+
# If a straight up type check fails, then it attempts to convert the object to
|
28
|
+
# an `Array` via the `to_ary` method.
|
29
|
+
#
|
30
|
+
# And wha-lah. We simply need to undefine the `to_ary` method added to
|
31
|
+
# `Tablesmith::Table` by `DelegateClass(Array)` and `rb_io_puts` will no
|
32
|
+
# longer output `Table` as an `Array` and will use its `to_s` method, the same
|
33
|
+
# as `print`.
|
34
|
+
def self.delegated_array_class
|
35
|
+
DelegateClass(Array).tap do |klass|
|
36
|
+
klass.undef_method(:to_ary)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# ActiveRecord and HashRowsSource share a lot, but not everything.
|
2
4
|
module Tablesmith::HashRowsBase
|
3
5
|
# not all resulting rows will have data in all columns, so make sure all rows pad out missing columns
|
@@ -8,6 +10,7 @@ module Tablesmith::HashRowsBase
|
|
8
10
|
|
9
11
|
def sort_columns(rows)
|
10
12
|
return if column_order.empty?
|
13
|
+
|
11
14
|
rows.map! do |row|
|
12
15
|
# this sort gives preference to column_order then falls back to alphabetic for leftovers.
|
13
16
|
# this is handy when columns auto-generate based on hash data.
|
@@ -32,4 +35,4 @@ module Tablesmith::HashRowsBase
|
|
32
35
|
column_names = rows.first.map(&:first)
|
33
36
|
grouped_headers(column_names) + [apply_column_aliases(column_names), :separator]
|
34
37
|
end
|
35
|
-
end
|
38
|
+
end
|
@@ -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
|
@@ -21,34 +21,32 @@ class HtmlFormatter
|
|
21
21
|
|
22
22
|
def append_table_head
|
23
23
|
@lines << "#{indent}<thead>"
|
24
|
-
@lines << "#{indent}<tr>"
|
25
24
|
unless @table.empty?
|
26
25
|
rows = @table.text_table.rows[0..0]
|
27
26
|
append_rows(rows, 'th')
|
28
27
|
end
|
29
|
-
@lines << "#{indent}</tr>"
|
30
28
|
@lines << "#{indent}</thead>"
|
31
29
|
end
|
32
30
|
|
33
31
|
def append_table_body
|
34
32
|
@lines << "#{indent}<tbody>"
|
35
|
-
@lines << "#{indent}<tr>"
|
36
|
-
|
37
33
|
unless @table.empty?
|
38
34
|
rows = @table.text_table.rows[2..-1]
|
39
35
|
append_rows(rows, 'td')
|
40
36
|
end
|
41
|
-
@lines << "#{indent}</tr>"
|
42
37
|
@lines << "#{indent}</tbody>"
|
43
38
|
end
|
44
39
|
|
45
40
|
def append_rows(rows, tag)
|
46
41
|
rows.each do |row|
|
47
42
|
next if row == :separator
|
43
|
+
|
44
|
+
@lines << "#{indent}<tr>"
|
48
45
|
row.map do |cell|
|
49
46
|
value = cell_value(cell)
|
50
47
|
@lines << "#{indent}#{indent}<#{tag}>#{value}</#{tag}>"
|
51
48
|
end
|
49
|
+
@lines << "#{indent}</tr>"
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
data/lib/tablesmith/table.rb
CHANGED
@@ -1,21 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'text-table'
|
2
4
|
require 'csv'
|
3
5
|
|
4
6
|
module Tablesmith
|
5
|
-
class Table <
|
7
|
+
class Table < Tablesmith.delegated_array_class
|
8
|
+
def initialize(array = [])
|
9
|
+
super(array)
|
10
|
+
@array = array
|
11
|
+
end
|
12
|
+
|
6
13
|
def method_missing(meth_id, *args)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
# In order to support `Kernel::puts` of a `Table`, we need to ignore
|
15
|
+
# `to_ary` calls here as well. See comments on `delegated_array_class`.
|
16
|
+
#
|
17
|
+
# While `DelegatorClass(Array)` proactively defines methods on `Table`
|
18
|
+
# that come from `Array`, it _also_ will pass calls through method_missing
|
19
|
+
# to the target object if it says it will respond to it.
|
20
|
+
#
|
21
|
+
# It seems a little redundant, but it is what it is, and so we must also
|
22
|
+
# cut off calls to `to_ary` in both places.
|
23
|
+
return nil if meth_id.to_sym == :to_ary
|
24
|
+
|
25
|
+
super
|
13
26
|
end
|
14
27
|
|
15
|
-
def respond_to_missing?
|
28
|
+
def respond_to_missing?(meth_id, _include_all)
|
29
|
+
return false if meth_id.to_sym == :to_ary
|
30
|
+
|
16
31
|
super
|
17
32
|
end
|
18
33
|
|
34
|
+
def to_s
|
35
|
+
text_table.to_s
|
36
|
+
end
|
37
|
+
|
19
38
|
# irb
|
20
39
|
def inspect
|
21
40
|
pretty_inspect
|
@@ -32,9 +51,9 @@ module Tablesmith
|
|
32
51
|
end
|
33
52
|
|
34
53
|
def text_table
|
35
|
-
return ['(empty)'].to_text_table if
|
54
|
+
return ['(empty)'].to_text_table if empty?
|
36
55
|
|
37
|
-
rows =
|
56
|
+
rows = map { |item| convert_item_to_hash_row(item) }.compact
|
38
57
|
|
39
58
|
normalize_keys(rows)
|
40
59
|
|
@@ -48,12 +67,13 @@ module Tablesmith
|
|
48
67
|
CSV.generate do |csv|
|
49
68
|
text_table.rows.each do |row|
|
50
69
|
next if row == :separator
|
70
|
+
|
51
71
|
csv << row.map do |cell|
|
52
72
|
case cell
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
73
|
+
when Hash
|
74
|
+
cell[:value]
|
75
|
+
else
|
76
|
+
cell
|
57
77
|
end
|
58
78
|
end
|
59
79
|
end
|
@@ -70,8 +90,7 @@ module Tablesmith
|
|
70
90
|
end
|
71
91
|
|
72
92
|
# override in subclass or mixin
|
73
|
-
def sort_columns(rows)
|
74
|
-
end
|
93
|
+
def sort_columns(rows); end
|
75
94
|
|
76
95
|
# override in subclass or mixin
|
77
96
|
def convert_item_to_hash_row(item)
|
@@ -79,22 +98,24 @@ module Tablesmith
|
|
79
98
|
end
|
80
99
|
|
81
100
|
# override in subclass or mixin
|
82
|
-
def normalize_keys(rows)
|
83
|
-
end
|
101
|
+
def normalize_keys(rows); end
|
84
102
|
|
85
103
|
# override in subclass or mixin
|
86
104
|
def column_order
|
87
105
|
[]
|
88
106
|
end
|
89
107
|
|
90
|
-
|
91
|
-
@columns
|
92
|
-
end
|
108
|
+
attr_reader :columns
|
93
109
|
|
94
110
|
def create_headers(rows)
|
95
|
-
|
96
|
-
|
97
|
-
|
111
|
+
first_element = rows.first
|
112
|
+
if first_element.is_a?(Array)
|
113
|
+
top_row = first_element
|
114
|
+
column_names = top_row.first.is_a?(Array) ? top_row.map(&:first) : top_row
|
115
|
+
grouped_headers(column_names) + [apply_column_aliases(column_names), :separator]
|
116
|
+
else
|
117
|
+
[]
|
118
|
+
end
|
98
119
|
end
|
99
120
|
|
100
121
|
def grouped_headers(column_names)
|
@@ -110,9 +131,7 @@ module Tablesmith
|
|
110
131
|
else
|
111
132
|
row = []
|
112
133
|
# this relies on Ruby versions where hash retains add order
|
113
|
-
groups.
|
114
|
-
row << {value: name, align: :center, colspan: span}
|
115
|
-
end
|
134
|
+
groups.each { |name, span| row << {value: name, align: :center, colspan: span} }
|
116
135
|
[row, :separator]
|
117
136
|
end
|
118
137
|
end
|
@@ -121,7 +140,7 @@ module Tablesmith
|
|
121
140
|
column_names.map do |name|
|
122
141
|
instance = columns.detect { |ca| ca.name.to_s == name.to_s }
|
123
142
|
value = instance ? instance.display_name : name
|
124
|
-
{:
|
143
|
+
{ value: value, align: :center }
|
125
144
|
end
|
126
145
|
end
|
127
146
|
end
|
@@ -129,14 +148,14 @@ module Tablesmith
|
|
129
148
|
class Column
|
130
149
|
attr_accessor :source, :name, :alias
|
131
150
|
|
132
|
-
def initialize(attributes={})
|
151
|
+
def initialize(attributes = {})
|
133
152
|
@source = attributes.delete(:source)
|
134
153
|
@name = attributes.delete(:name)
|
135
154
|
@alias = attributes.delete(:alias)
|
136
155
|
end
|
137
156
|
|
138
157
|
def display_name
|
139
|
-
|
158
|
+
(@alias || @name).to_s
|
140
159
|
end
|
141
160
|
|
142
161
|
def full_unaliased_name
|
@@ -157,18 +176,12 @@ class Array
|
|
157
176
|
# so mixed content could be supported. Maybe every cell could be
|
158
177
|
# rendered appropriately, with nested tables.
|
159
178
|
if defined?(ActiveRecord) && defined?(ActiveRecord::Base)
|
160
|
-
|
161
|
-
b.extend Tablesmith::ActiveRecordSource
|
162
|
-
end
|
179
|
+
b.extend Tablesmith::ActiveRecordSource if b.first&.is_a?(ActiveRecord::Base)
|
163
180
|
end
|
164
181
|
|
165
|
-
|
166
|
-
b.extend Tablesmith::HashRowsSource
|
167
|
-
end
|
182
|
+
b.extend Tablesmith::HashRowsSource if b.first&.is_a?(Hash)
|
168
183
|
|
169
|
-
|
170
|
-
b.extend Tablesmith::ArrayRowsSource
|
171
|
-
end
|
184
|
+
b.extend Tablesmith::ArrayRowsSource if b.first&.is_a?(Array)
|
172
185
|
|
173
186
|
b
|
174
187
|
end
|
@@ -179,4 +192,4 @@ class Hash
|
|
179
192
|
b = Tablesmith::Table.new([self])
|
180
193
|
b.extend Tablesmith::HashRowsSource
|
181
194
|
end
|
182
|
-
end
|
195
|
+
end
|
data/lib/tablesmith/version.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
5
|
+
# rubocop:disable Rails/SkipsModelValidations
|
3
6
|
describe 'ActiveRecordSource' do
|
4
7
|
it 'outputs text table of multiple ActiveRecords' do
|
5
8
|
a = Person.new.tap { |c| c.first_name = 'A' }
|
@@ -12,11 +15,11 @@ describe 'ActiveRecordSource' do
|
|
12
15
|
| | B | | | |
|
13
16
|
+----+------------+-----------+-----+-------------------+
|
14
17
|
TABLE
|
15
|
-
[a, b].to_table.
|
18
|
+
[a, b].to_table.to_s.should == expected
|
16
19
|
end
|
17
20
|
|
18
21
|
it 'outputs ActiveRecord in column order' do
|
19
|
-
p = Person.create(:
|
22
|
+
p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
|
20
23
|
expected = <<~TABLE
|
21
24
|
+----+------------+-----------+-----+-------------------+
|
22
25
|
| id | first_name | last_name | age | custom_attributes |
|
@@ -24,11 +27,11 @@ describe 'ActiveRecordSource' do
|
|
24
27
|
| 1 | chris | mo | 43 | |
|
25
28
|
+----+------------+-----------+-----+-------------------+
|
26
29
|
TABLE
|
27
|
-
[p].to_table.
|
30
|
+
[p].to_table.to_s.should == expected
|
28
31
|
end
|
29
32
|
|
30
33
|
it 'handles custom serialization options in batch' do
|
31
|
-
p = Person.create(:
|
34
|
+
p = Person.create(first_name: 'chrismo', age: 43)
|
32
35
|
|
33
36
|
expected = <<~TABLE
|
34
37
|
+------------+-----+-----------+
|
@@ -40,14 +43,14 @@ describe 'ActiveRecordSource' do
|
|
40
43
|
b = [p].to_table
|
41
44
|
|
42
45
|
def b.serializable_options
|
43
|
-
{:
|
46
|
+
{ only: %i[first_name age], methods: [:year_born] }
|
44
47
|
end
|
45
48
|
|
46
|
-
b.
|
49
|
+
b.to_s.should == expected
|
47
50
|
end
|
48
51
|
|
49
52
|
it 'auto reloads records' do
|
50
|
-
p = Person.create(:
|
53
|
+
p = Person.create(first_name: 'chrismo', age: 43)
|
51
54
|
|
52
55
|
expected = <<~TABLE
|
53
56
|
+------------+-----+-----------+
|
@@ -59,9 +62,9 @@ describe 'ActiveRecordSource' do
|
|
59
62
|
b = [p].to_table
|
60
63
|
|
61
64
|
def b.serializable_options
|
62
|
-
{:
|
65
|
+
{ only: %i[first_name age], methods: [:year_born] }
|
63
66
|
end
|
64
|
-
b.
|
67
|
+
b.to_s.should == expected
|
65
68
|
|
66
69
|
# update the value through another instance.
|
67
70
|
Person.last.update_column(:age, 46)
|
@@ -73,11 +76,11 @@ describe 'ActiveRecordSource' do
|
|
73
76
|
| chrismo | 46 | 1968 |
|
74
77
|
+------------+-----+-----------+
|
75
78
|
TABLE
|
76
|
-
b.
|
79
|
+
b.to_s.should == expected
|
77
80
|
end
|
78
81
|
|
79
82
|
it 'handles column name partials' do
|
80
|
-
p = Person.create(:
|
83
|
+
p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
|
81
84
|
expected = <<~TABLE
|
82
85
|
+-------+------+-----+
|
83
86
|
| first | last | age |
|
@@ -88,14 +91,14 @@ describe 'ActiveRecordSource' do
|
|
88
91
|
b = [p].to_table
|
89
92
|
|
90
93
|
def b.serializable_options
|
91
|
-
{:
|
94
|
+
{ only: %i[first last age] }
|
92
95
|
end
|
93
96
|
|
94
|
-
b.
|
97
|
+
b.to_s.should == expected
|
95
98
|
end
|
96
99
|
|
97
100
|
it 'handles column name partials across words' do
|
98
|
-
p = Person.create(:
|
101
|
+
p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
|
99
102
|
expected = <<~TABLE
|
100
103
|
+--------+--------+-----+
|
101
104
|
| f_name | l_name | age |
|
@@ -106,14 +109,14 @@ describe 'ActiveRecordSource' do
|
|
106
109
|
b = [p].to_table
|
107
110
|
|
108
111
|
def b.serializable_options
|
109
|
-
{:
|
112
|
+
{ only: %i[f_name l_name age] }
|
110
113
|
end
|
111
114
|
|
112
|
-
b.
|
115
|
+
b.to_s.should == expected
|
113
116
|
end
|
114
117
|
|
115
118
|
it 'handles explicit column aliases' do
|
116
|
-
p = Person.create(:
|
119
|
+
p = Person.create(first_name: 'chris', last_name: 'mo', age: 43)
|
117
120
|
expected = <<~TABLE
|
118
121
|
+---------------+----------+-----+
|
119
122
|
| primer_nombre | apellido | age |
|
@@ -129,10 +132,10 @@ describe 'ActiveRecordSource' do
|
|
129
132
|
end
|
130
133
|
|
131
134
|
def b.serializable_options
|
132
|
-
{:
|
135
|
+
{ only: %i[first_name last_name age] }
|
133
136
|
end
|
134
137
|
|
135
|
-
b.
|
138
|
+
b.to_s.should == expected
|
136
139
|
end
|
137
140
|
|
138
141
|
it 'handles associations without aliases' do
|
@@ -141,7 +144,7 @@ describe 'ActiveRecordSource' do
|
|
141
144
|
b = [s].to_table
|
142
145
|
|
143
146
|
def b.serializable_options
|
144
|
-
{:
|
147
|
+
{ only: [:name], include: { account: { only: %i[name tax_identification_number] } } }
|
145
148
|
end
|
146
149
|
|
147
150
|
expected = <<~TABLE
|
@@ -154,7 +157,7 @@ describe 'ActiveRecordSource' do
|
|
154
157
|
+----------+---------+---------------------------+
|
155
158
|
TABLE
|
156
159
|
|
157
|
-
b.
|
160
|
+
b.to_s.should == expected
|
158
161
|
end
|
159
162
|
|
160
163
|
it 'handles associations with aliases' do
|
@@ -163,7 +166,7 @@ describe 'ActiveRecordSource' do
|
|
163
166
|
b = [s].to_table
|
164
167
|
|
165
168
|
def b.serializable_options
|
166
|
-
{:
|
169
|
+
{ only: [:name], include: { account: { only: %i[name tax_id] } } }
|
167
170
|
end
|
168
171
|
|
169
172
|
expected = <<~TABLE
|
@@ -176,7 +179,7 @@ describe 'ActiveRecordSource' do
|
|
176
179
|
+----------+---------+--------+
|
177
180
|
TABLE
|
178
181
|
|
179
|
-
b.
|
182
|
+
b.to_s.should == expected
|
180
183
|
end
|
181
184
|
|
182
185
|
it 'retains serializable_options ordering'
|
@@ -187,7 +190,7 @@ describe 'ActiveRecordSource' do
|
|
187
190
|
|
188
191
|
# may need/want to handle the hash resulting from an association differently from the hash resulting from a method/attr
|
189
192
|
it 'supports field with hash contents' do
|
190
|
-
p = Person.create(first_name: 'chrismo', custom_attributes: {skills: {instrument: 'piano', style: 'jazz'}})
|
193
|
+
p = Person.create(first_name: 'chrismo', custom_attributes: { skills: { instrument: 'piano', style: 'jazz' } })
|
191
194
|
b = [p].to_table
|
192
195
|
|
193
196
|
a = format_ids([p.id])[0]
|
@@ -201,12 +204,12 @@ describe 'ActiveRecordSource' do
|
|
201
204
|
+----+------------+-----------+-----+----------------------------------------+
|
202
205
|
TABLE
|
203
206
|
|
204
|
-
b.
|
207
|
+
b.to_s.should == expected
|
205
208
|
end
|
206
209
|
|
207
210
|
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'})
|
211
|
+
p2 = Person.create(first_name: 'romer', custom_attributes: { instrument: 'kazoo' })
|
212
|
+
p1 = Person.create(first_name: 'chrismo', custom_attributes: { instrument: 'piano', style: 'jazz' })
|
210
213
|
p3 = Person.create(first_name: 'glv', custom_attributes: {})
|
211
214
|
batch = [p2, p1, p3].to_table
|
212
215
|
|
@@ -224,12 +227,12 @@ describe 'ActiveRecordSource' do
|
|
224
227
|
+----+------------+-----------+-----+------------+-----------+
|
225
228
|
TABLE
|
226
229
|
|
227
|
-
batch.
|
230
|
+
batch.to_s.should == expected
|
228
231
|
end
|
229
232
|
|
230
233
|
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'})
|
234
|
+
p1 = Person.create(first_name: 'chrismo', custom_attributes: { instrument: 'piano', style: 'jazz' })
|
235
|
+
p2 = Person.create(first_name: 'romer', custom_attributes: { hobby: 'games' })
|
233
236
|
batch = [p1, p2].to_table
|
234
237
|
|
235
238
|
a, b = format_ids([p1.id, p2.id])
|
@@ -245,7 +248,7 @@ describe 'ActiveRecordSource' do
|
|
245
248
|
+----+------------+-----------+-----+--------+------------+--------+
|
246
249
|
TABLE
|
247
250
|
|
248
|
-
batch.
|
251
|
+
batch.to_s.should == expected
|
249
252
|
end
|
250
253
|
|
251
254
|
it 'handles AR instance without an association present' do
|
@@ -253,7 +256,7 @@ describe 'ActiveRecordSource' do
|
|
253
256
|
b = [s].to_table
|
254
257
|
|
255
258
|
def b.serializable_options
|
256
|
-
{:
|
259
|
+
{ only: [:name], include: { account: { only: %i[name tax_id] } } }
|
257
260
|
end
|
258
261
|
|
259
262
|
expected = <<~TABLE
|
@@ -264,11 +267,11 @@ describe 'ActiveRecordSource' do
|
|
264
267
|
+----------+
|
265
268
|
TABLE
|
266
269
|
|
267
|
-
b.
|
270
|
+
b.to_s.should == expected
|
268
271
|
end
|
269
272
|
|
270
273
|
it 'properly groups when original columns not sequential' do
|
271
|
-
s2 = Supplier.create(name: 'sup. two', custom_attributes: {a: 1})
|
274
|
+
s2 = Supplier.create(name: 'sup. two', custom_attributes: { a: 1 })
|
272
275
|
|
273
276
|
def s2.foo
|
274
277
|
''
|
@@ -278,7 +281,7 @@ describe 'ActiveRecordSource' do
|
|
278
281
|
|
279
282
|
# methods need Columns as well
|
280
283
|
def b.serializable_options
|
281
|
-
{:
|
284
|
+
{ only: %i[name custom_attributes], methods: [:foo] }
|
282
285
|
end
|
283
286
|
|
284
287
|
expected = <<~TABLE
|
@@ -291,12 +294,12 @@ describe 'ActiveRecordSource' do
|
|
291
294
|
+----------+------+-------------------+
|
292
295
|
TABLE
|
293
296
|
|
294
|
-
b.
|
297
|
+
b.to_s.should == expected
|
295
298
|
end
|
296
299
|
|
297
300
|
it 'supports one to many association' do
|
298
301
|
p = Parent.create(name: 'parent')
|
299
|
-
|
302
|
+
Child.create(name: 'child', parent: p)
|
300
303
|
|
301
304
|
b = [p].to_table
|
302
305
|
|
@@ -310,32 +313,32 @@ describe 'ActiveRecordSource' do
|
|
310
313
|
TABLE
|
311
314
|
|
312
315
|
def b.serializable_options
|
313
|
-
{:
|
316
|
+
{ include: { children: { only: [:name] } } }
|
314
317
|
end
|
315
318
|
|
316
|
-
b.
|
319
|
+
b.to_s.should == expected
|
317
320
|
end
|
318
321
|
|
319
322
|
def format_ids(ary)
|
320
|
-
ary.map {|value| " #{value.to_s.ljust(3)}" }
|
323
|
+
ary.map { |value| " #{value.to_s.ljust(3)}" }
|
321
324
|
end
|
322
325
|
|
323
326
|
describe 'fold un-sourced attributes into source hash' do
|
324
327
|
let(:obj) { Object.new.extend Tablesmith::ActiveRecordSource }
|
325
328
|
|
326
329
|
it 'should handle simple hash' do
|
327
|
-
obj.fold_un_sourced_attributes_into_source_hash(:foo,
|
330
|
+
obj.fold_un_sourced_attributes_into_source_hash(:foo, a: 1, b: 2).should == { foo: { a: 1, b: 2 } }
|
328
331
|
end
|
329
332
|
|
330
333
|
it 'should handle nested hashes' do
|
331
|
-
before = {'name' => 'chris', account: {'name' => 'account_name'}}
|
332
|
-
expected = {foo: {'name' => 'chris'}, account: {'name' => 'account_name'}}
|
334
|
+
before = { 'name' => 'chris', account: { 'name' => 'account_name' } }
|
335
|
+
expected = { foo: { 'name' => 'chris' }, account: { 'name' => 'account_name' } }
|
333
336
|
obj.fold_un_sourced_attributes_into_source_hash(:foo, before).should == expected
|
334
337
|
end
|
335
338
|
|
336
339
|
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' =>
|
340
|
+
before = { 'name' => 'chris', account: { 'id' => { 'name' => 'account_name', 'number' => 123_456 } } }
|
341
|
+
expected = { foo: { 'name' => 'chris' }, account: { 'id' => { 'name' => 'account_name', 'number' => 123_456 } } }
|
339
342
|
obj.fold_un_sourced_attributes_into_source_hash(:foo, before).should == expected
|
340
343
|
end
|
341
344
|
end
|
@@ -344,17 +347,18 @@ describe 'ActiveRecordSource' do
|
|
344
347
|
let(:obj) { Object.new.extend Tablesmith::ActiveRecordSource }
|
345
348
|
|
346
349
|
it 'should flatten inner hash' do
|
347
|
-
before = {foo: {'name' => 'chris'}, account: {'name' => 'account_name'}}
|
348
|
-
expected = {'foo.name' => 'chris', 'account.name' => 'account_name'}
|
350
|
+
before = { foo: { 'name' => 'chris' }, account: { 'name' => 'account_name' } }
|
351
|
+
expected = { 'foo.name' => 'chris', 'account.name' => 'account_name' }
|
349
352
|
|
350
353
|
obj.flatten_inner_hashes(before).should == expected
|
351
354
|
end
|
352
355
|
|
353
356
|
it 'should to_s deep nested hashes' do
|
354
|
-
before = {foo: {'name' => 'chris'}, account: {'id' => {'name' => 'account_name', 'number' =>
|
355
|
-
expected = {'foo.name' => 'chris',
|
357
|
+
before = { foo: { 'name' => 'chris' }, account: { 'id' => { 'name' => 'account_name', 'number' => 123_456 } } }
|
358
|
+
expected = { 'foo.name' => 'chris', 'account.id' => '{"name"=>"account_name", "number"=>123456}' }
|
356
359
|
|
357
360
|
obj.flatten_inner_hashes(before).should == expected
|
358
361
|
end
|
359
362
|
end
|
360
363
|
end
|
364
|
+
# rubocop:enable Rails/SkipsModelValidations
|