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.
@@ -0,0 +1,164 @@
1
+ require 'object_table'
2
+
3
+ #
4
+ # spec: kind of proof of concept on how to extend a table
5
+ # and any children views, groups etc.
6
+ #
7
+ # the idea is that we can make a mixin and make it available
8
+ # to the table and also whenever we filter (#where), group (#group)
9
+ # or #clone
10
+ #
11
+ describe 'Subclassing ObjectTable and friends' do
12
+
13
+ class MyTable < ObjectTable
14
+ module Mixin
15
+ Table = MyTable
16
+
17
+ def a_plus_b
18
+ a + b
19
+ end
20
+ end
21
+
22
+ include Mixin
23
+
24
+ class StaticView < StaticView; include Mixin; end
25
+ class View < View; include Mixin; end
26
+ class Group < Group; include Mixin; end
27
+ end
28
+
29
+ let(:table){ MyTable.new(a: 0...100, b: 100.times.map{rand}) }
30
+
31
+ subject{ table }
32
+
33
+ it 'should have the mixin method available' do
34
+ expect(subject.a_plus_b).to eq (subject.a + subject.b)
35
+ end
36
+
37
+ describe '#clone' do
38
+ it 'should be an instance of the table subclass' do
39
+ expect(table.clone).to be_a MyTable
40
+ end
41
+ end
42
+
43
+ describe '#apply' do
44
+ context 'with a block returning a grid' do
45
+ subject{ table.apply{ ObjectTable::BasicGrid[col1: [4, 5, 6]] } }
46
+
47
+ it 'should coerce to the table subclass' do
48
+ expect(subject).to be_a MyTable
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#where' do
54
+ subject{ table.where{a > 50} }
55
+
56
+ it 'should create view extending the mixin' do
57
+ expect(subject).to be_a ObjectTable::View
58
+ expect(subject).to be_a MyTable::Mixin
59
+ end
60
+
61
+ it 'should have the mixin method available' do
62
+ expect(subject.a_plus_b).to eq (subject.a + subject.b)
63
+ end
64
+
65
+ describe '#clone' do
66
+ it 'should be an instance of the subclass' do
67
+ expect(subject.clone).to be_a MyTable
68
+ end
69
+ end
70
+
71
+ describe '#apply' do
72
+ subject{ table.where{a > 50}.apply{self} }
73
+
74
+ it 'should create static view extending the mixin' do
75
+ expect(subject).to be_a ObjectTable::StaticView
76
+ expect(subject).to be_a MyTable::Mixin
77
+ end
78
+
79
+ describe '#clone' do
80
+ it 'should be an instance of the table subclass' do
81
+ expect(subject.clone).to be_a MyTable
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ describe '#group_by' do
89
+ subject{ table.group_by{{gt_50: a > 50}} }
90
+
91
+ describe '#each' do
92
+ let(:groups) do
93
+ _groups = []
94
+ subject.each{ _groups << self }
95
+ _groups
96
+ end
97
+
98
+ it 'should give groups extending the Mixin' do
99
+ groups.each do |g|
100
+ expect(g).to be_a ObjectTable::Group
101
+ expect(g).to be_a MyTable::Mixin
102
+ end
103
+ end
104
+
105
+ it 'should have the mixin method available to the groups' do
106
+ groups.each do |g|
107
+ expect(g.a_plus_b).to eql (g.a + g.b)
108
+ end
109
+ end
110
+
111
+ describe '#clone' do
112
+ it 'should be an instance of the table subclass' do
113
+ groups.each do |g|
114
+ expect(g.clone).to be_a MyTable
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ describe '#apply' do
121
+ let(:groups) do
122
+ _groups = []
123
+ subject.apply{ _groups << self; nil }
124
+ _groups
125
+ end
126
+
127
+ it 'should give groups extending the mixin' do
128
+ groups.each do |g|
129
+ expect(g).to be_a ObjectTable::Group
130
+ expect(g).to be_a MyTable::Mixin
131
+ end
132
+ end
133
+
134
+ it 'should have the mixin method available to the groups' do
135
+ groups.each do |g|
136
+ expect(g.a_plus_b).to eql (g.a + g.b)
137
+ end
138
+ end
139
+
140
+ it 'should aggregate into a subclassed table' do
141
+ expect(subject.apply{nil}).to be_a MyTable
142
+ end
143
+
144
+ describe '#clone' do
145
+ it 'should be an instance of the table subclass' do
146
+ groups.each do |g|
147
+ expect(g.clone).to be_a MyTable
148
+ end
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
156
+ describe '#sort_by' do
157
+ let(:sorted){ table.sort_by(table.b) }
158
+
159
+ it 'should return an instance of the table subclass' do
160
+ expect(sorted).to be_a MyTable
161
+ end
162
+ end
163
+
164
+ end
@@ -9,13 +9,15 @@ RSpec.shared_examples 'an object table' do |cls|
9
9
  if @cls == ObjectTable
