christiank-turntable 0.4.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/turntable.rb +109 -198
  2. metadata +2 -2
data/turntable.rb CHANGED
@@ -1,221 +1,132 @@
1
1
  #
2
- # turntable v0.4.4
3
- # Christian Koch < ckoch002@student.ucr.edu >
4
- #
5
- # This is free software. You are permitted to modify, implement, and otherwise
6
- # use this source code freely (as in beer and freedom).
2
+ # turntable v0.5
3
+ # Christian Koch <ckoch002@student.ucr.edu>
7
4
  #
8
5
 
9
- class Turntable
10
-
11
- @@version = '0.4.4'
12
-
13
- # Creates a new Turntable object and database file.
14
- # Requires a filename and at least one column name.
15
- def initialize filename, *args
16
- raise 'Turntable.new requires a filename and at least one column name' if args.empty?
17
-
18
- @columns = []
19
- @table = []
20
- @filename = File.expand_path(filename)
21
-
22
- args.each do |column|
23
- @columns.push column
24
- end
25
-
26
- self.save_to @filename
27
- end
28
-
29
- # Loads an external database file and returns the Turntable object.
30
- def Turntable.load filename
31
- raise "\"#{filename}\": file does not exist" unless File.exists?(filename)
32
-
33
- @filename = File.expand_path(filename)
34
- f = File.open @filename, 'r'
35
- return Marshal.load(f)
36
- f.close
37
- end
38
-
39
- # Returns the version number.
40
- def Turntable.version
41
- @@version
42
- end
43
-
44
- # Reader methods for the object's columns and filename.
45
- attr_reader :columns
46
- attr_reader :filename
47
-
48
- # Pushes a new column. Fills the new fields with the empty string
49
- # (because filling them with nil creates issues). Returns the the new table.
50
- def add_column column_name
51
- @columns.push column_name
52
- @table.each { |row| row[column_name] = '' }
53
- end
54
-
55
- # Returns true or false, depending on whether the result is empty.
56
- def contains? hash
57
- !self.select(hash).empty?
6
+ require 'pstore'
7
+ require 'ostruct'
8
+
9
+ class Turntable < PStore
10
+
11
+ @@version = 'v0.5'
12
+
13
+ # Creates a new Turntable object and corresponding PStore.
14
+ def initialize file, *columns
15
+ raise ArgumentError, 'Turntable.new() requires a filename and at least one column' if columns.empty?
16
+
17
+ super(file)
18
+ @filename = File.expand_path(file)
19
+ @columns = columns
20
+
21
+ # A quick hack for loading databases
22
+ @columns = columns[0] if columns.length == 1
58
23
  end
59
-
60
- # Deletes a row as dictated by a hash {:id => some_value}.
61
- def delete hash
62
- raise 'delete() requires :id => some_value' unless hash.has_key?(:id)
63
-
64
- before_table = @table.dup
65
-
66
- # Delete the table for real here:
67
- what_wasnt_deleted = @table.delete_if { |row| row[:id] == hash[:id] }
68
- self.save_to @filename
69
-
70
- # Return the deleted row here:
71
- return before_table - what_wasnt_deleted
24
+
25
+ # Loads an existing database if it exists, complains otherwise.
26
+ # Tries to reconstruct what @columns should be by looking at the first row.
27
+ def Turntable.load file
28
+ raise "#{file}: file cannot be found" unless File.exists?(file)
29
+
30
+ @filename = File.expand_path(file)
31
+ db = Turntable.new @filename, :foo
32
+ @columns = db.transaction { db[0].columns }
33
+
34
+ return Turntable.new(@filename, @columns)
72
35
  end
73
-
74
- # Eliminates an entire column.
75
- def drop_column column_name
76
- @columns.delete_if { |column| column == column_name }
77
- @table.each { |row| row.delete column_name }
36
+
37
+ # Reader methods for this database's metadata.
38
+ attr_reader :filename, :columns
39
+ def version; @@version; end
40
+
41
+ # This hack should eliminate the need for transactions
42
+ #def in_transaction; true; end
43
+ #def in_transaction_wr; true; end
44
+
45
+ # The next best thing is to make transactions more domain-specific...
46
+ alias :update :transaction
47
+
48
+ # Returns the entire database as an array.
49
+ def all
50
+ as_array = []
51
+
52
+ self.transaction do
53
+ self.roots.each { |root| as_array.push self[root] }
54
+ end
55
+
56
+ return as_array
78
57
  end
79
-
80
- # Inserts a new row in the database. It requires that the number of arguments
81
- # passed in equal the number of columns, just like any SQL INSERT.
82
- # Columns named :id function like AUTOINCREMENT fields.
58
+ alias :select_all :all
59
+
60
+ # Pushes self with a new row.
61
+ # Columns named :id are autoincrement fields.
83
62
  def insert *args
84
- unless args.length == @columns.length
85
- raise "wrong number of arguments (#{args.length} for #{@columns.length})"
86
- end
87
-
88
- row = {}
89
-
90
- @columns.each do |column_name|
91
- if column_name == :id
92
- if @table.empty?
93
- row[:id] = 0
94
- else
95
- row[:id] = @table.last[:id] + 1
96
- end
97
-
98
- args.shift
99
- else
100
- row[column_name] = args.shift
101
- end
63
+ if args.length != @columns.length
64
+ raise ArgumentError, "wrong number of arguments (#{args.length} for #{columns.length})"
102
65
  end
