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.
@@ -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 = ActiveSupport::OrderedHash.new
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
- self.map do |hash_row|
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
- $stderr.puts "#{e.message}: #{col_or_hash}" if @debug
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(:first_row_is_head => true)
63
+ table.to_table(first_row_is_head: true)
64
64
  end
65
65
  end
@@ -40,6 +40,7 @@ class HtmlFormatter
40
40
  def append_rows(rows, tag)
41
41
  rows.each do |row|
42
42
  next if row == :separator
43
+
43
44
  @lines << "#{indent}<tr>"
44
45
  row.map do |cell|
45
46
  value = cell_value(cell)
@@ -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
- self.map do |t|
9
- $stderr.print '.' if count.divmod(100)[1] == 0
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 self.empty?
41
+ return ['(empty)'].to_text_table if empty?
36
42
 
37
- rows = self.map { |item| convert_item_to_hash_row(item) }.compact
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
- when Hash
54
- cell[:value]
55
- else
56
- cell
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
- def columns
91
- @columns
92
- end
95
+ attr_reader :columns
93
96
 
94
97
  def create_headers(rows)
95
- top_row = rows.first
96
- column_names = top_row.first.is_a?(Array) ? top_row.map(&:first) : top_row
97
- grouped_headers(column_names) + [apply_column_aliases(column_names), :separator]
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
- {:value => value, :align => :center}
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
- "#{@alias || @name}"
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
- if b.first && b.first.is_a?(ActiveRecord::Base)
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
- if b.first && b.first.is_a?(Hash)
166
- b.extend Tablesmith::HashRowsSource
167
- end
171
+ b.extend Tablesmith::HashRowsSource if b.first&.is_a?(Hash)
168
172
 
169
- if b.first && b.first.is_a?(Array)
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Tablesmith
2
- VERSION = '0.4.1'.freeze
4
+ VERSION = '0.5.0'
3
5
  end
@@ -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.text_table.to_s.should == expected
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(:first_name => 'chris', :last_name => 'mo', :age => 43)
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.text_table.to_s.should == expected
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(:first_name => 'chrismo', :age => 43)
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
- {:only => [:first_name, :age], :methods => [:year_born]}
45
+ { only: %i[first_name age], methods: [:year_born] }
44
46
  end
45
47
 
46
- b.text_table.to_s.should == expected
48
+ b.to_s.should == expected
47
49
  end
48
50
 
49
51
  it 'auto reloads records' do
50
- p = Person.create(:first_name => 'chrismo', :age => 43)
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
- {:only => [:first_name, :age], :methods => [:year_born]}
64
+ { only: %i[first_name age], methods: [:year_born] }
63
65
  end
64
- b.text_table.to_s.should == expected
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.text_table.to_s.should == expected
78
+ b.to_s.should == expected
77
79
  end
78
80
 
79
81
  it 'handles column name partials' do
80
- p = Person.create(:first_name => 'chris', :last_name => 'mo', :age => 43)
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
- {:only => [:first, :last, :age]}
93
+ { only: %i[first last age] }
92
94
  end
93
95
 
94
- b.text_table.to_s.should == expected
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(:first_name => 'chris', :last_name => 'mo', :age => 43)
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
- {:only => [:f_name, :l_name, :age]}
111
+ { only: %i[f_name l_name age] }
110
112
  end
111
113
 
112
- b.text_table.to_s.should == expected
114
+ b.to_s.should == expected
113
115
  end
114
116
 
115
117
  it 'handles explicit column aliases' do
116
- p = Person.create(:first_name => 'chris', :last_name => 'mo', :age => 43)
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
- {:only => [:first_name, :last_name, :age]}
134
+ { only: %i[first_name last_name age] }
133
135
  end
134
136
 
135
- b.text_table.to_s.should == expected
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
- {:only => [:name], :include => {:account => {:only => [:name, :tax_identification_number]}}}
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.text_table.to_s.should == expected
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
- {:only => [:name], :include => {:account => {:only => [:name, :tax_id]}}}
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.text_table.to_s.should == expected
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.text_table.to_s.should == expected
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.text_table.to_s.should == expected
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.text_table.to_s.should == expected
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
- {:only => [:name], :include => {:account => {:only => [:name, :tax_id]}}}
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.text_table.to_s.should == expected
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
- {:only => [:name, :custom_attributes], :methods => [:foo]}
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.text_table.to_s.should == expected
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
- c = Child.create(name: 'child', parent: p)
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
- {:include => {:children => {:only => [:name]}}}
315
+ { include: { children: { only: [:name] } } }
314
316
  end
315
317
 
316
- b.text_table.to_s.should == expected
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, {a: 1, b: 2}).should == {foo: {a: 1, b: 2}}
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' => 123456}}}
338
- expected = {foo: {'name' => 'chris'}, account: {'id' => {'name' => 'account_name', 'number' => 123456}}}
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' => 123456}}}
355
- expected = {'foo.name' => 'chris', "account.id" => "{\"name\"=>\"account_name\", \"number\"=>123456}"}
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