chair 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](http://img.shields.io/travis/toroidal-code/
|
3
|
-
[![Code Climate](https://img.shields.io/codeclimate/github/toroidal-code/
|
4
|
-
[![Coverage](https://img.shields.io/codeclimate/coverage/github/toroidal-code/
|
2
|
+
[![Build Status](http://img.shields.io/travis/toroidal-code/chair/master.svg?style=flat)](https://travis-ci.org/toroidal-code/chair)
|
3
|
+
[![Code Climate](https://img.shields.io/codeclimate/github/toroidal-code/chair.svg?style=flat)](https://codeclimate.com/github/toroidal-code/chair)
|
4
|
+
[![Coverage](https://img.shields.io/codeclimate/coverage/github/toroidal-code/chair.svg?style=flat)](https://codeclimate.com/github/toroidal-code/chair)
|
5
|
+
[![Gem Version](http://img.shields.io/gem/v/chair.svg?style=flat)](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