object_table 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +249 -56
- data/lib/object_table/basic_grid.rb +1 -1
- data/lib/object_table/group.rb +10 -0
- data/lib/object_table/grouped.rb +8 -13
- data/lib/object_table/masked_column.rb +4 -2
- data/lib/object_table/static_view.rb +24 -0
- data/lib/object_table/table_methods.rb +21 -9
- data/lib/object_table/temp_grouped.rb +1 -1
- data/lib/object_table/version.rb +1 -1
- data/lib/object_table/view.rb +46 -7
- data/lib/object_table.rb +8 -7
- data/spec/object_table/basic_grid_spec.rb +20 -0
- data/spec/object_table/grouped_spec.rb +52 -7
- data/spec/object_table/static_view_spec.rb +10 -0
- data/spec/object_table/temp_grouped_spec.rb +38 -0
- data/spec/object_table/view_spec.rb +61 -106
- data/spec/object_table_spec.rb +29 -0
- data/spec/subclassing_spec.rb +164 -0
- data/spec/support/object_table_example.rb +49 -11
- data/spec/{object_table/temp_view_spec.rb → support/view_example.rb} +50 -86
- metadata +10 -5
- data/lib/object_table/temp_view.rb +0 -63
data/lib/object_table/view.rb
CHANGED
@@ -1,24 +1,63 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require_relative 'view_methods'
|
2
|
-
require_relative 'basic_grid'
|
3
3
|
require_relative 'masked_column'
|
4
4
|
|
5
5
|
class ObjectTable::View
|
6
|
+
Table = ObjectTable
|
6
7
|
include ObjectTable::ViewMethods
|
7
|
-
attr_reader :indices
|
8
8
|
|
9
|
-
|
9
|
+
extend Forwardable
|
10
|
+
def_delegators :make_view, :apply
|
11
|
+
|
12
|
+
def initialize(parent, &block)
|
10
13
|
super()
|
11
14
|
@parent = parent
|
15
|
+
@filter = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def_delegators :@parent, :has_column?
|
19
|
+
|
20
|
+
def get_column(name)
|
21
|
+
col = @parent.get_column(name)
|
22
|
+
ObjectTable::MaskedColumn.mask(col, indices) if col
|
23
|
+
end
|
24
|
+
alias_method :[], :get_column
|
25
|
+
|
26
|
+
def make_view
|
27
|
+
self.class::Table::StaticView.new @parent, indices
|
28
|
+
end
|
29
|
+
|
30
|
+
def clone
|
31
|
+
cols = ObjectTable::BasicGrid[@parent.columns.map{|k, v| [k, v[indices]]}]
|
32
|
+
self.class::Table.new(cols)
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect(*args)
|
36
|
+
cache_columns{ super }
|
37
|
+
rescue NoMethodError => e
|
38
|
+
raise Exception.new(e)
|
39
|
+
end
|
40
|
+
|
41
|
+
def indices
|
42
|
+
@indices or NArray.int(@parent.nrows).indgen![@parent.apply &@filter]
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache_indices(&block)
|
12
46
|
@indices = indices
|
13
|
-
|
47
|
+
value = block.call()
|
48
|
+
@indices = nil
|
49
|
+
value
|
14
50
|
end
|
15
51
|
|
16
52
|
def columns
|
17
|
-
@columns
|
53
|
+
@columns or super
|
18
54
|
end
|
19
55
|
|
20
|
-
def
|
21
|
-
@columns
|
56
|
+
def cache_columns(&block)
|
57
|
+
@columns = columns
|
58
|
+
value = block.call()
|
59
|
+
@columns = nil
|
60
|
+
value
|
22
61
|
end
|
23
62
|
|
24
63
|
end
|
data/lib/object_table.rb
CHANGED
@@ -2,7 +2,7 @@ require_relative "object_table/version"
|
|
2
2
|
require_relative "object_table/basic_grid"
|
3
3
|
require_relative "object_table/table_methods"
|
4
4
|
require_relative "object_table/view"
|
5
|
-
require_relative "object_table/
|
5
|
+
require_relative "object_table/static_view"
|
6
6
|
require_relative "object_table/column"
|
7
7
|
require_relative "object_table/grouped"
|
8
8
|
require_relative "object_table/temp_grouped"
|
@@ -22,7 +22,7 @@ class ObjectTable
|
|
22
22
|
@columns = columns
|
23
23
|
|
24
24
|
@columns.each do |k, v|
|
25
|
-
@columns[k] = Column.make(v)
|
25
|
+
@columns[k] = ObjectTable::Column.make(v)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -38,12 +38,13 @@ class ObjectTable
|
|
38
38
|
case x
|
39
39
|
when ObjectTable::TableMethods
|
40
40
|
x = x.columns
|
41
|
-
when BasicGrid
|
41
|
+
when ObjectTable::BasicGrid
|
42
42
|
x._ensure_uniform_columns!
|
43
43
|
end
|
44
44
|
|
45
|
-
raise "Don't know how to append a #{x.class}" unless x.is_a?(BasicGrid)
|
46
|
-
|
45
|
+
raise "Don't know how to append a #{x.class}" unless x.is_a?(ObjectTable::BasicGrid)
|
46
|
+
next if x.empty?
|
47
|
+
raise 'Mismatch in column names' unless (colnames | x.keys) == (colnames & x.keys)
|
47
48
|
|
48
49
|
x.each do |k, v|
|
49
50
|
v = v.to_a if v.is_a? NArray
|
@@ -54,7 +55,7 @@ class ObjectTable
|
|
54
55
|
return self if new_values.empty?
|
55
56
|
|
56
57
|
new_values.each do |k, v|
|
57
|
-
@columns[k] = Column.make(@columns[k].to_a + v)
|
58
|
+
@columns[k] = ObjectTable::Column.make(@columns[k].to_a + v)
|
58
59
|
end
|
59
60
|
self
|
60
61
|
end
|
@@ -64,7 +65,7 @@ class ObjectTable
|
|
64
65
|
base = values.shift
|
65
66
|
|
66
67
|
case base
|
67
|
-
when BasicGrid
|
68
|
+
when ObjectTable::BasicGrid
|
68
69
|
base = self.new(base.clone)
|
69
70
|
when ObjectTable, ObjectTable::View
|
70
71
|
base = base.clone
|
@@ -75,6 +75,26 @@ describe ObjectTable::BasicGrid do
|
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
78
|
+
context 'with empty narrays' do
|
79
|
+
|
80
|
+
context 'with all other columns empty' do
|
81
|
+
let(:columns) { {col1: [], col2: NArray[]} }
|
82
|
+
|
83
|
+
it 'should succeed' do
|
84
|
+
expect{subject}.to_not raise_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
context 'with other non-empty columns' do
|
90
|
+
let(:columns) { {col1: [], col2: NArray[]} }
|
91
|
+
|
92
|
+
it 'should fail' do
|
93
|
+
expect{subject}.to_not raise_error
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
78
98
|
end
|
79
99
|
|
80
100
|
end
|
@@ -35,18 +35,39 @@ describe ObjectTable::Grouped do
|
|
35
35
|
let(:even_group){ table.where{ (col1 % 2).eq(0) } }
|
36
36
|
let(:odd_group) { table.where{ (col1 % 2).eq(1) } }
|
37
37
|
|
38
|
+
context 'when the block takes an argument' do
|
39
|
+
it 'should not evaluate in the context of the group' do
|
40
|
+
rspec_context = self
|
41
|
+
|
42
|
+
grouped.each do |group|
|
43
|
+
receiver = eval('self', binding)
|
44
|
+
expect(receiver).to_not be_a ObjectTable::Group
|
45
|
+
expect(receiver).to be rspec_context
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when the block takes no arguments' do
|
51
|
+
it 'should call the block in the context of the group' do
|
52
|
+
_ = self
|
53
|
+
grouped.each do
|
54
|
+
receiver = eval('self', binding)
|
55
|
+
_.expect(receiver).to _.be_a ObjectTable::Group
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
38
60
|
it 'should yield the groups' do
|
39
|
-
groups = []
|
61
|
+
groups = [even_group, odd_group]
|
40
62
|
grouped.each do |group|
|
41
|
-
groups
|
63
|
+
expect(groups).to include group
|
64
|
+
groups -= [group]
|
42
65
|
end
|
43
|
-
|
44
|
-
expect(groups).to match_array [even_group, odd_group]
|
45
66
|
end
|
46
67
|
|
47
68
|
it 'should give access to the keys' do
|
48
69
|
keys = []
|
49
|
-
grouped.each do
|
70
|
+
grouped.each do
|
50
71
|
keys << @K
|
51
72
|
end
|
52
73
|
|
@@ -56,7 +77,7 @@ describe ObjectTable::Grouped do
|
|
56
77
|
it 'should give access to the correct key' do
|
57
78
|
keys = []
|
58
79
|
correct_keys = []
|
59
|
-
grouped.each do
|
80
|
+
grouped.each do
|
60
81
|
keys << [@K[:parity]]
|
61
82
|
correct_keys << (self.col1 % 2).uniq.to_a
|
62
83
|
end
|
@@ -95,7 +116,7 @@ describe ObjectTable::Grouped do
|
|
95
116
|
end
|
96
117
|
|
97
118
|
context 'with results that are grids' do
|
98
|
-
subject{ grouped.apply{
|
119
|
+
subject{ grouped.apply{ @R[sum: col1.sum, mean: col2.mean] } }
|
99
120
|
|
100
121
|
it 'should return a table with the group keys' do
|
101
122
|
expect(subject).to be_a ObjectTable
|
@@ -110,6 +131,30 @@ describe ObjectTable::Grouped do
|
|
110
131
|
)
|
111
132
|
end
|
112
133
|
end
|
134
|
+
|
135
|
+
context 'when the block takes an argument' do
|
136
|
+
it 'should not evaluate in the context of the group' do
|
137
|
+
rspec_context = self
|
138
|
+
|
139
|
+
grouped.apply do |group|
|
140
|
+
receiver = eval('self', binding)
|
141
|
+
expect(receiver).to_not be_a ObjectTable::Group
|
142
|
+
expect(receiver).to be rspec_context
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'when the block takes no arguments' do
|
149
|
+
it 'should call the block in the context of the group' do
|
150
|
+
_ = self
|
151
|
+
grouped.apply do
|
152
|
+
receiver = eval('self', binding)
|
153
|
+
_.expect(receiver).to _.be_a ObjectTable::Group
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
113
158
|
end
|
114
159
|
|
115
160
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'object_table'
|
2
|
+
require 'object_table/static_view'
|
3
|
+
|
4
|
+
require 'support/object_table_example'
|
5
|
+
require 'support/view_example'
|
6
|
+
|
7
|
+
describe ObjectTable::StaticView do
|
8
|
+
it_behaves_like 'an object table', ObjectTable::StaticView
|
9
|
+
it_behaves_like 'a table view', ObjectTable::StaticView
|
10
|
+
end
|
@@ -9,6 +9,44 @@ describe ObjectTable::TempGrouped do
|
|
9
9
|
let(:even){ (table.col1 % 2).eq(0).where }
|
10
10
|
let(:odd) { (table.col1 % 2).eq(1).where }
|
11
11
|
|
12
|
+
describe '#initialize' do
|
13
|
+
|
14
|
+
context 'when the block takes an argument' do
|
15
|
+
it 'should not evaluate in the context of the table' do
|
16
|
+
rspec_context = self
|
17
|
+
|
18
|
+
grouped = ObjectTable::TempGrouped.new(table) do |tbl|
|
19
|
+
receiver = eval('self', binding)
|
20
|
+
expect(receiver).to_not be table
|
21
|
+
expect(receiver).to be rspec_context
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
grouped._groups # call _groups to make it call the block
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should pass the table into the block' do
|
28
|
+
grouped = ObjectTable::TempGrouped.new(table) do |tbl|
|
29
|
+
expect(tbl).to be table
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
grouped._groups # call _groups to make it call the block
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when the block takes no arguments' do
|
37
|
+
it 'should call the block in the context of the table' do
|
38
|
+
_ = self
|
39
|
+
grouped = ObjectTable::TempGrouped.new(table) do
|
40
|
+
receiver = eval('self', binding)
|
41
|
+
_.expect(receiver).to _.be _.table
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
grouped._groups # call _groups to make it call the block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
12
50
|
context 'with changes to the parent' do
|
13
51
|
subject{ grouped }
|
14
52
|
|
@@ -2,138 +2,93 @@ require 'object_table'
|
|
2
2
|
require 'object_table/view'
|
3
3
|
|
4
4
|
require 'support/object_table_example'
|
5
|
+
require 'support/view_example'
|
5
6
|
|
6
7
|
describe ObjectTable::View do
|
7
8
|
it_behaves_like 'an object table', ObjectTable::View
|
9
|
+
it_behaves_like 'a table view', ObjectTable::View
|
8
10
|
|
9
|
-
describe '#
|
10
|
-
let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
|
11
|
-
|
12
|
-
subject{ ObjectTable::View.new(table, (table.col1 > 2).where) }
|
13
|
-
|
14
|
-
it 'should mask the columns of the parent table' do
|
15
|
-
mask = table.col1 > 2
|
16
|
-
table.columns.each do |k, v|
|
17
|
-
expect(subject.columns[k].to_a).to eql v[mask].to_a
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should make masked columns' do
|
22
|
-
subject.columns.each do |k, v|
|
23
|
-
expect(v).to be_a ObjectTable::MaskedColumn
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
context 'with a matrix in a column' do
|
28
|
-
let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
|
29
|
-
end
|
30
|
-
end
|
11
|
+
describe '#initialize' do
|
12
|
+
let(:table) { ObjectTable.new(col1: [1, 2, 3], col2: 5) }
|
31
13
|
|
32
|
-
|
33
|
-
let(:table) { ObjectTable.new(col1: [0, 1, 2, 3], col2: 5) }
|
34
|
-
let(:view) { ObjectTable::View.new(table, (table.col1 > 0).where) }
|
14
|
+
context 'when the block takes an argument' do
|
35
15
|
|
36
|
-
|
37
|
-
|
16
|
+
it 'should not evaluate in the context of the table' do
|
17
|
+
rspec_context = self
|
38
18
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
subject
|
46
|
-
expect(view.columns[column].to_a).to eql value
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'should not modify anything outside the view' do
|
50
|
-
subject
|
51
|
-
expect(table.columns[column].to_a).to eql [0] + value
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
context 'with a scalar' do
|
57
|
-
let(:value){ 10 }
|
58
|
-
it 'should fill the column with that value' do
|
59
|
-
subject
|
60
|
-
expect(view.columns[column].to_a).to eql ([value] * view.nrows)
|
19
|
+
view = ObjectTable::View.new(table) do |tbl|
|
20
|
+
receiver = eval('self', binding)
|
21
|
+
expect(receiver).to_not be table
|
22
|
+
expect(receiver).to be rspec_context
|
23
|
+
end
|
24
|
+
view.columns # call columns to make it call the block
|
61
25
|
end
|
62
|
-
end
|
63
26
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
27
|
+
it 'should pass the table into the block' do
|
28
|
+
view = ObjectTable::View.new(table) do |tbl|
|
29
|
+
expect(tbl).to be table
|
30
|
+
end
|
31
|
+
view.columns # call columns to make it call the block
|
68
32
|
end
|
69
33
|
end
|
70
34
|
|
71
|
-
context '
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
expect(view.columns[column].to_a).to eql value
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'should affect the parent table' do
|
81
|
-
subject
|
82
|
-
expect(table.columns).to include column
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'should fill values outside the view with a default' do
|
86
|
-
subject
|
87
|
-
default = NArray.new(table.columns[column].typecode, 1)[0]
|
88
|
-
expect(table.columns[column].to_a).to eql [default] + value
|
89
|
-
end
|
90
|
-
|
91
|
-
context 'with an NArray' do
|
92
|
-
let(:value){ NArray.int(3, 4, view.nrows).random! }
|
93
|
-
|
94
|
-
it 'should use the narray parameters' do
|
95
|
-
subject
|
96
|
-
expect(view.columns[column].to_a).to eql value.to_a
|
35
|
+
context 'when the block takes no arguments' do
|
36
|
+
it 'should call the block in the context of the table' do
|
37
|
+
_ = self
|
38
|
+
view = ObjectTable::View.new(table) do
|
39
|
+
receiver = eval('self', binding)
|
40
|
+
_.expect(receiver).to _.be _.table
|
97
41
|
end
|
42
|
+
view.columns # call columns to make it call the block
|
98
43
|
end
|
44
|
+
end
|
99
45
|
|
100
|
-
|
101
|
-
let(:value){ NArray[1, 2, 3] }
|
46
|
+
end
|
102
47
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
view.columns[column] = ObjectTable::Column.make([0] * 10)
|
107
|
-
end
|
48
|
+
context 'with changes to the parent' do
|
49
|
+
let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
|
50
|
+
subject{ ObjectTable::View.new(table){ col1 > 2 } }
|
108
51
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
end
|
52
|
+
it 'should mirror changes to the parent' do
|
53
|
+
expect(subject).to eql ObjectTable.new(col1: 3, col2: 5)
|
54
|
+
table[:col1] = [5, 6, 7]
|
55
|
+
expect(subject).to eql ObjectTable.new(col1: [5, 6, 7], col2: 5)
|
115
56
|
end
|
116
|
-
|
117
57
|
end
|
118
58
|
|
119
|
-
|
59
|
+
context 'with nested views' do
|
120
60
|
let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
|
61
|
+
let(:view1){ table.where{col1 > 1} }
|
62
|
+
let(:view2){ view1.where{col1 < 3} }
|
63
|
+
|
64
|
+
it 'should add columns correctly' do
|
65
|
+
view2[:col3] = 5
|
66
|
+
expect(view2.col3.to_a).to eql [5]
|
67
|
+
expect(view1.col3.to_a).to eql [5, nil]
|
68
|
+
expect(table.col3.to_a).to eql [nil, 5, nil]
|
69
|
+
end
|
70
|
+
end
|
121
71
|
|
122
|
-
|
123
|
-
let(:
|
72
|
+
describe '#apply' do
|
73
|
+
let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
|
74
|
+
let(:block){ Proc.new{col1 + 100} }
|
124
75
|
|
125
|
-
subject{
|
76
|
+
subject{ ObjectTable::View.new(table){ col1 > 2 } }
|
126
77
|
|
127
|
-
it 'should
|
128
|
-
|
129
|
-
expect(
|
130
|
-
|
78
|
+
it 'should create a view' do
|
79
|
+
view = spy('view')
|
80
|
+
expect(ObjectTable::StaticView).to receive(:new).with(table, (table.col1 > 2).where){ view }
|
81
|
+
subject.apply(&block)
|
131
82
|
end
|
132
83
|
|
133
|
-
it 'should
|
134
|
-
|
135
|
-
expect(
|
136
|
-
expect(
|
84
|
+
it 'should call #apply on the view' do
|
85
|
+
view = spy('view')
|
86
|
+
expect(ObjectTable::StaticView).to receive(:new){ view }
|
87
|
+
expect(view).to receive(:apply) do |&b|
|
88
|
+
expect(b).to be block
|
89
|
+
end
|
90
|
+
|
91
|
+
subject.apply(&block)
|
137
92
|
end
|
138
93
|
end
|
139
94
|
|
data/spec/object_table_spec.rb
CHANGED
@@ -153,6 +153,18 @@ describe ObjectTable do
|
|
153
153
|
|
154
154
|
end
|
155
155
|
|
156
|
+
context 'with an empty table' do
|
157
|
+
let(:table) { ObjectTable.new }
|
158
|
+
let(:value) { [] }
|
159
|
+
|
160
|
+
context 'adding an empty column' do
|
161
|
+
it 'should add the column' do
|
162
|
+
subject
|
163
|
+
expect(table.columns[column].to_a).to eq value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
156
168
|
end
|
157
169
|
|
158
170
|
describe '#pop_column' do
|
@@ -214,6 +226,23 @@ describe ObjectTable do
|
|
214
226
|
expect{subject}.to raise_error
|
215
227
|
end
|
216
228
|
end
|
229
|
+
|
230
|
+
context 'with empty tables' do
|
231
|
+
let(:others) { [ ObjectTable.new(col1: [1, 2, 3], col2: 5), ObjectTable.new ] }
|
232
|
+
|
233
|
+
it 'should ignore empty tables' do
|
234
|
+
expect(subject).to eql others[0]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
context 'with empty grids' do
|
239
|
+
let(:others) { [ ObjectTable.new(col1: [1, 2, 3], col2: 5), ObjectTable::BasicGrid.new ] }
|
240
|
+
|
241
|
+
it 'should ignore empty grids' do
|
242
|
+
expect(subject).to eql others[0]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
217
246
|
end
|
218
247
|
|
219
248
|
describe '#sort_by!' do
|