10
10
  table
11
11
 
12
- elsif @cls == ObjectTable::TempView
12
+ # for views, basically add one row to the parent and mask the view
13
+ # so that it only includes the original rows
14
+ elsif @cls == ObjectTable::View
13
15
  table.stack! ObjectTable::BasicGrid[table.columns.map{|k, v| [k, v.max]}]
14
16
  column = table.colnames.first
15
17
  table[column][-1] += 1
16
18
  table.where{table[column] < table[column][-1]}
17
19
 
18
- elsif @cls == ObjectTable::View
20
+ elsif @cls == ObjectTable::StaticView
19
21
  table.stack! ObjectTable::BasicGrid[table.columns.map{|k, v| [k, v.max]}]
20
22
  column = table.colnames.first
21
23
  table[column][-1] += 1
@@ -159,6 +161,14 @@ EOS
159
161
  expect(table.nrows).to eql 0
160
162
  end
161
163
  end
164
+
165
+ context 'on a table with empty columns' do
166
+ let(:table){ ObjectTable.new(a: []) }
167
+
168
+ it 'should return 0' do
169
+ expect(table.nrows).to eql 0
170
+ end
171
+ end
162
172
  end
163
173
 
164
174
  describe 'column methods' do
@@ -230,24 +240,52 @@ EOS
230
240
  describe '#apply' do
231
241
  let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
232
242
 
233
- it 'should evaluate in the context of the table' do
243
+ it 'should return the results of the block' do
234
244
  expect(subject.apply{ col1 }).to eql subject.col1
235
- expect(subject.apply{ col2.sum }).to eql subject.col2.sum
236
245
  end
237
246
 
238
247
  context 'with a block returning a grid' do
239
- subject{ table.apply{ ObjectTable::BasicGrid[col1: [4, 5, 6]] } }
248
+ let(:result) { subject.apply{ ObjectTable::BasicGrid[col1: [4, 5, 6]] } }
240
249
 
241
250
  it 'should coerce to a table' do
242
- expect(subject).to be_a ObjectTable
251
+ expect(result).to be_a ObjectTable
243
252
  end
244
253
  end
245
254
 
246
255
  it 'should have access to a BasicGrid shortcut' do
247
- result = table.apply{ @R[value: col1 + 5] }
256
+ result = subject.apply{ @R[value: col1 + 5] }
248
257
  expect(result).to be_a ObjectTable
249
- expect(result.value).to eql (table.col1 + 5)
258
+ expect(result.value).to eql (subject.col1 + 5)
250
259
  end
260
+
261
+ context 'when the block takes an argument' do
262
+ it 'should not evaluate in the context of the table' do
263
+ rspec_context = self
264
+
265
+ subject.apply do |tbl|
266
+ receiver = eval('self', binding)
267
+ expect(receiver).to_not be subject
268
+ expect(receiver).to be rspec_context
269
+ end
270
+ end
271
+
272
+ it 'should pass the table into the block' do
273
+ subject.apply do |tbl|
274
+ expect(tbl).to eq subject
275
+ end
276
+ end
277
+ end
278
+
279
+ context 'when the block takes no arguments' do
280
+ it 'should call the block in the context of the table' do
281
+ _ = self
282
+ subject.apply do
283
+ receiver = eval('self', binding)
284
+ _.expect(receiver).to _.eq _.subject
285
+ end
286
+ end
287
+ end
288
+
251
289
  end
