dbf 0.4.7 → 0.5.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.
data/History.txt ADDED
@@ -0,0 +1,9 @@
1
+ == 0.5.0 / 2007-05-25
2
+
3
+ * New find method
4
+ * Full compatibility with the two flavors of memo file
5
+ * Two modes of operation:
6
+ * In memory (default): All records are loaded into memory on the first
7
+ request. Records are retrieved from memory for all subsequent requests.
8
+ * File I/O: All records are retrieved from disk on every request
9
+ * Improved documentation and more usage examples
data/Manifest.txt ADDED
@@ -0,0 +1,28 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ benchmarks/performance.rb
6
+ benchmarks/seek_benchmark.rb
7
+ lib/dbf.rb
8
+ lib/dbf/field.rb
9
+ lib/dbf/globals.rb
10
+ lib/dbf/reader.rb
11
+ lib/dbf/record.rb
12
+ spec/field_spec.rb
13
+ spec/fixtures/dbase_iii_memo_schema.rb
14
+ spec/reader_spec.rb
15
+ spec/record_spec.rb
16
+ spec/spec_helper.rb
17
+ test/common.rb
18
+ test/databases/dbase_iii.dbf
19
+ test/databases/dbase_iii_memo.dbf
20
+ test/databases/dbase_iii_memo.dbt
21
+ test/databases/foxpro.dbf
22
+ test/databases/foxpro.fpt
23
+ test/databases/visual_foxpro.dbf
24
+ test/databases/visual_foxpro.fpt
25
+ test/dbase_iii_memo_read_test.rb
26
+ test/dbase_iii_read_test.rb
27
+ test/foxpro_read_test.rb
28
+ test/visual_foxpro_read_test.rb
data/README.txt CHANGED
@@ -2,47 +2,59 @@
2
2
 
3
3
  DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro database files. It is written completely in Ruby and has no external dependencies.
4
4
 
5
+ Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org, www.infused.org>
6
+
7
+ * Official project page: http://rubyforge.org/projects/dbf
8
+ * API Documentation: http://dbf.rubyforge.org/docs
9
+ * To report bugs: http://www.rubyforge.org/tracker/?group_id=2009
10
+ * Questions: Email keithm@infused.org and put DBF somewhere in the subject
11
+ line
12
+
5
13
  == Features
6
14
 
7
15
  * No external dependencies
8
- * DB fields are type cast
16
+ * Fields are type cast to the appropriate Ruby types
9
17
  * Date/Time fields are returned as either a Time or Date object. Date
10
- will only be used if the date is outside the range for Time.
18
+ will only be used if the date is out of range for Ruby's built in Time
19
+ class.
20
+ * Ability to dump the database schema in the portable ActiveRecord::Schema
21
+ format.
11
22
 
12
23
  == Installation
13
-
24
+
14
25
  gem install dbf
15
26
 
16
- == Usage
27
+ == Basic Usage
28
+
29
+ require 'rubygems'
30
+ require 'dbf'
17
31
 
18
32
  reader = DBF::Reader.new("old_data.dbf")
19
33
 
34
+ # Print the 'name' field from record number 4
35
+ puts reader.record(4)['name']
36
+
37
+ # Print the 'name' and 'address' fields from each record
20
38
  reader.records.each do |record|
21
39
  puts record['name']
22
40
  puts record['email']
23
41
  end
24
-
25
- puts reader.records[4]['name']
26
- puts reader.record(4)['name']
27
-
28
- === A note on record vs. records
29
-
30
- DBF::Reader#records is an in-memory array of all rows in the database. All
31
- rows are loaded the first time that the method is called. Subsequent calls
32
- retrieve the row from memory.
33
-
34
- DBF::Reader#record retrieves the requested row from the database each time
35
- it is called.
36
-
37
- Using records is probably faster most of the time. Record is more appropriate
38
- for very large databases where you don't want the whole db loaded into memory.
39
42
 
43
+ # Find records
44
+ reader.find :all, :first_name => 'Keith'
45
+ reader.find :all, :first_name => 'Keith', :last_name => 'Morrison'
46
+ reader.find :first, :first_name => 'Keith'
47
+ reader.find(10)
48
+
40
49
  == Limitations and known bugs
