chair 1.0.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 +7 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +7 -0
- data/chairs.gemspec +26 -0
- data/demo.rb +4 -0
- data/lib/chair/row.rb +62 -0
- data/lib/chair/table.rb +258 -0
- data/lib/chair/version.rb +3 -0
- data/lib/chair.rb +3 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/table_search_spec.rb +69 -0
- data/spec/table_spec.rb +94 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cfab2cfe22182e362af7c3958e7c20d906f69510
|
4
|
+
data.tar.gz: 8fdc21582f8c3f8af34c03a0c1af94fa1826e88f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d74dc734076ed87b19b1f5f12c71d8f3118c8539e38dfc2954f71a5000eae1f73e1c977248bafe1c8dac850f924a23c5645297318b989a5d58490c1856e81deb
|
7
|
+
data.tar.gz: a7b060604bcb97cef3cfb5f15272747493bbe5fe4221b6b0e3d00fd4c0150451c86f4c380a14c293e5e8eb8516883a4699906131c5f93c15e1bc77d26d59993d
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
/.idea
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --format documentation
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Katherine Whitlock
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Chair
|
2
|
+
[](https://travis-ci.org/toroidal-code/chairs)
|
3
|
+
[](https://codeclimate.com/github/toroidal-code/chairs)
|
4
|
+
[](https://codeclimate.com/github/toroidal-code/chairs)
|
5
|
+
|
6
|
+
> Me: What's the first thing you think of when I say 'Tables'?
|
7
|
+
> J: 'Chair'.
|
8
|
+
|
9
|
+
Chair is a simple Table class for Ruby, with an associated Row class.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'chairs'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install chairs
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
```irb
|
28
|
+
>> require 'chairs'
|
29
|
+
=> true
|
30
|
+
>> t = Table.new :title
|
31
|
+
=> #<Table:0x0000000162ee08>
|
32
|
+
>> t.set_primary_key! :title
|
33
|
+
=> :title
|
34
|
+
>> t.insert! title: 'Looking for Alaska'
|
35
|
+
=> #<Row:0x007feb28035be0>
|
36
|
+
>> t.find_by_title('Looking for Alaska')
|
37
|
+
>> ["Looking for Alaska"]
|
38
|
+
>> t.add_column! :author
|
39
|
+
=> true
|
40
|
+
>> t.insert! title: 'An Abundance of Katherines', author: 'John Green'
|
41
|
+
=> #<Row>
|
42
|
+
>> t.add_index! :author
|
43
|
+
=> true
|
44
|
+
>> t.find_by_title('Looking for Alaska')[:author] = 'John Green'
|
45
|
+
=> 'John Green'
|
46
|
+
>> t.find_by_author('John Green').to_a
|
47
|
+
=> ["An Abundance of Katherines", "John Green"]
|
48
|
+
>> t.find_by_title('An Abundance of Katherines')[:author] = 'John Green'
|
49
|
+
=> "John Green"
|
50
|
+
>> r = t.where_author_is 'John Green'
|
51
|
+
=> [#<Row>, #<Row>]
|
52
|
+
>> r.map {|r| r.to_a}
|
53
|
+
=> [["An Abundance of Katherines", "John Green"], ["Looking for Alaska", "John Green"]]
|
54
|
+
```
|
55
|
+
|
56
|
+
## Contributing
|
57
|
+
|
58
|
+
1. Fork it ( https://github.com/toroidal-code/chairs/fork )
|
59
|
+
2. Create your feature branch (`git checkout -b features/my-new-feature`)
|
60
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
61
|
+
4. Push to the branch (`git push origin features/my-new-feature`)
|
62
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/chairs.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chair/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'chair'
|
8
|
+
spec.version = Chair::VERSION
|
9
|
+
spec.date = '2014-06-04'
|
10
|
+
spec.summary = "Tables!"
|
11
|
+
spec.description = "A pure ruby table implementation with arbitray column indices"
|
12
|
+
spec.authors = ["Katherine Whitlock"]
|
13
|
+
spec.email = 'toroidalcode@gmail.com'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.homepage = 'http://github.com/toroidal-code/chair'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec'
|
26
|
+
end
|
data/demo.rb
ADDED
data/lib/chair/row.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
class Row
|
2
|
+
|
3
|
+
# Create a new cell
|
4
|
+
# @param table [Table] the table holding this row
|
5
|
+
# @param id [Fixnum] the array index of this row in the interal 2D array
|
6
|
+
def initialize(table, id)
|
7
|
+
@row_id = id
|
8
|
+
@table = table
|
9
|
+
@row = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get a cell based on the column name
|
13
|
+
# @return [Object, nil] the value in the cell, can be nil
|
14
|
+
def [](col)
|
15
|
+
idx = @table.send(:get_column_id, col)
|
16
|
+
@row[idx]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Assign a new value to one of the cells in the row
|
20
|
+
# @param col [Symbol] the column name to add to
|
21
|
+
# @param value [Object] the value to assign
|
22
|
+
# @return [Object] the assigned value
|
23
|
+
def []=(col, value)
|
24
|
+
idx = @table.send(:get_column_id, col)
|
25
|
+
if @table.indices.include? col
|
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
|
31
|
+
end
|
32
|
+
@row[idx] = value
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
@row.empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create a hash of the data based on the columns
|
40
|
+
# @return [Hash<Symbol, Object>] the data in the row
|
41
|
+
def to_hash
|
42
|
+
map = {}
|
43
|
+
@table.columns.each do |col|
|
44
|
+
idx = @table.send(:get_column_id, col)
|
45
|
+
map[col] = row[idx]
|
46
|
+
end
|
47
|
+
map
|
48
|
+
end
|
49
|
+
|
50
|
+
# Convert the row data to an array
|
51
|
+
# @return [Array<Object>] the data in the row
|
52
|
+
def to_a
|
53
|
+
@row
|
54
|
+
end
|
55
|
+
|
56
|
+
# Compare Row instances based on internal representation
|
57
|
+
# @param other [Object] the object to compare to
|
58
|
+
# @return [Bool] whether or not the objects are the same
|
59
|
+
def eql?(other)
|
60
|
+
@row.eql?(other.instance_variable_get("@row"))
|
61
|
+
end
|
62
|
+
end
|
data/lib/chair/table.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# @author Katherine Whitlock <toroidalcode@gmail.com>
|
4
|
+
# @attr_reader primary_key [Symbol] the primary key of the table
|
5
|
+
# @attr_reader indices [Set<Symbol>] the set of indices for the table
|
6
|
+
class Table
|
7
|
+
attr_reader :primary_key, :indices
|
8
|
+
|
9
|
+
# Creates a new Table object
|
10
|
+
# @param columns [Symbol] columns to insert into the table at initialization
|
11
|
+
def initialize(*columns)
|
12
|
+
@table = []
|
13
|
+
@columns = {}
|
14
|
+
@columns_id_counter = 0
|
15
|
+
add_columns!(*columns)
|
16
|
+
@primary_key = nil
|
17
|
+
@indices = Set.new
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add a new column to the table.
|
21
|
+
# @param column [Symbol] the column name to add
|
22
|
+
# @raise [ArgumentError] if the column name is not a symbol
|
23
|
+
# @return [Bool] whether or not we successfully added the new column
|
24
|
+
def add_column!(column)
|
25
|
+
case column
|
26
|
+
when Symbol
|
27
|
+
else raise ArgumentError, "Column name should be Symbol not #{column.class}"
|
28
|
+
end
|
29
|
+
|
30
|
+
if @columns.include? column
|
31
|
+
false
|
32
|
+
else
|
33
|
+
@columns[column] = @columns_id_counter
|
34
|
+
@columns_id_counter += 1
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add multiple columns to the table
|
40
|
+
# @param columns [Symbol] the columns to add
|
41
|
+
# @return [Bool] whether or not all of the columns were successfully added
|
42
|
+
def add_columns!(*columns)
|
43
|
+
result = true
|
44
|
+
columns.each { |c| result &&= add_column!(c) }
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
# Retrieve the current columns
|
49
|
+
# @return [Array<Symbol>] the columns in the table
|
50
|
+
def columns
|
51
|
+
@columns.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add a new index to the table
|
55
|
+
# @param column [Symbol] the column to create the index on
|
56
|
+
# @return [Bool] whether or not we added the index
|
57
|
+
def add_index!(column)
|
58
|
+
result = false
|
59
|
+
get_column_id(column)
|
60
|
+
unless instance_variable_defined?("@#{column}_index_map".to_sym)
|
61
|
+
instance_variable_set("@#{column}_index_map".to_sym, {})
|
62
|
+
result ||= true
|
63
|
+
end
|
64
|
+
|
65
|
+
unless @indices.include? column
|
66
|
+
@indices = @indices << column
|
67
|
+
result ||= true
|
68
|
+
end
|
69
|
+
|
70
|
+
build_index column
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
# Remove an index from the table
|
75
|
+
# @param column [Symbol] the column to remove the index from
|
76
|
+
# @return [Bool] whether or not the column was successfully removed
|
77
|
+
def remove_index!(column)
|
78
|
+
result = false
|
79
|
+
if instance_variable_defined?("@#{column}_index_map".to_sym)
|
80
|
+
remove_instance_variable("@#{column}_index_map")
|
81
|
+
result ||= true
|
82
|
+
end
|
83
|
+
if @indices.include? column
|
84
|
+
@indices = @indices.delete column
|
85
|
+
result ||= true
|
86
|
+
end
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
# Insert a new row of data into the column
|
91
|
+
# @param args [Hash] the columns to insert
|
92
|
+
# @return [Row, nil] the row inserted, or nil if the row was empty
|
93
|
+
def insert!(args = {})
|
94
|
+
row = Row.new(self, @table.size)
|
95
|
+
args.each_pair do |col, value|
|
96
|
+
# If there's a primary_key defined
|
97
|
+
if has_primary_key? and columns.include? col and @primary_key == col
|
98
|
+
@pk_map[value] = @table.size
|
99
|
+
end
|
100
|
+
row[col] = value
|
101
|
+
end
|
102
|
+
unless row.empty?
|
103
|
+
@table << row
|
104
|
+
row
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Method_missing is used to dispatch to find_by_* and where_*_is
|
109
|
+
# @param method_sym [Symbol] the method called
|
110
|
+
def method_missing(method_sym, *arguments, &block)
|
111
|
+
# the first argument is a Symbol, so you need to_s it if you want to pattern match
|
112
|
+
if method_sym.to_s =~ /^find_by_(.*)$/
|
113
|
+
find_by($1.to_sym => arguments.first)
|
114
|
+
elsif method_sym.to_s =~ /^where_(.*)_is$/
|
115
|
+
where($1.to_sym => arguments.first)
|
116
|
+
else
|
117
|
+
super
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# The number of rows in the table
|
122
|
+
# @return [Fixnum] the size
|
123
|
+
def size
|
124
|
+
@table.size
|
125
|
+
end
|
126
|
+
|
127
|
+
alias_method :count, :size
|
128
|
+
|
129
|
+
# Finds a row by searching based on primary key
|
130
|
+
# @param pk [Object] The primary key to look up using
|
131
|
+
# @return [Row,nil] The row that matches
|
132
|
+
def find(pk)
|
133
|
+
if has_primary_key?
|
134
|
+
idx = @pk_map[pk]
|
135
|
+
@table[idx]
|
136
|
+
else nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Search for rows based on given data
|
141
|
+
# @param args [Hash<Symbol, Object>] the data to search for
|
142
|
+
# @return [Array<Row>, nil] the matching rows, can be nil
|
143
|
+
def where(args)
|
144
|
+
# Try and find a primary key
|
145
|
+
if has_primary_key? and args.keys.include? @primary_key
|
146
|
+
idx = @pk_map[args[@primary_key]]
|
147
|
+
return [@table[idx]]
|
148
|
+
end
|
149
|
+
indexed_cols = find_valid_indices(args.keys)
|
150
|
+
|
151
|
+
results = @table.to_set
|
152
|
+
|
153
|
+
# First restrict the query as far as we can with indices
|
154
|
+
unless indexed_cols.empty?
|
155
|
+
indexed_cols.each do |col|
|
156
|
+
results = restrict_with_index(col, args[col], results)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Then, perform table scans for the rest of the restrictions
|
161
|
+
# Removed the indexed columns
|
162
|
+
args = args.reject { |col, val| indexed_cols.include? col }
|
163
|
+
#slow O(N) find
|
164
|
+
args.each_pair do |col, val|
|
165
|
+
results = restrict_with_table_scan(col, val, results)
|
166
|
+
end
|
167
|
+
results.to_a
|
168
|
+
end
|
169
|
+
|
170
|
+
# Find a row based on the data given
|
171
|
+
# @param args [Hash<Symbol, Object>] the data to search for
|
172
|
+
# @return [Row, nil] the matching row, can be nil
|
173
|
+
def find_by(args)
|
174
|
+
where(args).first
|
175
|
+
end
|
176
|
+
|
177
|
+
# Scan the table to find rows
|
178
|
+
# @param args [Hash<Symbol, Object>] the rows to find
|
179
|
+
# @return [Array<Row>] the rows found
|
180
|
+
def table_scan(args)
|
181
|
+
results = @table.to_set
|
182
|
+
options.each_pair do |col, value|
|
183
|
+
results = restrict_with_table_scan(col, value, results)
|
184
|
+
end
|
185
|
+
results.to_a
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set the primary key of the table.
|
189
|
+
# @param column [Symbol] the column to be primary key
|
190
|
+
# @return [Symbol, nil] the primary key assigned. can be nil if the column doesn't exist
|
191
|
+
def set_primary_key!(column)
|
192
|
+
unless @columns.has_key? column
|
193
|
+
return nil
|
194
|
+
end
|
195
|
+
@pk_map = {}
|
196
|
+
@primary_key = column
|
197
|
+
end
|
198
|
+
|
199
|
+
# Does this table have a primary key?
|
200
|
+
# @return [Bool] whether or not there is a primary key
|
201
|
+
def has_primary_key?
|
202
|
+
not @primary_key.nil?
|
203
|
+
end
|
204
|
+
|
205
|
+
protected
|
206
|
+
def get_column_id(name)
|
207
|
+
id = @columns[name]
|
208
|
+
if id.nil?
|
209
|
+
raise ArgumentError, "No such column #{name}"
|
210
|
+
end
|
211
|
+
id
|
212
|
+
end
|
213
|
+
|
214
|
+
def find_valid_indices(cols)
|
215
|
+
@indices.intersection(cols).to_a
|
216
|
+
end
|
217
|
+
|
218
|
+
def restrict_with_index(key, value, initial=@table.to_set)
|
219
|
+
idx_map = instance_variable_get("@#{key}_index_map".to_sym)
|
220
|
+
unless idx_map.has_key? value
|
221
|
+
return Set.new
|
222
|
+
end
|
223
|
+
row_idxs = idx_map[value]
|
224
|
+
if row_idxs.nil?
|
225
|
+
return Set.new
|
226
|
+
end
|
227
|
+
rows = @table.values_at *row_idxs
|
228
|
+
initial.intersection rows
|
229
|
+
end
|
230
|
+
|
231
|
+
def restrict_with_table_scan(col, value, initial=@table.to_set)
|
232
|
+
initial.keep_if { |row| row[col] == value }
|
233
|
+
end
|
234
|
+
|
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
|
+
# Scan the table and add all the rows to the index
|
243
|
+
# @param column [Symbol] the column to construct the index for
|
244
|
+
def build_index(column)
|
245
|
+
ivar_name = "@#{column}_index_map".to_sym
|
246
|
+
@table.each_with_index do |row, idx|
|
247
|
+
val = row[column]
|
248
|
+
unless val.nil?
|
249
|
+
if instance_variable_get(ivar_name)[val].nil?
|
250
|
+
instance_variable_get(ivar_name)[val] = Set.new
|
251
|
+
end
|
252
|
+
instance_variable_get(ivar_name)[val] =
|
253
|
+
instance_variable_get(ivar_name)[val] << idx
|
254
|
+
end
|
255
|
+
end
|
256
|
+
return
|
257
|
+
end
|
258
|
+
end
|
data/lib/chair.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Table do
|
4
|
+
subject(:table) { Table.new :id, :title, :author }
|
5
|
+
|
6
|
+
describe 'finds by' do
|
7
|
+
it 'primary key' do
|
8
|
+
table.set_primary_key! :title
|
9
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
10
|
+
expect(table.find('War and Peace').to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'index to use restrict_with_index' do
|
14
|
+
table.set_primary_key! :id
|
15
|
+
table.add_index! :title
|
16
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
17
|
+
expect(table).to receive(:restrict_with_index)
|
18
|
+
table.find_by(title: 'War and Peace')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'index with method_missing to use restrict_with_index' do
|
22
|
+
table.set_primary_key! :id
|
23
|
+
table.add_index! :title
|
24
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
25
|
+
expect(table).to receive(:restrict_with_index)
|
26
|
+
table.find_by_title('War and Peace')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'index to return a row' do
|
30
|
+
table.set_primary_key! :id
|
31
|
+
table.add_index! :title
|
32
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
33
|
+
row = table.find_by(title: 'War and Peace')
|
34
|
+
expect(row.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'index with method_missing to return a row' do
|
38
|
+
table.set_primary_key! :id
|
39
|
+
table.add_index! :title
|
40
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
41
|
+
row = table.find_by_title('War and Peace')
|
42
|
+
expect(row.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'table scan' do
|
46
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
47
|
+
expect(table.find_by_title('War and Peace').to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'searches using' do
|
52
|
+
it 'primary key' do
|
53
|
+
table.set_primary_key! :title
|
54
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
55
|
+
expect(table.where(title: 'War and Peace').first.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'dispatch with method_missing' do
|
59
|
+
table.set_primary_key! :title
|
60
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
61
|
+
expect(table.where_title_is('War and Peace').first.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'table scan' do
|
65
|
+
table.insert! id: 0, title: 'War and Peace', author: 'Leo Tolstoy'
|
66
|
+
expect(table.where_title_is('War and Peace').first.to_a).to eq([0, 'War and Peace', 'Leo Tolstoy'])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/table_spec.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Table do
|
4
|
+
describe 'initializes' do
|
5
|
+
it 'with 0 columns by default' do
|
6
|
+
table = Table.new
|
7
|
+
expect(table.columns.count).to eq(0)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'with 0 rows by default' do
|
11
|
+
table = Table.new
|
12
|
+
expect(table.size).to eq(0)
|
13
|
+
end
|
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
|
+
it 'with no primary key' do
|
21
|
+
table = Table.new
|
22
|
+
expect(table.has_primary_key?).to eq(false)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'with no indices' do
|
26
|
+
table = Table.new
|
27
|
+
expect(table.indices.size).to eq(0)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'adds a column' do
|
32
|
+
table = Table.new
|
33
|
+
expect {
|
34
|
+
table.add_column!(:title)
|
35
|
+
}.to change{ table.columns.count }.by(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'raises an ArgumentError if the name is not a symbol' do
|
39
|
+
table = Table.new
|
40
|
+
expect{
|
41
|
+
table.add_column!('Title')
|
42
|
+
}.to raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'doesn\'t add column with the same name' do
|
46
|
+
table = Table.new :title
|
47
|
+
expect {
|
48
|
+
table.add_column!(:title)
|
49
|
+
}.to_not change{ table.columns.count }
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'adds multiple columns' do
|
53
|
+
table = Table.new
|
54
|
+
expect {
|
55
|
+
table.add_columns! :title, :author
|
56
|
+
}.to change {table.columns.count}.by(2)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'inserts a row' do
|
60
|
+
table = Table.new :title
|
61
|
+
expect {
|
62
|
+
table.insert!({title: "Gone with the Wind"})
|
63
|
+
}.to change { table.size }.by(1)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'sets the primary key' do
|
67
|
+
table = Table.new :title
|
68
|
+
expect {
|
69
|
+
table.set_primary_key! :title
|
70
|
+
}.to change {table.primary_key}.from(nil).to(:title)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "doesn't set the primary key if it's not a column" do
|
74
|
+
table = Table.new
|
75
|
+
expect{
|
76
|
+
table.set_primary_key! :title
|
77
|
+
}.not_to change{table.primary_key}.from(nil)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'adds an index' do
|
81
|
+
table = Table.new :title
|
82
|
+
expect {
|
83
|
+
table.add_index! :title
|
84
|
+
}.to change {table.indices.size}.by(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'deletes an index' do
|
88
|
+
table = Table.new :title
|
89
|
+
table.add_index! :title
|
90
|
+
expect {
|
91
|
+
table.remove_index! :title
|
92
|
+
}.to change {table.indices.size}.by(-1)
|
93
|
+
end
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chair
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Katherine Whitlock
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A pure ruby table implementation with arbitray column indices
|
56
|
+
email: toroidalcode@gmail.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- ".gitignore"
|
62
|
+
- ".rspec"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- chairs.gemspec
|
69
|
+
- demo.rb
|
70
|
+
- lib/chair.rb
|
71
|
+
- lib/chair/row.rb
|
72
|
+
- lib/chair/table.rb
|
73
|
+
- lib/chair/version.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
- spec/table_search_spec.rb
|
76
|
+
- spec/table_spec.rb
|
77
|
+
homepage: http://github.com/toroidal-code/chair
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.2.2
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Tables!
|
101
|
+
test_files:
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/table_search_spec.rb
|
104
|
+
- spec/table_spec.rb
|
105
|
+
has_rdoc:
|