252
290
 
253
291
  describe '#where' do
@@ -256,15 +294,15 @@ EOS
256
294
  let(:filtered){ subject.where &block }
257
295
 
258
296
  it 'should return a temp view' do
259
- expect(filtered).to be_a ObjectTable::TempView
297
+ expect(filtered).to be_a ObjectTable::View
260
298
  expect(filtered.instance_eval('@filter')).to eql block
261
299
  end
262
300
  end
263
301
 
264
- describe '#group' do
302
+ describe '#group_by' do
265
303
  let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
266
304
  let(:block){ Proc.new{col1 > 1} }
267
- let(:grouped){ subject.group &block }
305
+ let(:grouped){ subject.group_by &block }
268
306
 
269
307
  it 'should return groups' do
270
308
  expect(grouped).to be_a ObjectTable::TempGrouped
@@ -1,100 +1,62 @@
1
1
  require 'object_table'
2
- require 'object_table/temp_view'
3
2
 
4
- require 'support/object_table_example'
3
+ RSpec.shared_examples 'a table view' do |cls|
4
+ before do
5
+ @cls = cls
6
+ end
5
7
 
6
- describe ObjectTable::TempView do
7
- it_behaves_like 'an object table', ObjectTable::TempView
8
+ def _make_relevant_view(table, block)
9
+ if @cls == ObjectTable::View
10
+ @cls.new(table, &block)
8
11
 
9
- context 'with changes to the parent' do
10
- let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
11
- subject{ ObjectTable::TempView.new(table){ col1 > 2 } }
12
+ elsif @cls == ObjectTable::StaticView
13
+ indices = table.apply(&block).where
14
+ @cls.new(table, indices)
12
15
 
13
- it 'should mirror changes to the parent' do
14
- expect(subject).to eql ObjectTable.new(col1: 3, col2: 5)
15
- table[:col1] = [5, 6, 7]
16
- expect(subject).to eql ObjectTable.new(col1: [5, 6, 7], col2: 5)
16
+ else
17
+ nil
17
18
  end
18
19
  end
19
20
 
20
- context 'with nested views' do
21
- let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
22
- let(:view1){ table.where{col1 > 1} }
23
- let(:view2){ view1.where{col1 < 3} }
24
-
25
- it 'should add columns correctly' do
26
- view2[:col3] = 5
27
- expect(view2.col3.to_a).to eql [5]
28
- expect(view1.col3.to_a).to eql [5, nil]
29
- expect(table.col3.to_a).to eql [nil, 5, nil]
30
- end
31
- end
21
+ subject{ _make_relevant_view(table, block) }
32
22
 
33
- describe '#apply' do
23
+ describe '#columns' do
34
24
  let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
35
- let(:block){ Proc.new{col1 + 100} }
25
+ let(:block){ Proc.new{col1 > 2} }
36
26
 
37
- subject{ ObjectTable::TempView.new(table){ col1 > 2 } }
38
-
39
- it 'should create a view' do
40
- view = spy('view')
41
- expect(ObjectTable::View).to receive(:new).with(table, (table.col1 > 2).where){ view }
42
- subject.apply(&block)
27
+ it 'should mask the columns of the parent table' do
28
+ mask = table.col1 > 2
29
+ table.columns.each do |k, v|
30
+ expect(subject.columns[k].to_a).to eql v[mask].to_a
31
+ end
43
32
  end
44
33
 
45
- it 'should call #apply on the view' do
46
- view = spy('view')
47
- expect(ObjectTable::View).to receive(:new){ view }
48
- expect(view).to receive(:apply) do |&b|
49
- expect(b).to be block
34
+ it 'should make masked columns' do
35
+ subject.columns.each do |k, v|
36
+ expect(v).to be_a ObjectTable::MaskedColumn
50
37
  end
51
-
52
- subject.apply(&block)
53
38
  end
54
- end
55
39
 
56
- describe '#group' do
57
- let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
58
- let(:block){ Proc.new{col1 + 100} }
59
-
60
- subject{ ObjectTable::TempView.new(table){ col1 > 2 } }
40
+ context 'with a matrixy narray in a column' do
41
+ let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: NArray[[1,2,3], [4, 5, 6], [7, 8, 9]] ) }
61
42
 
