Class: Chair

Inherits:
Object
  • Object
show all
Defined in:
lib/chair/chair.rb,
lib/chair/version.rb

Overview

Author:

Defined Under Namespace

Classes: Row

Constant Summary collapse

VERSION =
"1.1.3"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*columns) ⇒ Chair

Creates a new Table object

Parameters:

  • columns (Symbol)

    columns to insert into the table at initialization



11
12
13
14
15
16
17
18
# File 'lib/chair/chair.rb', line 11

def initialize(*columns)
  @table = []
  @columns = {}
  @columns_id_counter = 0
  add_columns!(*columns)
  @primary_key = nil
  @indices = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &block) ⇒ Object

Method_missing is used to dispatch to find_by_* and where_*_is

Parameters:

  • method_sym (Symbol)

    the method called



106
107
108
109
110
111
112
113
114
115
# File 'lib/chair/chair.rb', line 106

def method_missing(method_sym, *arguments, &block)
  # the first argument is a Symbol, so you need to_s it if you want to pattern match
  if method_sym.to_s =~ /^find_by_(.*)$/
    find_by($1.to_sym => arguments.first)
  elsif method_sym.to_s =~ /^where_(.*)_is$/
    where($1.to_sym => arguments.first)
  else
    super
  end
end

Instance Attribute Details

#indicesSet<Symbol> (readonly)

the set of indices for the table

Returns:

  • (Set<Symbol>)

    the current value of indices



6
7
8
# File 'lib/chair/chair.rb', line 6

def indices
  @indices
end

#primary_keySymbol (readonly)

the primary key of the table

Returns:

  • (Symbol)

    the current value of primary_key



6
7
8
# File 'lib/chair/chair.rb', line 6

def primary_key
  @primary_key
end

Instance Method Details

#add_column!(column) ⇒ Bool

Add a new column to the table.

Parameters:

  • column (Symbol)

    the column name to add

Returns:

  • (Bool)

    whether or not we successfully added the new column

Raises:

  • (ArgumentError)

    if the column name is not a symbol



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/chair/chair.rb', line 24

def add_column!(column)
  case column
  when Symbol
  else raise ArgumentError, "Column name should be Symbol not #{column.class}"
  end

  if @columns.include? column
    false
  else
    @columns[column] = @columns_id_counter
    @columns_id_counter += 1
    true
  end
end

#add_columns!(*columns) ⇒ Bool

Add multiple columns to the table

Parameters:

  • columns (Symbol)

    the columns to add

Returns:

  • (Bool)

    whether or not all of the columns were successfully added



42
43
44
45
46
# File 'lib/chair/chair.rb', line 42

def add_columns!(*columns)
  result = true
  columns.each { |c| result &&= add_column!(c) }
  result
end

#add_index!(column) ⇒ Symbol

Add a new index to the table

Parameters:

  • column (Symbol)

    the column to create the index on

Returns:

  • (Symbol)

    the column name of the index



59
60
61
62
63
64
65
66
67
68
# File 'lib/chair/chair.rb', line 59

def add_index!(column)
  check_column_exists(column)
  if @indices.include?(column) or instance_variable_defined?("@#{column}_index_map".to_sym)
    raise ArgumentError, "Column #{column.inspect} is already an index"
  end

  @indices[column] = "@#{column}_index_map".to_sym
  instance_variable_set(@indices[column], build_index(column))
  column
end

#allArray<Chair::Row>

Retrieve all rows

Returns:

  • (Array<Chair::Row>)

    all of the rows in the table



240
241
242
# File 'lib/chair/chair.rb', line 240

def all
  @table
end

#columnsArray<Symbol>

Retrieve the current columns Order is guaranteed to be the order that the columns were inserted in, i.e., left to right

Returns:

  • (Array<Symbol>)

    the columns in the table



52
53
54
# File 'lib/chair/chair.rb', line 52

def columns
  @columns.keys
end

#find(pk) ⇒ Row? Also known as: []

Finds a row by searching based on primary key

Parameters:

  • pk (Object)

    The primary key to look up using

Returns:

  • (Row, nil)

    The row that matches



132
133
134
135
136
137
138
# File 'lib/chair/chair.rb', line 132

def find(pk)
  if has_primary_key?
    idx = @pk_map[pk]
    @table[idx]
  else nil
  end
end

#find_by(args) ⇒ Row?

Find a row based on the data given

Parameters:

  • args (Hash<Symbol, Object>)

    the data to search for

Returns:

  • (Row, nil)

    the matching row, can be nil



174
175
176
# File 'lib/chair/chair.rb', line 174

def find_by(args)
  where(args).first
end

#firstChair::Row

Retrieve the first row in the table

Returns:



246
247
248
# File 'lib/chair/chair.rb', line 246

def first
  @table.first
end

#has_primary_key?Bool

Does this table have a primary key?

Returns:

  • (Bool)

    whether or not there is a primary key



203
204
205
# File 'lib/chair/chair.rb', line 203

def has_primary_key?
  not @primary_key.nil?
end

#insert!(args) ⇒ Row?

Insert a new row of data into the column

Parameters:

  • args (Hash, Array)

    the data to insert

Returns:

  • (Row, nil)

    the row inserted, or nil if the row was empty



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/chair/chair.rb', line 86