41
50
 
42
- * DBF is read-only. Writing to the database has not yet been implemented.
51
+ * DBF is read-only at the moment
52
+ * Index files are not utilized
43
53
 
44
54
  == License
45
55
 
56
+ (The MIT Licence)
57
+
46
58
  Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
47
59
 
48
60
  Permission is hereby granted, free of charge, to any person
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'hoe'
2
2
  require 'spec/rake/spectask'
3
3
 
4
4
  PKG_NAME = "dbf"
5
- PKG_VERSION = "0.4.7"
5
+ PKG_VERSION = "0.5.0"
6
6
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
7
7
 
8
8
  Hoe.new PKG_NAME, PKG_VERSION do |p|
@@ -10,6 +10,7 @@ Hoe.new PKG_NAME, PKG_VERSION do |p|
10
10
  p.author = "Keith Morrison"
11
11
  p.email = "keithm@infused.org"
12
12
  p.summary = "A small fast library for reading dBase, xBase, Clipper and FoxPro database files."
13
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
13
14
  p.url = "http://dbf.rubyforge.org"
14
15
  p.need_tar = true
15
16
  p.need_zip = true
File without changes
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require 'benchmark'
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
4
+ require 'dbf'
5
+
6
+ puts
7
+ puts "Runs 5000 random row seeks first using the I/O based record(n) method and then using"
8
+ puts "using the array of records."
9
+ puts
10
+
11
+ iterations = 5000
12
+ Benchmark.bm(20) do |x|
13
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__), '..', 'test', 'databases', 'foxpro.dbf'))
14
+ max = @dbf.record_count + 1
15
+
16
+ x.report("I/O based record(n)") { iterations.times { @dbf.record(rand(max)) } }
17
+ x.report("array of records[n]") { iterations.times { @dbf.records[rand(max)] } }
18
+ end
data/lib/dbf.rb CHANGED
@@ -3,4 +3,4 @@ require 'date'
3
3
  require 'dbf/globals'
4
4
  require 'dbf/record'
5
5
  require 'dbf/field'
6
- require 'dbf/reader'
6
+ require 'dbf/reader'
data/lib/dbf/reader.rb CHANGED
@@ -1,119 +1,247 @@
1
1
  module DBF
2
2
  class Reader
3
+ # The total number of fields (columns)
3
4
  attr_reader :field_count
5
+ # An array of DBF::Field records
4
6
  attr_reader :fields
7
+ # The total number of records. This number includes any deleted records.
5
8
  attr_reader :record_count
9
+ # Internal dBase version number
6
10
  attr_reader :version
11
+ # Last updated datetime
7
12
  attr_reader :last_updated
13
+ # Either :fpt or :dpt
8
14
  attr_reader :memo_file_format
15
+ # The block size for memo records
9
16
  attr_reader :memo_block_size
10
17
 
11
- def initialize(file)
12
- @data_file = File.open(file, 'rb')
13
- @memo_file = open_memo(file)
18
+ # Initialize a new DBF::Reader.
19
+ # Example:
20
+ # reader = DBF::Reader.new 'data.dbf'
21
+ def initialize(filename)
22
+ @in_memory = true
23
+ @data_file = File.open(filename, 'rb')
24
+ @memo_file = open_memo(filename)
14
25
  reload!
15
26
  end
16
27
 
28
+ # Reloads the database and memo files
17
29
  def reload!
30
+ @records = nil
18
31
  get_header_info
19
32
  get_memo_header_info if @memo_file
20
33
  get_field_descriptors
21
34
  end
22
35
 
36
+ # Returns true if there is a corresponding memo file
23
37
  def has_memo_file?
24
38
  @memo_file ? true : false
25
39
  end
26
40
 
