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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1c4de86a3818d3b96fce864c6bbdc051f57ee86
4
- data.tar.gz: bd291bf233af78c5e798155e0ec31ee8d31d6d1c
3
+ metadata.gz: 415c4dfeb44887b370a98e38d6b9dd0b97b734e2
4
+ data.tar.gz: 3fb7024b049ee94c0534f1c0e79376992b07d899
5
5
  SHA512:
6
- metadata.gz: 66005c8cbe09b4edef9d2ffdb7c41d62b9dc13f3221d1a730be1b293165d189fb001832d8aaf622bbbe0cfb6bead63061f8bbf48412706c0bf60fab7dec1993c
7
- data.tar.gz: 3e904cdd29fe3265eae4a251bbad08be501f40b361265782bafd56c8942ead50162c50c922f2a53d05dbd34b5e8db787ae5f924c6df62ecf8554f4545ed7daaf
6
+ metadata.gz: 99d8558f1baa52dfbfb986bdbe5848f37ba8c5210052b71af04fb5d57f89bbab9e5684a19ffbca1bffe8c1cbf26f66d60942dd70c8372c884cb069611f440edd
7
+ data.tar.gz: 9952115d82340884b0ccb5fdf35f2d2df944e2e6184b312db02cf7042df400a28192af68db21090e70538122558e94233bcce2cd83176d14b4db601e59f15132
data/.travis.yml CHANGED
@@ -9,4 +9,4 @@ rvm:
9
9
 
10
10
  addons:
11
11
  code_climate:
12
- repo_token: e3c6971d651290d13581ade555fc07abb89a8bf956342399f1c9949c51e34c4c
12
+ repo_token: 0be985c4b01192cc225409eb0adc6d1742064086c9658a2f88000e50c0e20527
data/Gemfile CHANGED
@@ -1,8 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in chairs.gemspec
3
+ # Specify your gem's dependencies in chair.gemspec
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem "codeclimate-test-reporter", group: :test, require: nil
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/chairs/master.svg?style=flat)](https://travis-ci.org/toroidal-code/chairs)
3
- [![Code Climate](https://img.shields.io/codeclimate/github/toroidal-code/chairs.png?style=flat)](https://codeclimate.com/github/toroidal-code/chairs)
4
- [![Coverage](https://img.shields.io/codeclimate/coverage/github/toroidal-code/chairs.png?style=flat)](https://codeclimate.com/github/toroidal-code/chairs)
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 'Tables'?
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 Table class for Ruby, with an associated Row class.
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 'chairs'
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 chairs
24
+ $ gem install chair
24
25
 
25
26
  ## Usage
26
27
 
27
28
  ```irb
28
- >> require 'chairs'
29
+ >> require 'chair'
29
30
  => true
30
- >> t = Table.new :title
31
- => #<Table:0x0000000162ee08>
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('An Abundance of Katherines')[:author] = 'John Green'
49
- => "John Green"
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/chairs/fork )
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 Table
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 = Set.new
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 [Bool] whether or not we added the index
58
+ # @return [Symbol] the column name of the index
57
59
  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
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
- unless @indices.include? column
66
- @indices = @indices << column
67
- result ||= true
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 [Bool] whether or not the column was successfully removed
72
+ # @return [Symbol] the column that was removed
77
73
  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
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
- if @indices.include? column
84
- @indices = @indices.delete column
85
- result ||= true
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 columns to insert
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
- 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
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
- options.each_pair do |col, value|
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
- protected
206
- def get_column_id(name)
207
- id = @columns[name]
208
- if id.nil?
209
- raise ArgumentError, "No such column #{name}"
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
- id
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.intersection(cols).to_a
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
- if row_idxs.nil?
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
- ivar_name = "@#{column}_index_map".to_sym
268
+ map = {}
246
269
  @table.each_with_index do |row, idx|
247
270
  val = row[column]
248
271
  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
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 interal 2D array
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
- @row = []
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 [](col)
15
- idx = @table.send(:get_column_id, col)
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 col [Symbol] the column name to add to
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 []=(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
26
+ def []=(column, value)
27
+ if @table.indices.include? column
28
+ add_to_index(column, value)
31
29
  end
32
- @row[idx] = value
30
+ @attributes[column] = value
33
31
  end
34
32
 
35
33
  def empty?
36
- @row.empty?
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
- map = {}
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
- @row
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 eql?(other)
60
- @row.eql?(other.instance_variable_get("@row"))
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
- module Chair
2
- VERSION = "1.0.2"
1
+ class Chair
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/chair.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'chair/version'
2
- require 'chair/table'
2
+ require 'chair/chair'
3
3
  require 'chair/row'
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
@@ -1,3 +1,6 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
1
4
  require 'codeclimate-test-reporter'
2
5
  CodeClimate::TestReporter.start
3
6
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Table do
4
- subject(:table) { Table.new :id, :title, :author }
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 Table do
3
+ describe Chair do
4
4
  describe 'initializes' do
5
- it 'with 0 columns by default' do
6
- table = Table.new
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 0 rows by default' do
11
- table = Table.new
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 = Table.new
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 = Table.new
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 = Table.new
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 = Table.new
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 'doesn\'t add column with the same name' do
46
- table = Table.new :title
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 = Table.new
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
- 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)
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
- it 'sets the primary key' do
67
- table = Table.new :title
68
- expect {
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
- }.to change {table.primary_key}.from(nil).to(:title)
118
+ }.not_to change{table.primary_key}.from(nil)
71
119
  end
72
120
 
73
- it "doesn't set the primary key if it's not a column" do
74
- table = Table.new
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
- it 'adds an index' do
81
- table = Table.new :title
82
- expect {
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
- }.to change {table.indices.size}.by(1)
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
- it 'deletes an index' do
88
- table = Table.new :title
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.remove_index! :title
92
- }.to change {table.indices.size}.by(-1)
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.2
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
- - chairs.gemspec
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
@@ -1,4 +0,0 @@
1
- table = Table.new [:title, :author]
2
- table.where do |row|
3
- row[:title]
4
- end