62
- it 'should create a view' do
63
- view = spy('view')
64
- expect(ObjectTable::View).to receive(:new).with(table, (table.col1 > 2).where){ view }
65
- subject.group(&block)
66
- end
67
-
68
- it 'should call #group on the view' do
69
- view = spy('view')
70
- expect(ObjectTable::View).to receive(:new){ view }
71
- expect(view).to receive(:group) do |&b|
72
- expect(b).to be block
43
+ it 'should mask the matrixy narray too' do
44
+ indices = (table.col1 > 2).where
45
+ expect(subject.columns[:col2]).to eq table.col2[nil, indices]
73
46
  end
74
47
 
75
- subject.group(&block)
76
48
  end
77
49
  end
78
50
 
79
- describe '#columns' do
80
- let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
81
-
82
- subject{ ObjectTable::TempView.new(table){ col1 > 2 } }
83
-
84
- it 'should mask the columns of the parent table' do
85
- mask = table.col1 > 2
86
- table.columns.each do |k, v|
87
- expect(subject.columns[k].to_a).to eql v[mask].to_a
88
- end
89
- end
90
- end
91
51
 
92
52
  describe '#set_column' do
93
53
  let(:table) { ObjectTable.new(col1: [0, 1, 2, 3], col2: 5) }
94
- let(:view) { ObjectTable::TempView.new(table){ col1 > 0 } }
54
+ let(:block) { Proc.new{col1 > 0} }
55
+ let(:view) { _make_relevant_view(table, block) }
95
56
 
96
- let(:column){ :col2 }
57
+ let(:column){ :col1 }
97
58
  let(:value) { [10, 20, 30] }
59
+
98
60
  let(:args) { [] }
99
61
 
100
62
  subject{ view.set_column(column, value, *args) }
@@ -107,7 +69,7 @@ describe ObjectTable::TempView do
107
69
 
108
70
  it 'should not modify anything outside the view' do
109
71
  subject
110
- expect(table.columns[column].to_a).to eql [5] + value
72
+ expect(table.columns[column].to_a).to eql [0] + value
111
73
  end
112
74
 
113
75
  end
@@ -120,17 +82,8 @@ describe ObjectTable::TempView do
120
82
  end
121
83
  end
122
84
 
123
- context 'with a range' do
124
- let(:value){ 0...3 }
125
- it 'should assign the range values' do
126
- subject
127
- expect(view.columns[column].to_a).to eql value.to_a
128
- end
129
- end
130
-
131
85
  context 'with the wrong length' do
132
86
  let(:value) { [1, 2] }
133
-
134
87
  it 'should fail' do
135
88
  expect{subject}.to raise_error
136
89
  end
@@ -150,7 +103,7 @@ describe ObjectTable::TempView do
150
103
  expect(table.columns).to include column
151
104
  end
152
105
 
153
- it 'should fill values outside the view with a default value' do
106
+ it 'should fill values outside the view with a default' do
154
107
  subject
155
108
  default = NArray.new(table.columns[column].typecode, 1)[0]
156
109
  expect(table.columns[column].to_a).to eql [default] + value
@@ -165,13 +118,13 @@ describe ObjectTable::TempView do
165
118
  end
166
119
  end
167
120
 
168
- context 'when failed to add column' do
121
+ context 'when failed to add column' do
169
122
  let(:value){ NArray[1, 2, 3] }
170
123
 
171
124
  it 'should not have that column' do
172
125
  expect(view).to receive(:add_column).with(column, value.typecode) do
173
- view.columns[column] = ObjectTable::Column.make([0] * 10)
174
126
  table.columns[column] = ObjectTable::Column.make([0] * 10)
127
+ view.columns[column] = ObjectTable::Column.make([0] * 10)
175
128
  end
176
129
 
177
130
  # the assignment is going to chuck an error
@@ -180,16 +133,27 @@ describe ObjectTable::TempView do
180
133
  expect(table.columns).to_not include column
181
134
  end