27
- def open_memo(file)
28
- %w(fpt FPT dbt DBT).each do |extension|
29
- filename = file.sub(/#{File.extname(file)[1..-1]}$/, extension)
30
- if File.exists?(filename)
31
- @memo_file_format = extension.downcase.to_sym
32
- return File.open(filename, 'rb')
33
- end
34
- end
35
- nil
41
+ # If true, DBF::Reader will load all records into memory. If false, records are retrieved using file I/O.
42
+ def in_memory?
43
+ @in_memory
36
44
  end
37
45
 
46
+ # Tells DBF::Reader whether to load all records into memory. Defaults to true.
47
+ # You may need to set this to false if the database is very large in order to reduce memory usage.
48
+ def in_memory=(boolean)
49
+ @in_memory = boolean
50
+ end
51
+
52
+ # Returns an instance of DBF::Field for <b>field_name</b>. <b>field_name</b>
53
+ # can be a symbol or a string.
38
54
  def field(field_name)
39
55
  @fields.detect {|f| f.name == field_name.to_s}
40
56
  end
41
57
 
42
58
  # An array of all the records contained in the database file
43
59
  def records
44
- seek_to_record(0)
45
- @records ||= Array.new(@record_count) do |i|
46
- if active_record?
47
- DBF::Record.new(self, @data_file, @memo_file)
48
- else
49
- seek_to_record(i + 1)
50
- nil
51
- end
60
+ if in_memory?
61
+ @records ||= get_all_records_from_file
62
+ else
63
+ get_all_records_from_file
52
64
  end
53
65
  end
54
66
 
55
67
  alias_method :rows, :records
56
68
 
57
- # Returns the record at <a>index</i> by seeking to the record in the
58
- # physical database file. See the documentation for the records method for
59
- # information on how these two methods differ.
69
+ # Returns the record at <tt>index</tt>.
60
70
  def record(index)
61
- seek_to_record(index)
62
- active_record? ? Record.new(self, @data_file, @memo_file) : nil
71
+ if in_memory?
72
+ records[index]
73
+ else
74
+ get_record_from_file(index)
75
+ end
76
+ end
77
+
78
+ # Find records. Examples:
79
+ # reader = DBF::Reader.new 'mydata.dbf'
80
+ #
81
+ # # Find record number 5
82
+ # reader.find(5)
83
+ #
84
+ # # Find all records for Keith Morrison
85
+ # reader.find :all, :first_name => "Keith", :last_name => "Morrison"
86
+ #
87
+ # # Find first record
88
+ # reader.find :first, :first_name => "Keith"
89
+ #
90
+ # The <b>command</b> can be an id, :all, or :first.
91
+ # <b>options</b> is optional and, if specified, should be a hash where the keys correspond
92
+ # to field names in the database. The values will be matched exactly with the value
93
+ # in the database. If you specify more than one key, all values must match in order
94
+ # for the record to be returned. The equivalent SQL would be "WHERE key1 = 'value1'
95
+ # AND key2 = 'value2'".
96
+ def find(command, options = {})
97
+ case command
98
+ when Fixnum
99
+ if !options.empty?
100
+ raise ArgumentError, "options are not allowed when command is a record number"
101
+ end
102
+ record(command)
103
+ when :all
104
+ records.select do |record|
105
+ options.map {|key, value| record[key.to_s] == value}.all?
106
+ end
107
+ when :first
108
+ return records.first if options.empty?
109
+ records.detect do |record|
110
+ options.map {|key, value| record[key.to_s] == value}.all?
111
+ end
112
+ end
63
113
  end
64
114
 
65
115
  alias_method :row, :record
66
116
 
117
+ # Returns a description of the current database file.
67
118
  def version_description
68
119
  VERSION_DESCRIPTIONS[version]
69
120
  end
70
121
 
122
+ # Returns a database schema in the portable ActiveRecord::Schema format.
123
+ #
124
+ # xBase data types are converted to generic types as follows:
125
+ # - Number fields are converted to :integer if there are no decimals, otherwise
126
+ # they are converted to :float
127
+ # - Date fields are converted to :datetime
128
+ # - Logical fields are converted to :boolean
129
+ # - Memo fields are converted to :text
130
+ # - Character fields are converted to :string and the :limit option is set
131
+ # to the length of the character field
132
+ #
133
+ # Example:
134
+ # create_table "mydata" do |t|
135
+ # t.column :name, :string, :limit => 30
136
+ # t.column :last_update, :datetime
137
+ # t.column :is_active, :boolean
138
+ # t.column :age, :integer
139
+ # t.column :notes, :text
140
+ # end
141
+ def schema(path = nil)
142
+ s = "ActiveRecord::Schema.define do\n"
143
+ s << " create_table \"#{File.basename(@data_file.path, ".*")}\" do |t|\n"
144
+ fields.each do |field|
145
+ s << " t.column \"#{field.name}\""
146
+ case field.type
147
+ when "N" # number
148
+ if field.decimal > 0
149
+ s << ", :float"
150
+ else
151
+ s << ", :integer"
152
+ end
153
+ when "D" # date
154
+ s << ", :datetime"
155
+ when "L" # boolean
156
+ s << ", :boolean"
157
+ when "M" # memo
158
+ s << ", :text"
159
+ else
160
+ s << ", :string, :limit => #{field.length}"
161
+ end
162
+ s << "\n"
163
+ end
164
+ s << " end\nend"
165
+
166
+ if path
167
+ return File.open(path, 'w') {|f| f.puts(s)}
168
+ else
169
+ return s
170
+ end
171
+ end
172
+
71
173
  private
72
174
 
73
- # Returns false if the record has been marked as deleted, otherwise it returns true. When dBase records are deleted a
74
- # flag is set, marking the record as deleted. The record will not be fully removed until the database has been compacted.
75
- def active_record?
76
- @data_file.read(1).unpack('H2').to_s == '20'
77
- rescue
78
- false
79
- end
175
+ def open_memo(file)
176
+ %w(fpt FPT dbt DBT).each do |extension|
177
+ filename = file.sub(/#{File.extname(file)[1..-1]}$/, extension)
178
+ if File.exists?(filename)
179
+ @memo_file_format = extension.downcase.to_sym
180
+ return File.open(filename, 'rb')
181
+ end
182
+ end
183
+ nil
184
+ end
80
185
 
81
- def get_header_info
82
- @data_file.rewind
83
- @version, @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('H2xxxVvv')
84
- @field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
85
- end
186
+ # Returns false if the record has been marked as deleted, otherwise it returns true. When dBase records are deleted a
187
+ # flag is set, marking the record as deleted. The record will not be fully removed until the database has been compacted.
188
+ def active_record?
189
+ @data_file.read(1).unpack('H2').to_s == '20'
190
+ rescue
191
+ false
192
+ end
193
+
194
+ def get_header_info
195
+ @data_file.rewind
196
+ @version, @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('H2xxxVvv')
197
+ @field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
198
+ end
86
199
 
87
- def get_field_descriptors
88
- @fields = []
89
- @field_count.times do
90
- name, type, length, decimal = @data_file.read(32).unpack('a10xax4CC')
91
- if length > 0 && !name.strip.empty?
92
- @fields << Field.new(name, type, length, decimal)
200
+ def get_field_descriptors
201
+ @fields = []
202
+ @field_count.times do
203
+ name, type, length, decimal = @data_file.read(32).unpack('a10xax4CC')
204
+ if length > 0 && !name.strip.empty?
205
+ @fields << Field.new(name, type, length, decimal)
206
+ end
93
207
  end
208
+ # adjust field count
209
+ @field_count = @fields.size
210
+ @fields
94
211
  end
95
- # adjust field count
96
- @field_count = @fields.size
97
- @fields
98
- end
99
212
 
100
- def get_memo_header_info
101
- @memo_file.rewind
102
- if @memo_file_format == :fpt
103
- @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
104
- else
105
- @memo_block_size = 512
106
- @memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
213
+ def get_memo_header_info
214
+ @memo_file.rewind
215
+ if @memo_file_format == :fpt
216
+ @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
217
+ else
218
+ @memo_block_size = 512
219
+ @memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
220
+ end
107
221
  end
108
- end
109
222
 
110
- def seek(offset)
111
- @data_file.seek(@header_length + offset)
112
- end
223
+ def seek(offset)
224
+ @data_file.seek(@header_length + offset)
225
+ end
113
226
 
114
- def seek_to_record(index)
115
- seek(index * @record_length)
116
- end
227
+ def seek_to_record(index)
228
+ seek(index * @record_length)
229
+ end
230
+
231
+ # Returns the record at <tt>index</tt> by seeking to the record in the
232
+ # physical database file. See the documentation for the records method for
233
+ # information on how these two methods differ.
234
+ def get_record_from_file(index)
235
+ seek_to_record(index)
236
+ active_record? ? Record.new(self, @data_file, @memo_file) : nil
237
+ end
238
+
239
+ def get_all_records_from_file
240
+ seek_to_record(0)
241
+ Array.new(@record_count) do |i|
242
+ active_record? ? DBF::Record.new(self, @data_file, @memo_file) : nil
243
+ end
244
+ end
117
245
 
118
246
  end
119
247
 
data/lib/dbf/record.rb CHANGED
@@ -21,7 +21,7 @@ module DBF
21
21
  when 'L' # logical
22
22
  self[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false
23
23
  else
24
- self[field.name] = unpack_string(field)
24
+ self[field.name] = unpack_string(field).strip
25
25
  end
26
26
  end
27
27
  self
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DBF::Field, "when initialized" do
4
+
5
+ before(:each) do
6
+ @field = DBF::Field.new "FieldName", "N", 1, 0
7
+ end
8
+
9
+ it "should set the 'name' accessor" do
10
+ @field.name.should == "FieldName"
11
+ end
12
+
13
+ it "should set the 'type' accessor" do
14
+ @field.type.should == "N"
15
+ end
16
+
17
+ it "should set the 'length' accessor" do
18
+ @field.length.should == 1
19
+ end
20
+
21
+ it "should set the 'decimal' accessor" do
22
+ @field.decimal.should == 0
23
+ end
24
+
25
+ it "should raise an error if length is greater than 0" do
26
+ lambda { field = DBF::Field.new "FieldName", "N", -1, 0 }.should raise_error(DBF::FieldLengthError)
27
+ end
28
+
29
+ it "should strip null characters from the name" do
30
+ field = DBF::Field.new "Field\0Name\0", "N", 1, 0
31
+ field.name.should == "FieldName"
32
+ end
33
+
34
+ end
@@ -0,0 +1,19 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table "dbase_iii_memo" do |t|
3
+ t.column "ID", :integer
4
+ t.column "CATCOUNT", :integer
5
+ t.column "AGRPCOUNT", :integer
6
+ t.column "PGRPCOUNT", :integer
7
+ t.column "ORDER", :integer
8
+ t.column "CODE", :string, :limit => 50
9
+ t.column "NAME", :string, :limit => 100
10
+ t.column "THUMBNAIL", :string, :limit => 254
11
+ t.column "IMAGE", :string, :limit => 254
12
+ t.column "PRICE", :float
13
+ t.column "COST", :float
14
+ t.column "DESC", :text
15
+ t.column "WEIGHT", :float
16
+ t.column "TAXABLE", :boolean
17
+ t.column "ACTIVE", :boolean
18
+ end
19
+ end
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DBF::Reader, "when initialized" do
4
+
5
+ before(:all) do
6
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
7
+ end
8
+
9
+ it "should load the data file" do
10
+ @reader.instance_eval("@data_file").should be_kind_of(File)
11
+ end
12
+
13
+ it "should locate load the memo file" do
14
+ @reader.has_memo_file?.should be_true
15
+ @reader.instance_eval("@memo_file").should be_kind_of(File)
16
+ end
17
+
18
+ it "should determine the memo file format" do
19
+ @reader.memo_file_format.should == :dbt
20
+ end
21
+
22
+ it "should determine the memo block size" do
23
+ @reader.memo_block_size.should == 512
24
+ end
25
+
26
+ it "should default to loading all records into memory" do
27
+ @reader.in_memory?.should be_true
28
+ end
29
+
30
+ it "should determine the number of fields in each record" do
31
+ @reader.fields.size.should == 15
32
+ end
33
+
34
+ it "should determine the number of records in the database" do
35
+ @reader.record_count.should == 67
36
+ end
37
+
38
+ it "should determine the database version" do
39
+ @reader.version.should == "83"
40
+ end
41
+
42
+ end
43
+
44
+ describe DBF::Reader, "when the in_memory flag is true" do
45
+
46
+ before(:each) do
47
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
48
+ end
49
+
50
+ it "should build the records array from disk only on the first request" do
51
+ @reader.expects(:get_all_records_from_file).at_most_once.returns([])
52
+ 3.times { @reader.records }
53
+ end
54
+
55
+ it "should read from the records array when using the record() method" do
56
+ @reader.expects(:get_all_records_from_file).at_most_once.returns([])
57
+ @reader.expects(:get_record_from_file).never
58
+ @reader.expects(:records).times(2).returns([])
59
+ @reader.record(1)
60
+ @reader.record(10)
61
+ end
62
+
63
+ end
64
+
65
+ describe DBF::Reader, "when the in_memory flag is false" do
66
+
67
+ before(:each) do
68
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
69
+ end
70
+
71
+ it "should read the records from disk on every request" do
72
+ @reader.in_memory = false
73
+ @reader.expects(:get_all_records_from_file).times(3).returns([])
74
+ 3.times { @reader.records }
75
+ end
76
+ end
77
+
78
+ describe DBF::Reader, "schema" do
79
+
80
+ it "should match test schema " do
81
+ reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
82
+ control_schema = File.read(File.dirname(__FILE__) + '/fixtures/dbase_iii_memo_schema.rb')
83
+
84
+ reader.schema.should == control_schema
85
+ end
86
+
87
+ end
88
+
89
+ describe DBF::Reader, "find(index)" do
90
+
91
+ before(:all) do
92
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
93
+ end
94
+
95
+ it "should return the correct record" do
96
+ @reader.find(5).should == @reader.record(5)
97
+ end
98
+
99
+ it "should raise an error if options are not empty" do
100
+ lambda { @reader.find(5, :name => 'test') }.should raise_error(ArgumentError)
101
+ end
102
+
103
+ end
104
+
105
+ describe DBF::Reader, "find(:all)" do
106
+
107
+ before(:all) do
108
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
109
+ end
110
+
111
+ it "should return all records if options are empty" do
112
+ @reader.find(:all).should == @reader.records
113
+ end
114
+
115
+ it "should return matching records when used with options" do
116
+ @reader.find(:all, "WEIGHT" => 0.0).should == @reader.records.select {|r| r["WEIGHT"] == 0.0}
117
+ end
118
+
119
+ it "with multiple options should search for all search terms as if using AND" do
120
+ @reader.find(:all, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should == []
121
+ end
122
+ end
123
+
124
+ describe DBF::Reader, "find(:first)" do
125
+
126
+ before(:all) do
127
+ @reader = DBF::Reader.new File.dirname(__FILE__) + '/../test/databases/dbase_iii_memo.dbf'
128
+ end
129
+
130
+ it "should return the first record if options are empty" do
131
+ @reader.find(:first).should == @reader.records.first
132
+ end
133
+
134
+ it "should return the first matching record when used with options" do
135
+ @reader.find(:first, "CODE" => "C").should == @reader.record(5)
136
+ end
137
+
138
+ it "with multiple options should search for all search terms as if using AND" do
139
+ @reader.find(:first, "ID" => 30, "IMAGE" => "graphics/00000001/TBC01.jpg").should be_nil
140
+ end
141
+ end
142
+
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DBF::Record, "when initialized" do
4
+
5
+ it "should convert number fields with 0 decimals to integers" do
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require "rubygems"
3
+ require "spec"
4
+ require "dbf"
5
+
6
+ Spec::Runner.configure do |config|
7
+ config.mock_with :mocha
8
+ end
Binary file
Binary file
@@ -0,0 +1,27 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'test/unit'
3
+ require 'dbf'
4
+ require 'common'
5
+
6
+ class DBaseIIIWithMemoReadTest < Test::Unit::TestCase
7
+ include CommonTests::Read
8
+
9
+ def setup
10
+ @controls = {
11
+ :version => "83",
12
+ :has_memo_file => true,
13
+ :memo_file_format => :dbt,
14
+ :field_count => 15,
15
+ :record_count => 67,
16
+ :record_length => 805,
17
+ :testable_character_field_names => ["CODE"],
18
+ :testable_date_field_names => [],
19
+ :testable_integer_field_names => ["AGRPCOUNT"],
20
+ :testable_float_field_names => ["PRICE"],
21
+ :testable_logical_field_names => ["TAXABLE"],
22
+ :testable_memo_field_names => ["DESC"]
23
+ }
24
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'dbase_iii_memo.dbf'))
25
+ end
26
+
27
+ end
@@ -0,0 +1,27 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'test/unit'
3
+ require 'dbf'
4
+ require 'common'
5
+
6
+ class VisualFoxproReadTest < Test::Unit::TestCase
7
+ include CommonTests::Read
8
+
9
+ def setup
10
+ @controls = {
11
+ :version => "30",
12
+ :has_memo_file => true,
13
+ :memo_file_format => :fpt,
14
+ :field_count => 145,
15
+ :record_count => 34,
16
+ :record_length => 3907,
17
+ :testable_character_field_names => [],
18
+ :testable_date_field_names => [],
19
+ :testable_integer_field_names => ["IMAGENO"],
20
+ :testable_float_field_names => [],
21
+ :testable_logical_field_names => [],
22
+ :testable_memo_field_names => ["CREDIT"]
23
+ }
24
+ @dbf = DBF::Reader.new(File.join(File.dirname(__FILE__),'databases', 'visual_foxpro.dbf'))
25
+ end
26
+
27
+ end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: dbf
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.7
7
- date: 2007-05-25 00:00:00 -07:00
6
+ version: 0.5.0
7
+ date: 2007-05-26 00:00:00 -07:00
8
8
  summary: A small fast library for reading dBase, xBase, Clipper and FoxPro database files.
9
9
  require_paths:
10
10
  - lib
@@ -29,31 +29,43 @@ post_install_message:
29
29
  authors:
30
30
  - Keith Morrison
31
31
  files:
32
- - Rakefile
32
+ - History.txt
33
+ - Manifest.txt
33
34
  - README.txt
34
- - LICENSE.txt
35
+ - Rakefile
36
+ - benchmarks/performance.rb
37
+ - benchmarks/seek_benchmark.rb
35
38
  - lib/dbf.rb
36
39
  - lib/dbf/field.rb
37
40
  - lib/dbf/globals.rb
38
41
  - lib/dbf/reader.rb
39
42
  - lib/dbf/record.rb
43
+ - spec/field_spec.rb
44
+ - spec/fixtures/dbase_iii_memo_schema.rb
45
+ - spec/reader_spec.rb
46
+ - spec/record_spec.rb
47
+ - spec/spec_helper.rb
40
48
  - test/common.rb
41
- - test/dbase_iii_read_test.rb
42
- - test/foxpro_read_test.rb
43
- - test/performance.rb
44
49
  - test/databases/dbase_iii.dbf
50
+ - test/databases/dbase_iii_memo.dbf
51
+ - test/databases/dbase_iii_memo.dbt
45
52
  - test/databases/foxpro.dbf
46
53
  - test/databases/foxpro.fpt
47
54
  - test/databases/visual_foxpro.dbf
48
55
  - test/databases/visual_foxpro.fpt
56
+ - test/dbase_iii_memo_read_test.rb
57
+ - test/dbase_iii_read_test.rb
58
+ - test/foxpro_read_test.rb
59
+ - test/visual_foxpro_read_test.rb
49
60
  test_files: []
50
61
 
51
62
  rdoc_options:
52
63
  - --main
53
64
  - README.txt
54
65
  extra_rdoc_files:
66
+ - History.txt
67
+ - Manifest.txt
55
68
  - README.txt
56
- - LICENSE.txt
57
69
  executables: []
58
70
 
59
71
  extensions: []
data/LICENSE.txt DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2006-2007 Keith Morrison <keithm@infused.org>
2
-
3
- Permission is hereby granted, free of charge, to any person
4
- obtaining a copy of this software and associated documentation
5
- files (the "Software"), to deal in the Software without
6
- restriction, including without limitation the rights to use,
7
- copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the
9
- Software is furnished to do so, subject to the following
10
- conditions:
11
-
12
- The above copyright notice and this permission notice shall be
13
- included in all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.