103
-
104
- @table.push row
105
- self.save_to @filename
66
+
67
+ row = Row.new
68
+ self.all.empty? ? (row.id = 0) : (row.id = self.all.last.id + 1)
69
+ row.columns = @columns
70
+ @columns.each { |column| eval "row.#{column} = args.shift" }
71
+
72
+ self.transaction { self[row.id] = row }
73
+
106
74
  return row
107
75
  end
108
-
109
- # Customize inspect() so dealing with Turntable in IRB is much cleaner.
110
- def inspect
111
- self.to_s
112
- end
113
-
114
- # Print a "plain text" table to STDOUT
115
- # TODO: create a table.each.columns.each protected method, or something
116
- def puts_formatted
117
- warn 'Warning: puts_formatted() is deprecated, use to_stdout()'
118
- to_stdout
76
+
77
+ # The select statement. Equivalent to self.all.select() if there's a block,
78
+ # otherwise just return the specified row. In a perfect world, we should be
79
+ # able to access individual rows with just db[x].
80
+ def select *id
81
+ if block_given?
82
+ result = []
83
+ self.all.each { |row| result.push(row) if yield(row) }
84
+ return result
85
+ else
86
+ raise ArgumentError, 'select requires a block or 1 id number' if id.length != 1
87
+ return self.all[ id[0] ]
88
+ end
119
89
  end
120
-
90
+
91
+ # Prints a plain text representation of the table to STDOUT
121
92
  def to_stdout
122
- @table.each do |row|
93
+ self.all.each do |row|
94
+ print '|'
95
+ print row.id
123
96
  @columns.each do |column|
124
- print "|#{row[column]}"
97
+ print '|'
98
+ print eval("row.#{column}")
125
99
  end
126
- puts "|"
100
+ puts '|'
127
101
  end
128
102
  return nil
129
103
  end
130
-
131
- # Returns a subset of the table which satisfies given conditions.
132
- # TODO: get compound conditions to work somehow
133
- def select hash
134
- result_set = []
135
-
136
- hash.each do |column, value|
137
- this_set = @table.collect do |row|
138
- row = row if row[column] == value
139
- end
140
-
141
- result_set += this_set.compact
142
- end
143
-
144
- return result_set
145
- end
146
-
147
- # Returns the entire table, an array of hashes.
148
- def select_all
149
- @table
150
- end
151
-
152
- alias :all :select_all
153
-
154
- # Replaces many columns at a time, but currently only on one row at a time.
155
- def update hash
156
- raise 'update() requires at least :id => some_value' unless hash.has_key?(:id)
157
-
158
- id = hash[:id]
159
- hash.delete_if { |column, value| column == :id }
160
-
161
- hash.each do |column, value|
162
- @table[id][column] = value
163
- end
164
-
165
- self.save_to @filename
166
- return @table[id]
167
- end
168
-
169
- protected
170
- # Marshals the database object to an external file.
171
- def save_to filename
172
- if File.exists? filename
173
- f = File.open filename, 'w'
174
- else
175
- f = File.new filename, 'w'
176
- end
177
-
178
- Marshal.dump self, f
179
- f.close
180
- end
104
+
181
105
  end
182
106
 
183
- class Array
184
-
185
- # Rearranges an array of hashes according to a specified column name.
186
- # TODO: how to check for @columns, even though it's a Turntable variable?
187
- def order_by column_name
188
-
189
- # Convert all row-hashes into arrays
190
- rearranged_table = self.collect { |row| row.to_a }
191
-
192
- # Convert all column-name-symbols into strings,
193
- # then switch the first element of each row the desired order_by column
194
- # (because the first element is where sort() looks)
195
- rearranged_table.each do |row|
196
- row.each { |column| column[0] = column[0].to_s if (column[0].class == Symbol) }
197
- row.switch 0, ( row.index(row.assoc(column_name.to_s)) )
198
- end
199
-
200
- # Sort everything properly, and convert it back to hashes and symbols
201
- rearranged_table.sort.collect do |row|
202
- this_row_hash = {}
203
- this_column_hash = {}
204
-
205
- row.each do |column|
206
- column[0] = column[0].to_sym
207
- this_column_hash[ column[0] ] = column[1]
208
- end
107
+ class Row < OpenStruct
209
108
 
210
- this_row_hash.update this_column_hash
109
+ # Custom inspect() to make dealing with rows in IRB cleaner
110
+ def inspect
111
+ str = "#<#{self.class}"
112
+ Thread.current[InspectKey] ||= []
113
+ if Thread.current[InspectKey].include?(self) then
114
+ str << " ..."
115
+ else
116
+ for k,v in @table
117
+ Thread.current[InspectKey] << v
118
+ begin
119
+ str << " #{k}=#{v.inspect}," unless k == :columns
120
+ ensure
121
+ Thread.current[InspectKey].pop
122
+ end
123
+ end
211
124
  end
125
+ str[-1, 1] = ""
126
+ str << ">"
212
127
  end
213
-
214
- # Exchanges two elements' positions within an array.
215
- def switch index1, index2
216
- old_array = self.dup
217
- self[index1] = old_array[index2]
218
- self[index2] = old_array[index1]
219
- end
220
-
128
+
221
129
  end
130
+
131
+ # Quick hack to shut up Object#id deprecation warnings
132
+ class Object; undef id; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: christiank-turntable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: "0.5"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Koch
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-31 00:00:00 -07:00
12
+ date: 2009-06-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15