chair 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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