182
135
  end
136
+ end
183
137
 
138
+ context 'with an empty view' do
139
+ let(:block) { Proc.new{col1 < 0} }
140
+
141
+ context 'adding an empty column' do
142
+ let(:value) { [] }
143
+ it 'should add the column' do
144
+ subject
145
+ expect(view.columns[column].to_a).to eq value
146
+ end
147
+ end
184
148
  end
185
149
 
186
150
  end
187
151
 
188
152
  describe '#pop_column' do
189
153
  let(:table){ ObjectTable.new(col1: [1, 2, 3], col2: 5) }
190
-
191
- let(:view) { ObjectTable::TempView.new(table){ col1 > 2 } }
192
- let(:name) { :col2 }
154
+ let(:block){ Proc.new{col1 > 2} }
155
+ let(:view) { _make_relevant_view(table, block) }
156
+ let(:name) { :col2 }
193
157
 
194
158
  subject{ view.pop_column(name) }
195
159
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: object_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cheney Lin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-08 00:00:00.000000000 Z
11
+ date: 2015-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: narray
@@ -81,11 +81,12 @@ files:
81
81
  - lib/object_table.rb
82
82
  - lib/object_table/basic_grid.rb
83
83
  - lib/object_table/column.rb
84
+ - lib/object_table/group.rb
84
85
  - lib/object_table/grouped.rb
85
86
  - lib/object_table/masked_column.rb
87
+ - lib/object_table/static_view.rb
86
88
  - lib/object_table/table_methods.rb
87
89
  - lib/object_table/temp_grouped.rb
88
- - lib/object_table/temp_view.rb
89
90
  - lib/object_table/version.rb
90
91
  - lib/object_table/view.rb
91
92
  - lib/object_table/view_methods.rb
@@ -94,11 +95,13 @@ files:
94
95
  - spec/object_table/column_spec.rb
95
96
  - spec/object_table/grouped_spec.rb
96
97
  - spec/object_table/masked_column_spec.rb
98
+ - spec/object_table/static_view_spec.rb
97
99
  - spec/object_table/temp_grouped_spec.rb
98
- - spec/object_table/temp_view_spec.rb
99
100
  - spec/object_table/view_spec.rb
100
101
  - spec/object_table_spec.rb
102
+ - spec/subclassing_spec.rb
101
103
  - spec/support/object_table_example.rb
104
+ - spec/support/view_example.rb
102
105
  homepage: https://github.com/lincheney/ruby-object-table
103
106
  licenses:
104
107
  - MIT
@@ -128,8 +131,10 @@ test_files:
128
131
  - spec/object_table/column_spec.rb
129
132
  - spec/object_table/grouped_spec.rb
130
133
  - spec/object_table/masked_column_spec.rb
134
+ - spec/object_table/static_view_spec.rb
131
135
  - spec/object_table/temp_grouped_spec.rb
132
- - spec/object_table/temp_view_spec.rb
133
136
  - spec/object_table/view_spec.rb
134
137
  - spec/object_table_spec.rb
138
+ - spec/subclassing_spec.rb
135
139
  - spec/support/object_table_example.rb
140
+ - spec/support/view_example.rb
@@ -1,63 +0,0 @@
1
- require 'forwardable'
2
- require_relative 'view_methods'
3
- require_relative 'masked_column'
4
- require_relative 'view'
5
-
6
- class ObjectTable::TempView
7
- include ObjectTable::ViewMethods
8
-
9
- extend Forwardable
10
- def_delegators :make_view, :group, :apply
11
-
12
- def initialize(parent, &block)
13
- super()
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
- ObjectTable::View.new @parent, indices
28
- end
29
-
30
- def clone
31
- cols = ObjectTable::BasicGrid[@parent.columns.map{|k, v| [k, v[indices]]}]
32
- ObjectTable.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)
46
- @indices = indices
47
- value = block.call()
48
- @indices = nil
49
- value
50
- end
51
-
52
- def columns
53
- @columns or super
54
- end
55
-
56
- def cache_columns(&block)
57
- @columns = columns
58
- value = block.call()
59
- @columns = nil
60
- value
61
- end
62
-
63
- end