tablesmith 0.4.0 → 0.6.2
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/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
|