chair 1.0.2 → 1.1.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/.travis.yml +1 -1
- data/Gemfile +3 -2
- data/README.md +18 -19
- data/{chairs.gemspec → chair.gemspec} +0 -0
- data/lib/chair/{table.rb → chair.rb} +125 -64
- data/lib/chair/row.rb +73 -27
- data/lib/chair/version.rb +2 -2
- data/lib/chair.rb +1 -1
- data/spec/row_spec.rb +79 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/table_search_spec.rb +28 -3
- data/spec/table_spec.rb +185 -36
- metadata +5 -4
- data/demo.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 415c4dfeb44887b370a98e38d6b9dd0b97b734e2
|
4
|
+
data.tar.gz: 3fb7024b049ee94c0534f1c0e79376992b07d899
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99d8558f1baa52dfbfb986bdbe5848f37ba8c5210052b71af04fb5d57f89bbab9e5684a19ffbca1bffe8c1cbf26f66d60942dd70c8372c884cb069611f440edd
|
7
|
+
data.tar.gz: 9952115d82340884b0ccb5fdf35f2d2df944e2e6184b312db02cf7042df400a28192af68db21090e70538122558e94233bcce2cd83176d14b4db601e59f15132
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in
|
3
|
+
# Specify your gem's dependencies in chair.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :test do
|
7
|
-
gem "codeclimate-test-reporter",
|
7
|
+
gem "codeclimate-test-reporter", require: nil
|
8
|
+
gem 'simplecov', require: nil
|
8
9
|
end
|
data/README.md
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
# Chair
|
2
|
-
[](https://travis-ci.org/toroidal-code/chair)
|
3
|
+
[](https://codeclimate.com/github/toroidal-code/chair)
|
4
|
+
[](https://codeclimate.com/github/toroidal-code/chair)
|
5
|
+
[](https://rubygems.org/gems/chair)
|
5
6
|
|
6
|
-
> Me: What's the first thing you think of when I say '
|
7
|
+
> Me: What's the first thing you think of when I say 'Table'?
|
7
8
|
> J: 'Chair'.
|
8
9
|
|
9
|
-
Chair is a simple
|
10
|
+
Chair is a simple table implementation for Ruby, with an associated Row class.
|
10
11
|
|
11
12
|
## Installation
|
12
13
|
|
13
14
|
Add this line to your application's Gemfile:
|
14
15
|
|
15
|
-
gem '
|
16
|
+
gem 'chair'
|
16
17
|
|
17
18
|
And then execute:
|
18
19
|
|
@@ -20,42 +21,40 @@ And then execute:
|
|
20
21
|
|
21
22
|
Or install it yourself as:
|
22
23
|
|
23
|
-
$ gem install
|
24
|
+
$ gem install chair
|
24
25
|
|
25
26
|
## Usage
|
26
27
|
|
27
28
|
```irb
|
28
|
-
>> require '
|
29
|
+
>> require 'chair'
|
29
30
|
=> true
|
30
|
-
>> t =
|
31
|
-
=> #<
|
31
|
+
>> t = Chair.new :title
|
32
|
+
=> #<Chair:0x0000000162ee08>
|
32
33
|
>> t.set_primary_key! :title
|
33
34
|
=> :title
|
34
35
|
>> t.insert! title: 'Looking for Alaska'
|
35
|
-
=> #<Row:0x007feb28035be0>
|
36
|
-
>> t.find_by_title('Looking for Alaska')
|
36
|
+
=> #<Chair::Row:0x007feb28035be0>
|
37
|
+
>> t.find_by_title('Looking for Alaska').to_a
|
37
38
|
>> ["Looking for Alaska"]
|
38
39
|
>> t.add_column! :author
|
39
40
|
=> true
|
40
41
|
>> t.insert! title: 'An Abundance of Katherines', author: 'John Green'
|
41
|
-
=> #<Row>
|
42
|
+
=> #<Chair::Row>
|
42
43
|
>> t.add_index! :author
|
43
44
|
=> true
|
44
|
-
>> t.find_by_title('Looking for Alaska')[:author] = 'John Green'
|
45
|
-
=> 'John Green'
|
46
45
|
>> t.find_by_author('John Green').to_a
|
47
46
|
=> ["An Abundance of Katherines", "John Green"]
|
48
|
-
>> t.find_by_title('
|
49
|
-
=>
|
47
|
+
>> t.find_by_title('Looking for Alaska')[:author] = 'John Green'
|
48
|
+
=> 'John Green'
|
50
49
|
>> r = t.where_author_is 'John Green'
|
51
|
-
=> [#<Row>, #<Row>]
|
50
|
+
=> [#<Chair::Row>, #<Chair::Row>]
|
52
51
|
>> r.map {|r| r.to_a}
|
53
52
|
=> [["An Abundance of Katherines", "John Green"], ["Looking for Alaska", "John Green"]]
|
54
53
|
```
|
55
54
|
|
56
55
|
## Contributing
|
57
56
|
|
58
|
-
1. Fork it ( https://github.com/toroidal-code/
|
57
|
+
1. Fork it ( https://github.com/toroidal-code/chair/fork )
|
59
58
|
2. Create your feature branch (`git checkout -b features/my-new-feature`)
|
60
59
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
61
60
|
4. Push to the branch (`git push origin features/my-new-feature`)
|
File without changes
|
@@ -3,7 +3,7 @@ require 'set'
|
|
3
3
|
# @author Katherine Whitlock <toroidalcode@gmail.com>
|
4
4
|
# @attr_reader primary_key [Symbol] the primary key of the table
|
5
5
|
# @attr_reader indices [Set<Symbol>] the set of indices for the table
|
6
|
-
class
|
6
|
+
class Chair
|
7
7
|
attr_reader :primary_key, :indices
|
8
8
|
|
9
9
|
# Creates a new Table object
|
@@ -14,7 +14,7 @@ class Table
|
|
14
14
|
@columns_id_counter = 0
|
15
15
|
add_columns!(*columns)
|
16
16
|
@primary_key = nil
|
17
|
-
@indices =
|
17
|
+
@indices = {}
|
18
18
|
end
|
19
19
|
|
20
20
|
# Add a new column to the table.
|
@@ -46,6 +46,8 @@ class Table
|
|
46
46
|
end
|
47
47
|
|
48
48
|
# Retrieve the current columns
|
49
|
+
# Order is guaranteed to be the order that the columns were inserted in,
|
50
|
+
# i.e., left to right
|
49
51
|
# @return [Array<Symbol>] the columns in the table
|
50
52
|
def columns
|
51
53
|
@columns.keys
|
@@ -53,56 +55,46 @@ class Table
|
|
53
55
|
|
54
56
|
# Add a new index to the table
|
55
57
|
# @param column [Symbol] the column to create the index on
|
56
|
-
# @return [
|
58
|
+
# @return [Symbol] the column name of the index
|
57
59
|
def add_index!(column)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
instance_variable_set("@#{column}_index_map".to_sym, {})
|
62
|
-
result ||= true
|
60
|
+
check_column_exists(column)
|
61
|
+
if @indices.include?(column) or instance_variable_defined?("@#{column}_index_map".to_sym)
|
62
|
+
raise ArgumentError, "Column #{column.inspect} is already an index"
|
63
63
|
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
build_index column
|
71
|
-
result
|
65
|
+
@indices[column] = "@#{column}_index_map".to_sym
|
66
|
+
instance_variable_set(@indices[column], build_index(column))
|
67
|
+
column
|
72
68
|
end
|
73
69
|
|
74
70
|
# Remove an index from the table
|
75
71
|
# @param column [Symbol] the column to remove the index from
|
76
|
-
# @return [
|
72
|
+
# @return [Symbol] the column that was removed
|
77
73
|
def remove_index!(column)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
result ||= true
|
74
|
+
check_column_exists(column)
|
75
|
+
unless @indices.include?(column) or instance_variable_defined?("@#{column}_index_map".to_sym)
|
76
|
+
raise ArgumentError, "Column #{column} is not indexed"
|
82
77
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
87
|
-
result
|
78
|
+
ivar = @indices.delete column
|
79
|
+
remove_instance_variable(ivar) unless ivar.nil?
|
80
|
+
column
|
88
81
|
end
|
89
82
|
|
90
83
|
# Insert a new row of data into the column
|
91
|
-
# @param args [Hash] the
|
84
|
+
# @param args [Hash, Array] the data to insert
|
92
85
|
# @return [Row, nil] the row inserted, or nil if the row was empty
|
93
|
-
def insert!(args
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
86
|
+
def insert!(args)
|
87
|
+
args = process_incoming_data(args)
|
88
|
+
if has_primary_key?
|
89
|
+
if args.include? @primary_key
|
90
|
+
@pk_map[args[@primary_key]] = @table.size
|
91
|
+
else # If our table has a primary key, but can't find it in the data
|
92
|
+
raise ArgumentError, 'Missing primary key in record to be inserted'
|
99
93
|
end
|
100
|
-
row[col] = value
|
101
|
-
end
|
102
|
-
unless row.empty?
|
103
|
-
@table << row
|
104
|
-
row
|
105
94
|
end
|
95
|
+
row = Row.new(self, @table.size, args)
|
96
|
+
@table << row
|
97
|
+
row
|
106
98
|
end
|
107
99
|
|
108
100
|
# Method_missing is used to dispatch to find_by_* and where_*_is
|
@@ -136,6 +128,7 @@ class Table
|
|
136
128
|
else nil
|
137
129
|
end
|
138
130
|
end
|
131
|
+
alias_method :[], :find
|
139
132
|
|
140
133
|
# Search for rows based on given data
|
141
134
|
# @param args [Hash<Symbol, Object>] the data to search for
|
@@ -179,7 +172,7 @@ class Table
|
|
179
172
|
# @return [Array<Row>] the rows found
|
180
173
|
def table_scan(args)
|
181
174
|
results = @table.to_set
|
182
|
-
|
175
|
+
args.each_pair do |col, value|
|
183
176
|
results = restrict_with_table_scan(col, value, results)
|
184
177
|
end
|
185
178
|
results.to_a
|
@@ -192,7 +185,7 @@ class Table
|
|
192
185
|
unless @columns.has_key? column
|
193
186
|
return nil
|
194
187
|
end
|
195
|
-
@pk_map =
|
188
|
+
@pk_map = build_pk_map column
|
196
189
|
@primary_key = column
|
197
190
|
end
|
198
191
|
|
@@ -202,17 +195,57 @@ class Table
|
|
202
195
|
not @primary_key.nil?
|
203
196
|
end
|
204
197
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
198
|
+
# Merge the table with a map of primary keys to values. There must be a primary key.
|
199
|
+
# @param column [Symbol] the column to insert the values into
|
200
|
+
# @param map [Hash<Object, Object>] a mapping of primary_key values to other values
|
201
|
+
# @param opts [Bool] :overwrite if a value already exists in the row, overwrite it
|
202
|
+
# @param opts [Bool] :create_row if the row doesn't already exist, create it
|
203
|
+
def merge!(column, map, opts = {})
|
204
|
+
unless has_primary_key?
|
205
|
+
raise 'No primary key exists for this table'
|
206
|
+
end
|
207
|
+
# For each key, value
|
208
|
+
map.each_pair do |key, val|
|
209
|
+
unless @pk_map.include? key # Check if we have the key in our pk_map. if not,
|
210
|
+
if opts[:create_row] # if we can create rows,
|
211
|
+
insert! @primary_key => key, column => val # create a row
|
212
|
+
else raise "No such row with primary key #{key.inspect} exists" # or raise an error
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# if we do, check if the row has a value in the column
|
216
|
+
row = @table[@pk_map[key]]
|
217
|
+
# if it does, can we overwrite?
|
218
|
+
if row.has_attribute?(column) and not opts[:overwrite]
|
219
|
+
raise "Value already exists in table for primary key #{key.inspect} and column #{column.inspect}"
|
220
|
+
end
|
221
|
+
# if so, overwrite
|
222
|
+
row[column] = val
|
210
223
|
end
|
211
|
-
|
224
|
+
self
|
212
225
|
end
|
213
226
|
|
227
|
+
# Retrieve all rows
|
228
|
+
# @return [Array<Chair::Row>] all of the rows in the table
|
229
|
+
def all
|
230
|
+
@table
|
231
|
+
end
|
232
|
+
|
233
|
+
# Retrieve the first row in the table
|
234
|
+
# @return [Chair::Row] the first row in the table
|
235
|
+
def first
|
236
|
+
@table.first
|
237
|
+
end
|
238
|
+
|
239
|
+
# Retrieve the last row in the table
|
240
|
+
# @return [Chair::Row] the last row in the table
|
241
|
+
def last
|
242
|
+
@table.last
|
243
|
+
end
|
244
|
+
|
245
|
+
protected
|
246
|
+
|
214
247
|
def find_valid_indices(cols)
|
215
|
-
@indices.
|
248
|
+
@indices.keys & cols
|
216
249
|
end
|
217
250
|
|
218
251
|
def restrict_with_index(key, value, initial=@table.to_set)
|
@@ -221,10 +254,7 @@ class Table
|
|
221
254
|
return Set.new
|
222
255
|
end
|
223
256
|
row_idxs = idx_map[value]
|
224
|
-
|
225
|
-
return Set.new
|
226
|
-
end
|
227
|
-
rows = @table.values_at *row_idxs
|
257
|
+
rows = @table.values_at *row_idxs # *nil is empty call
|
228
258
|
initial.intersection rows
|
229
259
|
end
|
230
260
|
|
@@ -232,27 +262,58 @@ class Table
|
|
232
262
|
initial.keep_if { |row| row[col] == value }
|
233
263
|
end
|
234
264
|
|
235
|
-
def select(col, table = @table, &block)
|
236
|
-
col_id = get_column_id(col)
|
237
|
-
table.select do |row|
|
238
|
-
block(col)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
265
|
# Scan the table and add all the rows to the index
|
243
266
|
# @param column [Symbol] the column to construct the index for
|
244
267
|
def build_index(column)
|
245
|
-
|
268
|
+
map = {}
|
246
269
|
@table.each_with_index do |row, idx|
|
247
270
|
val = row[column]
|
248
271
|
unless val.nil?
|
249
|
-
if
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
272
|
+
map[val] = Set.new if map[val].nil?
|
273
|
+
map[val] = map[val] << idx
|
274
|
+
end
|
275
|
+
end
|
276
|
+
map
|
277
|
+
end
|
278
|
+
|
279
|
+
def build_pk_map(column)
|
280
|
+
map = {}
|
281
|
+
@table.each_with_index do |row, idx|
|
282
|
+
val = row[column]
|
283
|
+
# if the value is nil, we can't use it
|
284
|
+
if val.nil? or val.empty?
|
285
|
+
raise "Row does not have a value in column #{column.inspect}"
|
286
|
+
end
|
287
|
+
|
288
|
+
#if we already have the value in our map, it's not unique and one-to-one
|
289
|
+
if map.include?(val)
|
290
|
+
raise "Primary key #{val.inspect} is not unique in column #{column.inspect}"
|
254
291
|
end
|
292
|
+
|
293
|
+
# otherwise we can assign it
|
294
|
+
map[val] = idx
|
295
|
+
end
|
296
|
+
map
|
297
|
+
end
|
298
|
+
|
299
|
+
# @return [Hash] the data in a :column => 'value' format
|
300
|
+
def process_incoming_data(args)
|
301
|
+
case args
|
302
|
+
when Hash
|
303
|
+
filtered = args.clone.keep_if{|col,_| @columns.include? col }
|
304
|
+
if args != filtered
|
305
|
+
invalid = args.clone.delete_if{|col, _| filtered.include? col }.keys.compact.join(', ')
|
306
|
+
raise ArgumentError, "No such column(s) #{invalid}"
|
307
|
+
end
|
308
|
+
filtered
|
309
|
+
else # Because we can guarantee the order of @columns, we use this to zip the array into a hash
|
310
|
+
Hash[columns.zip(args.to_a)]
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def check_column_exists(column)
|
315
|
+
unless @columns.include?(column)
|
316
|
+
raise ArgumentError, "Column #{column.inspect} does not exist in table"
|
255
317
|
end
|
256
|
-
return
|
257
318
|
end
|
258
319
|
end
|
data/lib/chair/row.rb
CHANGED
@@ -1,62 +1,108 @@
|
|
1
|
-
class Row
|
1
|
+
class Chair::Row
|
2
|
+
include Comparable
|
2
3
|
|
3
4
|
# Create a new cell
|
4
5
|
# @param table [Table] the table holding this row
|
5
|
-
# @param id [Fixnum] the array index of this row in the
|
6
|
-
def initialize(table, id)
|
6
|
+
# @param id [Fixnum] the array index of this row in the internal 2D array
|
7
|
+
def initialize(table, id, data = {})
|
7
8
|
@row_id = id
|
8
9
|
@table = table
|
9
|
-
@
|
10
|
+
@attributes = data
|
11
|
+
@attributes.clone.
|
12
|
+
keep_if{ |col, _| @table.indices.include? col }.
|
13
|
+
each_pair{ |col, val| add_to_index(col, val) }
|
10
14
|
end
|
11
15
|
|
12
16
|
# Get a cell based on the column name
|
13
17
|
# @return [Object, nil] the value in the cell, can be nil
|
14
|
-
def [](
|
15
|
-
|
16
|
-
@row[idx]
|
18
|
+
def [](column)
|
19
|
+
@attributes[column]
|
17
20
|
end
|
18
21
|
|
19
22
|
# Assign a new value to one of the cells in the row
|
20
|
-
# @param
|
23
|
+
# @param column [Symbol] the column name to add to
|
21
24
|
# @param value [Object] the value to assign
|
22
25
|
# @return [Object] the assigned value
|
23
|
-
def []=(
|
24
|
-
|
25
|
-
|
26
|
-
if @table.instance_variable_get("@#{col}_index_map".to_sym)[value].nil?
|
27
|
-
@table.instance_variable_get("@#{col}_index_map".to_sym)[value] = Set.new
|
28
|
-
end
|
29
|
-
@table.instance_variable_get("@#{col}_index_map")[value] =
|
30
|
-
@table.instance_variable_get("@#{col}_index_map")[value] << @row_id
|
26
|
+
def []=(column, value)
|
27
|
+
if @table.indices.include? column
|
28
|
+
add_to_index(column, value)
|
31
29
|
end
|
32
|
-
@
|
30
|
+
@attributes[column] = value
|
33
31
|
end
|
34
32
|
|
35
33
|
def empty?
|
36
|
-
@
|
34
|
+
@attributes.empty?
|
37
35
|
end
|
38
36
|
|
39
37
|
# Create a hash of the data based on the columns
|
40
38
|
# @return [Hash<Symbol, Object>] the data in the row
|
41
39
|
def to_hash
|
42
|
-
|
43
|
-
@table.columns.each do |col|
|
44
|
-
idx = @table.send(:get_column_id, col)
|
45
|
-
map[col] = row[idx]
|
46
|
-
end
|
47
|
-
map
|
40
|
+
@attributes
|
48
41
|
end
|
49
42
|
|
50
43
|
# Convert the row data to an array
|
51
44
|
# @return [Array<Object>] the data in the row
|
52
45
|
def to_a
|
53
|
-
@
|
46
|
+
@table.columns.map { |col| @attributes[col] }
|
54
47
|
end
|
55
48
|
|
56
49
|
# Compare Row instances based on internal representation
|
57
50
|
# @param other [Object] the object to compare to
|
58
51
|
# @return [Bool] whether or not the objects are the same
|
59
|
-
def
|
60
|
-
|
52
|
+
def ==(other)
|
53
|
+
case other
|
54
|
+
when Chair::Row
|
55
|
+
@attributes == other.instance_variable_get('@attributes')
|
56
|
+
when Array
|
57
|
+
@attributes.values == other
|
58
|
+
else false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Compare rows within a table
|
63
|
+
# @param other [Object] the object to compare to
|
64
|
+
def <=>(other)
|
65
|
+
# only be comparable if we're in the same table
|
66
|
+
if @table.equal?(other.instance_variable_get(@table))
|
67
|
+
other_id = other.instance_variable_get('@id')
|
68
|
+
@id <=> other_id
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the contents of the record as a nicely formatted string.
|
75
|
+
def inspect
|
76
|
+
pairs = []
|
77
|
+
# Use the table's column list to order our columns
|
78
|
+
@table.columns.each { |name| pairs << "#{name}: #{@attributes[name].inspect}"}
|
79
|
+
inspection = pairs.compact.join(', ')
|
80
|
+
"#<#{self.class} #{inspection}>"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Looks to see if we have a attribute
|
84
|
+
# @param name [Symbol] the column to look at
|
85
|
+
# @return [Bool] whether or not the value is empty
|
86
|
+
def has_attribute?(name)
|
87
|
+
val = @attributes[name]
|
88
|
+
val.nil? ? false : (not val.empty?)
|
89
|
+
end
|
90
|
+
|
91
|
+
def method_missing(method_sym, *arguments, &block)
|
92
|
+
# the first argument is a Symbol, so you need to_s it if you want to pattern match
|
93
|
+
if method_sym.to_s =~ /^has_(.*)\?$/
|
94
|
+
has_attribute?($1.to_sym)
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
def add_to_index(column, value)
|
102
|
+
if @table.instance_variable_get("@#{column}_index_map".to_sym)[value].nil?
|
103
|
+
@table.instance_variable_get("@#{column}_index_map".to_sym)[value] = Set.new
|
104
|
+
end
|
105
|
+
@table.instance_variable_get("@#{column}_index_map")[value] =
|
106
|
+
@table.instance_variable_get("@#{column}_index_map")[value] << @row_id
|
61
107
|
end
|
62
108
|
end
|
data/lib/chair/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "1.0
|
1
|
+
class Chair
|
2
|
+
VERSION = "1.1.0"
|
3
3
|
end
|
data/lib/chair.rb
CHANGED
data/spec/row_spec.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Chair::Row do
|
4
|
+
it 'can get value with []' do
|
5
|
+
table = Chair.new :title
|
6
|
+
row = Chair::Row.new table,0, {title: 'War and Peace'}
|
7
|
+
expect(row[:title]).to eq('War and Peace')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can set value with []=' do
|
11
|
+
table = Chair.new :title
|
12
|
+
row = Chair::Row.new table, 0, {title: 'War and Peace'}
|
13
|
+
expect{
|
14
|
+
row[:title] = 'The Fault in Our Stars'
|
15
|
+
}.to change{row.instance_variable_get('@attributes')[:title]}.from('War and Peace').to('The Fault in Our Stars')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'is empty after initialization' do
|
19
|
+
table = Chair.new :title
|
20
|
+
row = Chair::Row.new(table,0)
|
21
|
+
expect(row.empty?).to be(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'is not empty when data is present' do
|
25
|
+
table = Chair.new :title
|
26
|
+
row = Chair::Row.new(table,0, {title: 'Will Grayson, Will Grayson'})
|
27
|
+
expect(row.empty?).to be(false)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can be converted to a hash' do
|
31
|
+
table = Chair.new :title, :author
|
32
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson', author: 'John Green'}
|
33
|
+
expect(row.to_hash).to eq({title: 'Will Grayson, Will Grayson',
|
34
|
+
author: 'John Green'})
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can be converted to an array' do
|
38
|
+
table = Chair.new :title, :author
|
39
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson', author: 'John Green'}
|
40
|
+
expect(row.to_a).to eq(['Will Grayson, Will Grayson', 'John Green'])
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
it 'can compared against an Array with ==' do
|
45
|
+
table = Chair.new :title, :author
|
46
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson', author: 'John Green'}
|
47
|
+
expect(row == ['Will Grayson, Will Grayson', 'John Green']).to be(true)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'can compared against another Chair::Row with ==' do
|
51
|
+
table = Chair.new :title, :author
|
52
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson', author: 'John Green'}
|
53
|
+
other_row = Chair::Row.new table, 1, {title: 'Will Grayson, Will Grayson', author: 'John Green'}
|
54
|
+
expect(row == other_row).to be(true)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'inspect prints values' do
|
58
|
+
table = Chair.new :title, :author
|
59
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson', author: 'John Green'}
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'has attribute is true when present' do
|
63
|
+
table = Chair.new :title, :author
|
64
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson'}
|
65
|
+
expect(row.has_attribute? :title).to be(true)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'has attribute is false when nil' do
|
69
|
+
table = Chair.new :title, :author
|
70
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson'}
|
71
|
+
expect(row.has_attribute? :author).to be(false)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'has attribute is false when empty' do
|
75
|
+
table = Chair.new :title, :author
|
76
|
+
row = Chair::Row.new table, 0, {title: 'Will Grayson, Will Grayson', author: ''}
|
77
|
+
expect(row.has_attribute? :title).to be(true)
|
78
|
+
end
|
79
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/table_search_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
4
|
-
subject(:table) {
|
3
|
+
describe Chair do
|
4
|
+
subject(:table) { Chair.new :id, :title, :author }
|
5
5
|
|
6
6
|
describe 'finds by' do
|
7
7
|
it 'primary key' do
|
@@ -61,9 +61,34 @@ describe Table do
|
|
61
61
|
expect(table.where_title_is('War and Peace').first.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
62
62
|
end
|
63
63
|
|
64
|
-
it 'table scan' do
|
64
|
+
it 'table scan by fallback' do
|
65
65
|
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
66
66
|
expect(table.where_title_is('War and Peace').first.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
67
67
|
end
|
68
|
+
|
69
|
+
it 'table scan' do
|
70
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
71
|
+
expect(table.table_scan(title: 'War and Peace').first.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'gets all records with #all' do
|
76
|
+
table.insert! id: 0, title: 'Looking for Alaska', author: 'John Green'
|
77
|
+
table.insert! id: 1, title: 'Lost at Sea', author: "Bryan Lee O'Malley"
|
78
|
+
expect(table.all.map{|r| r.to_a}).to eq([[0, 'Looking for Alaska', 'John Green'],
|
79
|
+
[1, 'Lost at Sea', "Bryan Lee O'Malley"]])
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'gets the first record' do
|
83
|
+
table.insert! id: 0, title: 'Looking for Alaska', author: 'John Green'
|
84
|
+
table.insert! id: 1, title: 'Lost at Sea', author: "Bryan Lee O'Malley"
|
85
|
+
expect(table.first.to_a).to eq([0, 'Looking for Alaska', 'John Green'])
|
68
86
|
end
|
87
|
+
|
88
|
+
it 'gets the last record' do
|
89
|
+
table.insert! id: 0, title: 'Looking for Alaska', author: 'John Green'
|
90
|
+
table.insert! id: 1, title: 'Lost at Sea', author: "Bryan Lee O'Malley"
|
91
|
+
expect(table.last.to_a).to eq([1, 'Lost at Sea', "Bryan Lee O'Malley"])
|
92
|
+
end
|
93
|
+
|
69
94
|
end
|
data/spec/table_spec.rb
CHANGED
@@ -1,94 +1,243 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe Chair do
|
4
4
|
describe 'initializes' do
|
5
|
-
it 'with
|
6
|
-
table =
|
5
|
+
it 'with no columns' do
|
6
|
+
table = Chair.new
|
7
7
|
expect(table.columns.count).to eq(0)
|
8
8
|
end
|
9
9
|
|
10
|
-
it 'with
|
11
|
-
table =
|
10
|
+
it 'with no rows' do
|
11
|
+
table = Chair.new
|
12
12
|
expect(table.size).to eq(0)
|
13
13
|
end
|
14
14
|
|
15
|
-
it 'with columns given' do
|
16
|
-
table = Table.new :title, :author, :isbn
|
17
|
-
expect(table.columns).to eq([:title, :author, :isbn])
|
18
|
-
end
|
19
|
-
|
20
15
|
it 'with no primary key' do
|
21
|
-
table =
|
16
|
+
table = Chair.new
|
22
17
|
expect(table.has_primary_key?).to eq(false)
|
23
18
|
end
|
24
19
|
|
25
20
|
it 'with no indices' do
|
26
|
-
table =
|
21
|
+
table = Chair.new
|
27
22
|
expect(table.indices.size).to eq(0)
|
28
23
|
end
|
24
|
+
|
25
|
+
it 'with specified columns' do
|
26
|
+
table = Chair.new :title, :author, :isbn
|
27
|
+
expect(table.columns).to eq([:title, :author, :isbn])
|
28
|
+
end
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'adds a column' do
|
32
|
-
table =
|
32
|
+
table = Chair.new
|
33
33
|
expect {
|
34
34
|
table.add_column!(:title)
|
35
35
|
}.to change{ table.columns.count }.by(1)
|
36
36
|
end
|
37
37
|
|
38
38
|
it 'raises an ArgumentError if the name is not a symbol' do
|
39
|
-
table =
|
39
|
+
table = Chair.new
|
40
40
|
expect{
|
41
41
|
table.add_column!('Title')
|
42
42
|
}.to raise_error(ArgumentError)
|
43
43
|
end
|
44
44
|
|
45
|
-
it
|
46
|
-
table =
|
45
|
+
it "doesn't add column with the same name" do
|
46
|
+
table = Chair.new :title
|
47
47
|
expect {
|
48
48
|
table.add_column!(:title)
|
49
49
|
}.to_not change{ table.columns.count }
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'adds multiple columns' do
|
53
|
-
table =
|
53
|
+
table = Chair.new
|
54
54
|
expect {
|
55
55
|
table.add_columns! :title, :author
|
56
56
|
}.to change {table.columns.count}.by(2)
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
59
|
+
describe 'inserts a row' do
|
60
|
+
it 'from a hash' do
|
61
|
+
table = Chair.new :title
|
62
|
+
expect {
|
63
|
+
table.insert!({title: 'Gone with the Wind'})
|
64
|
+
}.to change { table.size }.by(1)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'from an array' do
|
68
|
+
table = Chair.new :title
|
69
|
+
expect {
|
70
|
+
table.insert!(['Gone with the Wind'])
|
71
|
+
}.to change { table.size }.by(1)
|
72
|
+
end
|
64
73
|
end
|
65
74
|
|
66
|
-
|
67
|
-
|
68
|
-
|
75
|
+
describe 'setting primary key' do
|
76
|
+
it 'with valid params is successful' do
|
77
|
+
table = Chair.new :title
|
78
|
+
expect {
|
79
|
+
table.set_primary_key! :title
|
80
|
+
}.to change {table.primary_key}.from(nil).to(:title)
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'fails to build index' do
|
84
|
+
it 'when duplicate fields exist' do
|
85
|
+
table = Chair.new :title
|
86
|
+
table.insert! title: 'Looking for Alaska'
|
87
|
+
table.insert! title: 'Looking for Alaska'
|
88
|
+
expect {
|
89
|
+
table.set_primary_key! :title
|
90
|
+
}.to raise_error RuntimeError, 'Primary key "Looking for Alaska" is not unique in column :title'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'when fields are nil' do
|
94
|
+
table = Chair.new :title
|
95
|
+
table.insert! title: 'Looking for Alaska'
|
96
|
+
table.insert! title: nil
|
97
|
+
expect {
|
98
|
+
table.set_primary_key! :title
|
99
|
+
}.to raise_error RuntimeError, 'Row does not have a value in column :title'
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'when fields are empty' do
|
103
|
+
table = Chair.new :title
|
104
|
+
table.insert! title: 'Looking for Alaska'
|
105
|
+
table.insert! title: ''
|
106
|
+
expect {
|
107
|
+
table.set_primary_key! :title
|
108
|
+
}.to raise_error RuntimeError, 'Row does not have a value in column :title'
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "doesn't set the primary key if it's not a valid column" do
|
115
|
+
table = Chair.new
|
116
|
+
expect{
|
69
117
|
table.set_primary_key! :title
|
70
|
-
}.
|
118
|
+
}.not_to change{table.primary_key}.from(nil)
|
71
119
|
end
|
72
120
|
|
73
|
-
it "doesn't set the primary key if
|
74
|
-
table =
|
121
|
+
it "doesn't set the primary key if there's already a primary key" do
|
122
|
+
table = Chair.new
|
75
123
|
expect{
|
76
124
|
table.set_primary_key! :title
|
77
125
|
}.not_to change{table.primary_key}.from(nil)
|
78
126
|
end
|
79
127
|
|
80
|
-
|
81
|
-
|
82
|
-
|
128
|
+
describe 'add index' do
|
129
|
+
it 'succeeds with valid params' do
|
130
|
+
table = Chair.new :title
|
131
|
+
expect {
|
132
|
+
table.add_index! :title
|
133
|
+
}.to change {table.indices.size}.by(1)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should raise ArgumentError when a column doesn't exist" do
|
137
|
+
table = Chair.new
|
138
|
+
expect{table.add_index! :title}.to raise_error(ArgumentError)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should raise ArgumentError when a column is already indexed' do
|
142
|
+
table = Chair.new :title
|
143
|
+
table.add_index! :title
|
144
|
+
expect{table.add_index! :title}.to raise_error(ArgumentError)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
describe 'remove index' do
|
150
|
+
it 'succeeds with valid params' do
|
151
|
+
table = Chair.new :title
|
83
152
|
table.add_index! :title
|
84
|
-
|
153
|
+
expect {
|
154
|
+
table.remove_index! :title
|
155
|
+
}.to change {table.indices.size}.by(-1)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should raise ArgumentError when a column doesn't exist" do
|
159
|
+
table = Chair.new
|
160
|
+
expect{table.remove_index! :title}.to raise_error(ArgumentError)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should raise ArgumentError when a column is not indexed' do
|
164
|
+
table = Chair.new :title
|
165
|
+
expect{table.remove_index! :title}.to raise_error(ArgumentError)
|
166
|
+
end
|
85
167
|
end
|
86
168
|
|
87
|
-
|
88
|
-
|
169
|
+
|
170
|
+
it 'builds an index of existing data' do
|
171
|
+
table = Chair.new :title
|
172
|
+
table.insert! title: 'The Fault in Our Stars'
|
89
173
|
table.add_index! :title
|
174
|
+
expect(
|
175
|
+
table.instance_variable_get("@title_index_map").has_key? 'The Fault in Our Stars'
|
176
|
+
).to eq(true)
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'adds data to the index' do
|
180
|
+
table = Chair.new :title
|
181
|
+
table.add_index! :title
|
182
|
+
expect {
|
183
|
+
table.insert! title: 'The Fault in Our Stars'
|
184
|
+
}.to change{table.instance_variable_get("@title_index_map").size}.by(1)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'data must have primary key if primary key is defined' do
|
188
|
+
table = Chair.new :title, :author
|
189
|
+
table.set_primary_key! :title
|
190
|
+
expect {
|
191
|
+
table.insert! author: 'John Green'
|
192
|
+
}.to raise_error ArgumentError
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'cannot insert a row if no such column exists' do
|
196
|
+
table = Chair.new :title
|
90
197
|
expect {
|
91
|
-
table.
|
92
|
-
}.to
|
198
|
+
table.insert! title:'The Fault in Our Stars', author: 'John'
|
199
|
+
}.to raise_error ArgumentError
|
200
|
+
end
|
201
|
+
|
202
|
+
describe 'merge' do
|
203
|
+
it 'data into the table' do
|
204
|
+
table = Chair.new :num, :string
|
205
|
+
table.set_primary_key! :num
|
206
|
+
table.insert! num: 123
|
207
|
+
table.insert! num: 456
|
208
|
+
table.merge!(:string, {123 => '123',
|
209
|
+
456 => '456'})
|
210
|
+
expect(table.find(123)[:string]).to eq('123')
|
211
|
+
expect(table.find(456)[:string]).to eq('456')
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should raise RuntimeError when no primary key' do
|
215
|
+
table = Chair.new :num, :string
|
216
|
+
expect {
|
217
|
+
table.merge!(:string, {123 => '123',
|
218
|
+
456 => '456'})
|
219
|
+
}.to raise_error RuntimeError, 'No primary key exists for this table'
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should raise RuntimeError when no such key exists' do
|
223
|
+
table = Chair.new :num, :string
|
224
|
+
table.set_primary_key! :num
|
225
|
+
expect {
|
226
|
+
table.merge!(:string, {123 => '123',
|
227
|
+
456 => '456'})
|
228
|
+
}.to raise_error RuntimeError, 'No such row with primary key 123 exists'
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'should raise RuntimeError instead of overwrite data by default' do
|
232
|
+
table = Chair.new :num, :string
|
233
|
+
table.set_primary_key! :num
|
234
|
+
table.insert! num: 123, string: '123'
|
235
|
+
table.insert! num: 456
|
236
|
+
expect {
|
237
|
+
table.merge!(:string, {123 => '123',
|
238
|
+
456 => '456'})
|
239
|
+
}.to raise_error RuntimeError, 'Value already exists in table for primary key 123 and column :string'
|
240
|
+
end
|
93
241
|
end
|
242
|
+
|
94
243
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chair
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katherine Whitlock
|
@@ -65,12 +65,12 @@ files:
|
|
65
65
|
- LICENSE.txt
|
66
66
|
- README.md
|
67
67
|
- Rakefile
|
68
|
-
-
|
69
|
-
- demo.rb
|
68
|
+
- chair.gemspec
|
70
69
|
- lib/chair.rb
|
70
|
+
- lib/chair/chair.rb
|
71
71
|
- lib/chair/row.rb
|
72
|
-
- lib/chair/table.rb
|
73
72
|
- lib/chair/version.rb
|
73
|
+
- spec/row_spec.rb
|
74
74
|
- spec/spec_helper.rb
|
75
75
|
- spec/table_search_spec.rb
|
76
76
|
- spec/table_spec.rb
|
@@ -99,6 +99,7 @@ signing_key:
|
|
99
99
|
specification_version: 4
|
100
100
|
summary: Tables!
|
101
101
|
test_files:
|
102
|
+
- spec/row_spec.rb
|
102
103
|
- spec/spec_helper.rb
|
103
104
|
- spec/table_search_spec.rb
|
104
105
|
- spec/table_spec.rb
|
data/demo.rb
DELETED