def insert!(args)
  args = process_incoming_data(args)
  if has_primary_key?
    unless args.include? @primary_key
      # If our table has a primary key, but can't find it in the data
      raise ArgumentError, 'Missing primary key in record to be inserted'
    end
    val = args[@primary_key]
    if @pk_map.has_key?(val)
      raise RuntimeError, "Primary key #{val.inspect} already exists in table"
    end
    @pk_map[val] = @table.size
  end
  row = Row.new(self, @table.size, args)
  @table << row
  row
end

#inspectObject



256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/chair/chair.rb', line 256

def inspect
  # Use the table's column list to order our columns
  inspection = []
  inspection << "primary_key: #{@primary_key.inspect}" if has_primary_key?
  inspection << "indices: #{@indices.keys.inspect}" unless @indices.empty?
  inspection << "columns: #{@columns.keys.inspect}" unless @columns.empty?
  inspection = inspection.compact.join(', ')
  unless inspection.empty?
    inspection.insert 0, ' '
  end
  "#<#{self.class}#{inspection}>"
end

#lastChair::Row

Retrieve the last row in the table

Returns:



252
253
254
# File 'lib/chair/chair.rb', line 252

def last
  @table.last
end

#merge!(column, map, opts = {}) ⇒ Object

Merge the table with a map of primary keys to values. There must be a primary key.

Parameters:

  • column (Symbol)

    the column to insert the values into

  • map (Hash<Object, Object>)

    a mapping of primary_key values to other values

  • opts (Bool) (defaults to: {})

    :overwrite if a value already exists in the row, overwrite it

  • opts (Bool) (defaults to: {})

    :create_row if the row doesn’t already exist, create it



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/chair/chair.rb', line 212

def merge!(column, map, opts = {})
  unless has_primary_key?
    raise 'No primary key exists for this table'
  end
  # For each key, value
  map.each_pair do |key, val|
    if @pk_map.include? key
      # if we do, check if the row has a value in the column
      row = @table[@pk_map[key]]
      # if it does, can we overwrite?
      if row.has_attribute?(column) and not opts[:overwrite]
        raise "Value already exists in table for primary key #{key.inspect} and column #{column.inspect}"
      end
      # if so, overwrite
      row[column] = val
    else # Check if we have the key in our pk_map. if not,
      if opts[:create_row] # if we can create rows,
        insert! @primary_key => key, column => val # create a row
      else
        raise "No such row with primary key #{key.inspect} exists" # or raise an error
      end
    end
  end
  self
end

#remove_index!(column) ⇒ Symbol

Remove an index from the table

Parameters:

  • column (Symbol)

    the column to remove the index from

Returns:

  • (Symbol)

    the column that was removed



73
74
75
76
77
78
79
80
81
# File 'lib/chair/chair.rb', line 73

def remove_index!(column)
  check_column_exists(column)
  unless @indices.include?(column) or instance_variable_defined?("@#{column}_index_map".to_sym)
    raise ArgumentError, "Column #{column} is not indexed"
  end
  ivar = @indices.delete column
  remove_instance_variable(ivar) unless ivar.nil?
  column
end

#set_primary_key!(column) ⇒ Symbol?

Set the primary key of the table.

Parameters:

  • column (Symbol)

    the column to be primary key

Returns:

  • (Symbol, nil)

    the primary key assigned. can be nil if the column doesn’t exist



192
193
194
195
196
197
198
199
# File 'lib/chair/chair.rb', line 192

def set_primary_key!(column)
  unless has_primary_key?
    check_column_exists column
    remove_index!(column) if indices.include? column
    @pk_map = build_pk_map column
    @primary_key = column
  end
end

#sizeFixnum Also known as: count

The number of rows in the table

Returns:

  • (Fixnum)

    the size



119
120
121
# File 'lib/chair/chair.rb', line 119

def size
  @table.size
end

#table_scan(args) ⇒ Array<Row>

Scan the table to find rows

Parameters:

  • args (Hash<Symbol, Object>)

    the rows to find

Returns:

  • (Array<Row>)

    the rows found



181
182
183
184
185
186
187
# File 'lib/chair/chair.rb', line 181

def table_scan(args)
  results = @table.to_set
  args.each_pair do |col, value|
    results = restrict_with_table_scan(col, value, results)
  end
  results.to_a
end

#where(args) ⇒ Array<Row>?

Search for rows based on given data

Parameters:

  • args (Hash<Symbol, Object>)

    the data to search for

Returns:

  • (Array<Row>, nil)

    the matching rows, can be nil



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/chair/chair.rb', line 144

def where(args)
  # Try and find a primary key
  if has_primary_key? and args.keys.include? @primary_key
    idx = @pk_map[args[@primary_key]]
    return [@table[idx]]
  end
  indexed_cols = find_valid_indices(args.keys)

  results = @table.to_set

  # First restrict the query as far as we can with indices
  unless indexed_cols.empty?
    indexed_cols.each do |col|
      results = restrict_with_index(col, args[col], results)
    end
  end

  # Then, perform table scans for the rest of the restrictions
  # Removed the indexed columns
  args = args.reject { |col, val| indexed_cols.include? col }
  #slow O(N) find
  args.each_pair do |col, val|
    results = restrict_with_table_scan(col, val, results)
  end
  results.to_a
end