New Upstream Release - ruby-dbf

Ready changes

Summary

Merged new upstream version: 4.2.2 (was: 3.0.5).

Resulting package

Built on 2023-01-11T04:17 (took 4m6s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-dbf

Lintian Result

Diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa738d3..b774451 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,120 +1,223 @@
-# 3.0.5
-  - Override table name for schema output
+# Changelog
 
-# 3.0.4
-  - Adds -v command-line option to print version
-  - Adds -r command-line option to create Sequel migration
+## 4.2.2
 
-# 3.0.3
-  - Uninitialized (N)umbers should return nil
+- Faster CSV generation
 
-# 3.0.2
-  - Performance improvements for large files
+## 4.2.1
 
-# 3.0.1
-  - Support FoxPro (G) general field type
-  - Fix ruby warnings
+- Support for dBase IV "04" type files
 
-# 3.0.0
-  - Requires Ruby version 2.0 and above
-  - Support the (G) General Foxpro field type
+## 4.2.0
 
-# 2.0.13
-  - Support 64-bit currency signed currency values
-    (see https://github.com/infused/dbf/pull/71)
+- Initial support for dBase 7 files
 
-# 2.0.12
-  - Parse (I) values as signed
-    (see https://github.com/infused/dbf/pull/70)
+## 4.1.6
 
-# 2.0.11
-  - Foxpro doubles should always return the full stored precision
-    (see https://github.com/infused/dbf/pull/69)
+- Add support for file type 32
 
-# 2.0.10
-  - allow 0 length fields, but always return nil as value
+## 4.1.5
 
-# 2.0.9
-  - fix dBase IV attributes when memo file is missing
+- Better handling for PIPE errors when using command line utility
 
-# 2.0.8
-  - fix FoxPro currency fields on some builds of Ruby 1.9.3 and 2.0.0
+## 4.1.4
 
-# 2.0.7
-  - fix the dbf binary on some linux systems
+- Add full support for FoxBase files
 
-# 2.0.6
-  - build_memo returns nil on errors
+## 4.1.3
 
-# 2.0.5
-  - use correct FoxPro memo block size
+- Raise DBF::NoColumnsDefined error when attempting to read records if no columns are defined
 
-# 2.0.4
-  - memo fields return nil if memo file is missing
+## 4.1.1
 
-# 2.0.3
-  - set encoding if table encoding is nil
+- Add required_ruby_version to gemspec
 
-# 2.0.2
-  - Allow overriding the character encoding specified in the file
+## 4.1.0
 
-# 2.0.1
-  - Add experimental support for character encodings under Ruby 1.8
+- Return Time instead of DateTime
 
-# 2.0.0
-  - #44 Require FasterCSV gem on all platforms
-  - Remove rdoc development dependency
-  - #42 Fixes encoding of memos
-  - #43 Improve handling of record attributes
+## 4.0.0
 
-# 1.7.5
-  - fixes FoxPro currency (Y) fields
+- Drop support for ruby-2.2 and earlier
 
-# 1.7.4
-  - Replace Memo Type with Memo File boolean in command-line utility summary output
+## 3.1.3
 
-# 1.7.3
-  - find_all/find_first should ignore deleted records
+- Ensure malformed dates return nil
 
-# 1.7.2
-  - Fix integer division under Ruby 1.8 when requiring mathn
-    standard library (see http://bugs.ruby-lang.org/issues/2121)
+## 3.1.2
 
-# 1.7.1
-  - Fix Table.FOXPRO_VERSIONS breakage on Ruby 1.8
+- Fix incorrect columns list when StringIO and encoding set
 
-# 1.7.0
-  - allow DBF::Table to work with dbf data in memory
-  - allow DBF::Table#to_csv to write to STDOUT
+## 3.1.1
 
-# 1.6.7
-  - memo columns return nil when no memo file found
+- Use Date.strptime to parse date fields
 
-# 1.6.6
-  - add binary data type support to ActiveRecord schema output
+## 3.1.0
 
-# 1.6.5
-  - support for visual foxpro double (b) data type
+- Use :binary for binary fields in ActiveRecord schemas
 
-# 1.6.3
-  - Replace invalid chars with 'unicode replacement character' (U+FFFD)
+## 3.0.8
+
+- Fix uninitialized constant error under Rails 5
+
+## 3.0.7
+
+- Ignore non-existent records if header record count is incorrect
+
+## 3.0.6
+
+- This version has been yanked from rubygems due to errors
+
+## 3.0.5
+
+- Override table name for schema output
+
+## 3.0.4
+
+- Adds -v command-line option to print version
+- Adds -r command-line option to create Sequel migration
+
+## 3.0.3
+
+- Uninitialized (N)umbers should return nil
+
+## 3.0.2
+  
+- Performance improvements for large files
+
+## 3.0.1
+
+- Support FoxPro (G) general field type
+- Fix ruby warnings
+
+## 3.0.0
+
+- Requires Ruby version 2.0 and above
+- Support the (G) General Foxpro field type
+
+## 2.0.13
+
+- Support 64-bit currency signed currency values
+  (see https://github.com/infused/dbf/pull/71)
+
+## 2.0.12
+
+- Parse (I) values as signed
+  (see https://github.com/infused/dbf/pull/70)
+
+## 2.0.11
+
+- Foxpro doubles should always return the full stored precision
+  (see https://github.com/infused/dbf/pull/69)
+
+## 2.0.10
+
+- allow 0 length fields, but always return nil as value
+
+## 2.0.9
+
+- fix dBase IV attributes when memo file is missing
+
+## 2.0.8
+
+- fix FoxPro currency fields on some builds of Ruby 1.9.3 and 2.0.0
+
+## 2.0.7
+
+- fix the dbf binary on some linux systems
+
+## 2.0.6
+
+- build_memo returns nil on errors
+
+## 2.0.5
+
+- use correct FoxPro memo block size
+
+## 2.0.4
+  
+- memo fields return nil if memo file is missing
+
+## 2.0.3
+
+- set encoding if table encoding is nil
+
+## 2.0.2
+
+- Allow overriding the character encoding specified in the file
+
+## 2.0.1
+
+- Add experimental support for character encodings under Ruby 1.8
+
+## 2.0.0
+
+- #44 Require FasterCSV gem on all platforms
+- Remove rdoc development dependency
+- #42 Fixes encoding of memos
+- #43 Improve handling of record attributes
+
+## 1.7.5
+  
+- fixes FoxPro currency (Y) fields
+
+## 1.7.4
+  
+- Replace Memo Type with Memo File boolean in command-line utility summary output
+
+## 1.7.3
+  
+- find_all/find_first should ignore deleted records
+
+## 1.7.2
+  
+- Fix integer division under Ruby 1.8 when requiring mathn
+  standard library (see http://bugs.ruby-lang.org/issues/2121)
+
+## 1.7.1
+
+- Fix Table.FOXPRO_VERSIONS breakage on Ruby 1.8
+
+## 1.7.0
+
+- allow DBF::Table to work with dbf data in memory
+- allow DBF::Table#to_csv to write to STDOUT
+
+## 1.6.7
+
+- memo columns return nil when no memo file found
+
+## 1.6.6
+
+- add binary data type support to ActiveRecord schema output
+
+## 1.6.5
+
+- support for visual foxpro double (b) data type
+
+## 1.6.3
+
+- Replace invalid chars with 'unicode replacement character' (U+FFFD)
 
 ## 1.6.2
-  - add Table#filename method
-  - Rakefile now loads gems with bundler
-  - add Table#supports_encoding?
-  - simplify encodings.yml loader
-  - add rake and rdoc as development dependencies
-  - simplify open_memo file search logic
-  - remove unnecessary requires in spec helper
-  - fix cli summary
+
+- add Table#filename method
+- Rakefile now loads gems with bundler
+- add Table#supports_encoding?
+- simplify encodings.yml loader
+- add rake and rdoc as development dependencies
+- simplify open_memo file search logic
+- remove unnecessary requires in spec helper
+- fix cli summary
 
 ## 1.6.1
-  - fix YAML issue when using MRI version > 1.9.1
-  - remove Table#seek_to_index and Table#current_record private methods
+
+- fix YAML issue when using MRI version > 1.9.1
+- remove Table#seek_to_index and Table#current_record private methods
 
 ## 1.6.0
-  - remove activesupport gem dependency
+
+- remove activesupport gem dependency
 
 ## 1.5.0
 
diff --git a/Gemfile b/Gemfile
index e3c0ef6..bc0e188 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,16 @@ gemspec
 source 'https://rubygems.org'
 
 group :development, :test do
-  gem 'rspec'
+  gem 'awesome_print'
+  gem 'byebug'
+  gem 'e2mmap'
   gem 'guard'
   gem 'guard-rspec'
+  gem 'irb'
+  gem 'rake'
+  gem 'rspec'
+  gem 'rubocop'
+  gem 'rubocop-performance'
+  gem 'rubocop-rspec'
+  gem 'yard'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
index 463ed0e..f182ac4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,80 +1,114 @@
 PATH
   remote: .
   specs:
+    dbf (4.2.1)
 
 GEM
   remote: https://rubygems.org/
   specs:
-    byebug (8.2.0)
-    coderay (1.1.0)
-    diff-lcs (1.2.5)
-    ffi (1.9.10)
-    ffi (1.9.10-java)
-    formatador (0.2.5)
-    guard (2.13.0)
+    ast (2.4.2)
+    awesome_print (1.9.2)
+    byebug (11.1.3)
+    coderay (1.1.3)
+    diff-lcs (1.5.0)
+    e2mmap (0.1.0)
+    ffi (1.15.5)
+    formatador (1.1.0)
+    guard (2.18.0)
       formatador (>= 0.2.4)
-      listen (>= 2.7, <= 4.0)
-      lumberjack (~> 1.0)
+      listen (>= 2.7, < 4.0)
+      lumberjack (>= 1.0.12, < 2.0)
       nenv (~> 0.1)
       notiffany (~> 0.0)
-      pry (>= 0.9.12)
+      pry (>= 0.13.0)
       shellany (~> 0.0)
       thor (>= 0.18.1)
     guard-compat (1.2.1)
-    guard-rspec (4.6.4)
+    guard-rspec (4.7.3)
       guard (~> 2.1)
       guard-compat (~> 1.1)
       rspec (>= 2.99.0, < 4.0)
-    listen (3.0.5)
-      rb-fsevent (>= 0.9.3)
-      rb-inotify (>= 0.9)
-    lumberjack (1.0.9)
-    method_source (0.8.2)
-    nenv (0.2.0)
-    notiffany (0.0.8)
+    io-console (0.5.11)
+    irb (1.4.1)
+      reline (>= 0.3.0)
+    listen (3.7.1)
+      rb-fsevent (~> 0.10, >= 0.10.3)
+      rb-inotify (~> 0.9, >= 0.9.10)
+    lumberjack (1.2.8)
+    method_source (1.0.0)
+    nenv (0.3.0)
+    notiffany (0.1.3)
       nenv (~> 0.1)
       shellany (~> 0.0)
-    pry (0.10.3)
-      coderay (~> 1.1.0)
-      method_source (~> 0.8.1)
-      slop (~> 3.4)
-    pry (0.10.3-java)
-      coderay (~> 1.1.0)
-      method_source (~> 0.8.1)
-      slop (~> 3.4)
-      spoon (~> 0.0)
-    rb-fsevent (0.9.6)
-    rb-inotify (0.9.5)
-      ffi (>= 0.5.0)
-    rspec (3.4.0)
-      rspec-core (~> 3.4.0)
-      rspec-expectations (~> 3.4.0)
-      rspec-mocks (~> 3.4.0)
-    rspec-core (3.4.1)
-      rspec-support (~> 3.4.0)
-    rspec-expectations (3.4.0)
+    parallel (1.22.1)
+    parser (3.1.2.0)
+      ast (~> 2.4.1)
+    pry (0.14.1)
+      coderay (~> 1.1)
+      method_source (~> 1.0)
+    rainbow (3.1.1)
+    rake (13.0.6)
+    rb-fsevent (0.11.1)
+    rb-inotify (0.10.1)
+      ffi (~> 1.0)
+    regexp_parser (2.5.0)
+    reline (0.3.1)
+      io-console (~> 0.5)
+    rexml (3.2.5)
+    rspec (3.11.0)
+      rspec-core (~> 3.11.0)
+      rspec-expectations (~> 3.11.0)
+      rspec-mocks (~> 3.11.0)
+    rspec-core (3.11.0)
+      rspec-support (~> 3.11.0)
+    rspec-expectations (3.11.0)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.4.0)
-    rspec-mocks (3.4.0)
+      rspec-support (~> 3.11.0)
+    rspec-mocks (3.11.1)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.4.0)
-    rspec-support (3.4.0)
+      rspec-support (~> 3.11.0)
+    rspec-support (3.11.0)
+    rubocop (1.30.1)
+      parallel (~> 1.10)
+      parser (>= 3.1.0.0)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml (>= 3.2.5, < 4.0)
+      rubocop-ast (>= 1.18.0, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 1.4.0, < 3.0)
+    rubocop-ast (1.18.0)
+      parser (>= 3.1.1.0)
+    rubocop-performance (1.14.2)
+      rubocop (>= 1.7.0, < 2.0)
+      rubocop-ast (>= 0.4.0)
+    rubocop-rspec (2.11.1)
+      rubocop (~> 1.19)
+    ruby-progressbar (1.11.0)
     shellany (0.0.1)
-    slop (3.6.0)
-    spoon (0.0.4)
-      ffi
-    thor (0.19.1)
+    thor (1.2.1)
+    unicode-display_width (2.1.0)
+    webrick (1.7.0)
+    yard (0.9.28)
+      webrick (~> 1.7.0)
 
 PLATFORMS
-  java
-  ruby
+  arm64-darwin-21
 
 DEPENDENCIES
+  awesome_print
   byebug
   dbf!
+  e2mmap
   guard
   guard-rspec
+  irb
+  rake
   rspec
+  rubocop
+  rubocop-performance
+  rubocop-rspec
+  yard
 
 BUNDLED WITH
-   1.10.6
+   2.3.20
diff --git a/Gemfile.travis b/Gemfile.travis
deleted file mode 100644
index cd887ac..0000000
--- a/Gemfile.travis
+++ /dev/null
@@ -1,7 +0,0 @@
-gemspec
-source 'https://rubygems.org'
-
-group :test do
-  gem 'rspec'
-  gem 'codeclimate-test-reporter', require: false, platform: :ruby
-end
diff --git a/Guardfile b/Guardfile
index 1b09082..4af4736 100644
--- a/Guardfile
+++ b/Guardfile
@@ -6,6 +6,6 @@ guard :rspec, cmd: 'bundle exec rspec' do
   # watch(%r{^lib/(.+)\.rb$})     { |m| "spec/#{m[1]}_spec.rb" }
   watch(%r{^lib/(.+)\.rb$})     { 'spec' }
   watch('spec/spec_helper.rb')  { 'spec' }
-  watch(/spec\/fixtures\/(.+)/) { 'spec' }
+  watch(%r{spec/fixtures/(.+)}) { 'spec' }
   watch('Guardfile')            { 'spec' }
 end
diff --git a/LICENSE b/LICENSE
index fa8d7dd..8a6f5e5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2006-2016 Keith Morrison <keithm@infused.org>
+Copyright (c) 2006-2021 Keith Morrison <keithm@infused.org>
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/README.md b/README.md
index 00ec1f8..1bf2e3d 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,32 @@
 # DBF
-[![Version](http://img.shields.io/gem/v/dbf.svg?style=flat)](https://rubygems.org/gems/dbf)
-[![Build Status](http://img.shields.io/travis/infused/dbf/master.svg?style=flat)](http://travis-ci.org/infused/dbf)
-[![Code Quality](http://img.shields.io/codeclimate/github/infused/dbf.svg?style=flat)](https://codeclimate.com/github/infused/dbf)
-[![Test Coverage](http://img.shields.io/codeclimate/coverage/github/infused/dbf.svg?style=flat)](https://codeclimate.com/github/infused/dbf)
-[![Dependency Status](http://img.shields.io/gemnasium/infused/dbf.svg?style=flat)](https://gemnasium.com/infused/dbf)
+[![Version](https://img.shields.io/gem/v/dbf.svg?style=flat)](https://rubygems.org/gems/dbf)
+[![Build Status](https://github.com/infused/dbf/actions/workflows/build.yml/badge.svg)](https://github.com/infused/dbf/actions/workflows/build.yml)
+[![Code Quality](https://img.shields.io/codeclimate/maintainability/infused/dbf.svg?style=flat)](https://codeclimate.com/github/infused/dbf)
+[![Code Coverage](https://img.shields.io/codeclimate/c/infused/dbf.svg?style=flat)](https://codeclimate.com/github/infused/dbf)
 [![Total Downloads](https://img.shields.io/gem/dt/dbf.svg)](https://rubygems.org/gems/dbf/)
+[![License](https://img.shields.io/github/license/infused/dbf.svg)](https://github.com/infused/dbf)
 
 DBF is a small fast library for reading dBase, xBase, Clipper and FoxPro
 database files
 
-* Project page: <http://github.com/infused/dbf>
-* API Documentation: <http://rubydoc.info/github/infused/dbf/>
-* Report bugs: <http://github.com/infused/dbf/issues>
+* Project page: <https://github.com/infused/dbf>
+* API Documentation: <https://dbf.infused.org>
+* Report bugs: <https://github.com/infused/dbf/issues>
 * Questions: Email <mailto:keithm@infused.org> and put DBF somewhere in the
   subject line
 * Change log: <https://github.com/infused/dbf/blob/master/CHANGELOG.md>
 
-NOTE: beginning with version 3 we have dropped support for Ruby 1.8 and 1.9. If you need support for older Rubies, please use 2.0.x (https://github.com/infused/dbf/tree/2_stable)
+NOTE: Beginning with version 4 we have dropped support for Ruby 2.0, 2.1, 2.2, and 2.3. If you need support for these older Rubies,
+please use 3.0.x (https://github.com/infused.org/dbf/tree/3_stable)
+
+NOTE: Beginning with version 3 we have dropped support for Ruby 1.8 and 1.9. If you need support for older Rubies,
+please use 2.0.x (https://github.com/infused/dbf/tree/2_stable)
 
 ## Compatibility
 
 DBF is tested to work with the following versions of Ruby:
 
-* MRI Ruby 2.0.x, 2.1.x, 2.2.x, 2.3.x
-* JRuby head
+* Ruby 2.4.x, 2.5.x, 2.6.x, 2.7.x, 3.0.x, TruffleRuby
 
 ## Installation
 
@@ -55,7 +58,7 @@ data = File.open('widgets.dbf')
 widgets = DBF::Table.new(data)
 ```
 
-Open a DBF by passing in raw data (wrap the raw data with a StringIO):
+Open a DBF by passing in raw data (wrap the raw data with StringIO):
 
 ```ruby
 widgets = DBF::Table.new(StringIO.new('raw binary data'))
@@ -79,26 +82,22 @@ widget = widgets.find(6)
 Note that find() will return nil if the requested record has been deleted
 and not yet pruned from the database.
 
-The value for a attribute can be accessed via element reference in one of three
-ways
+The value for an attribute can be accessed via element reference in several
+ways.
 
 ```ruby
+widget.slot_number     # underscored field name as method
+
 widget["SlotNumber"]   # original field name in dbf file
 widget['slot_number']  # underscored field name string
 widget[:slot_number]   # underscored field name symbol
 ```
 
-Attributes can also be accessed as method using the underscored field name
-
-```ruby
-widget.slot_number
-```
-
 Get a hash of all attributes. The keys are the original column names.
 
 ```ruby
 widget.attributes
-# => {"Name" => "Thing1", "SlotNumber" => 1}
+# => {"Name" => "Thing1 | SlotNumber" => 1}
 ```
 
 Search for records using a simple hash format. Multiple search criteria are
@@ -149,9 +148,67 @@ data. Try using the 'Russion OEM' encoding:
 table = DBF::Table.new('dbf/books.dbf', nil, 'cp866')
 ```
 
-See
-[doc/supported_encodings.csv](docs/supported_encodings.csv)
-for a full list of supported encodings.
+| Code Page | Encoding | Description |
+| --------- | -------- | ----------- |
+| 01 | cp437 | U.S. MS–DOS |
+| 02 | cp850 | International MS–DOS |
+| 03 | cp1252 | Windows ANSI |
+| 08 | cp865 | Danish OEM |
+| 09 | cp437 | Dutch OEM |
+| 0a | cp850 | Dutch OEM* |
+| 0b | cp437 | Finnish OEM |
+| 0d | cp437 | French OEM |
+| 0e | cp850 | French OEM* |
+| 0f | cp437 | German OEM |
+| 10 | cp850 | German OEM* |
+| 11 | cp437 | Italian OEM |
+| 12 | cp850 | Italian OEM* |
+| 13 | cp932 | Japanese Shift-JIS |
+| 14 | cp850 | Spanish OEM* |
+| 15 | cp437 | Swedish OEM |
+| 16 | cp850 | Swedish OEM* |
+| 17 | cp865 | Norwegian OEM |
+| 18 | cp437 | Spanish OEM |
+| 19 | cp437 | English OEM (Britain) |
+| 1a | cp850 | English OEM (Britain)* |
+| 1b | cp437 | English OEM (U.S.) |
+| 1c | cp863 | French OEM (Canada) |
+| 1d | cp850 | French OEM* |
+| 1f | cp852 | Czech OEM |
+| 22 | cp852 | Hungarian OEM |
+| 23 | cp852 | Polish OEM |
+| 24 | cp860 | Portuguese OEM |
+| 25 | cp850 | Portuguese OEM* |
+| 26 | cp866 | Russian OEM |
+| 37 | cp850 | English OEM (U.S.)* |
+| 40 | cp852 | Romanian OEM |
+| 4d | cp936 | Chinese GBK (PRC) |
+| 4e | cp949 | Korean (ANSI/OEM) |
+| 4f | cp950 | Chinese Big5 (Taiwan) |
+| 50 | cp874 | Thai (ANSI/OEM) |
+| 57 | cp1252 | ANSI |
+| 58 | cp1252 | Western European ANSI |
+| 59 | cp1252 | Spanish ANSI |
+| 64 | cp852 | Eastern European MS–DOS |
+| 65 | cp866 | Russian MS–DOS |
+| 66 | cp865 | Nordic MS–DOS |
+| 67 | cp861 | Icelandic MS–DOS |
+| 6a | cp737 | Greek MS–DOS (437G) |
+| 6b | cp857 | Turkish MS–DOS |
+| 6c | cp863 | French–Canadian MS–DOS |
+| 78 | cp950 | Taiwan Big 5 |
+| 79 | cp949 | Hangul (Wansung) |
+| 7a | cp936 | PRC GBK |
+| 7b | cp932 | Japanese Shift-JIS |
+| 7c | cp874 | Thai Windows/MS–DOS |
+| 86 | cp737 | Greek OEM |
+| 87 | cp852 | Slovenian OEM |
+| 88 | cp857 | Turkish OEM |
+| c8 | cp1250 | Eastern European Windows |
+| c9 | cp1251 | Russian Windows |
+| ca | cp1254 | Turkish Windows |
+| cb | cp1253 | Greek Windows |
+| cc | cp1257 | Baltic Windows |
 
 ## Migrating to ActiveRecord
 
@@ -180,7 +237,7 @@ end
 ```
 
 If you have initalized the DBF::Table with raw data, you will need to set the
-table name manually with:
+exported table name manually:
 
 ```ruby
 table.name = 'my_table_name'
@@ -212,8 +269,8 @@ Sequel.migration do
 end
 ```
 
-If you have initalized the DBF::Table with raw data, you will need to set the
-table name manually with:
+If you have initialized the DBF::Table with raw data, you will need to set the
+exported table name manually:
 
 ```ruby
 table.name = 'my_table_name'
@@ -230,7 +287,7 @@ A small command-line utility called dbf is installed along with the gem.
       -s = print summary information
       -a = create an ActiveRecord::Schema
       -r = create a Sequel Migration
-      -c = create a csv file
+      -c = export as CSV
 
 Create an executable ActiveRecord schema:
 
@@ -246,17 +303,17 @@ Dump all records to a CSV file:
 
 ## Reading a Visual Foxpro database (v8, v9)
 
-A special Database::Foxpro class is available to read Visual Foxpro container files (.dbc-files). When using this class,
-long field names are supported and tables can be referenced without using names.
+A special Database::Foxpro class is available to read Visual Foxpro container
+files (file with .dbc extension). When using this class long field names are
+supported and tables can be referenced without using names.
 
 ```ruby
 require 'dbf'
 
-contacts = DBF::Database::Foxpro.new('contactdatabase.dbc').contacts
+contacts = DBF::Database::Foxpro.new('contact_database.dbc').contacts
 my_contact = contacts.record(1).spouses_interests
 ```
 
-
 ## dBase version compatibility
 
 The basic dBase data types are generally supported well. Support for the
@@ -265,9 +322,45 @@ supported. If you have insight into how any of the unsupported data types are
 implemented, please give me a shout. FoxBase/dBase II files are not supported
 at this time.
 
-See
-[doc/supported_types.markdown](docs/supported_types.markdown)
-for a full list of supported column types.
+### Supported data types by dBase version
+
+| Version | Description                                         | C | N | L | D | M | F | B | G | P | Y | T | I | V | X | @ | O | + |
+|---------|-----------------------------------------------------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+| 02      | FoxBase                                             | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
+| 03      | dBase III without memo file                         | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
+| 04      | dBase IV without memo file                          | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
+| 05      | dBase V without memo file                           | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
+| 07      | Visual Objects 1.x                                  | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
+| 30      | Visual FoxPro                                       | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | - |
+| 31      | Visual FoxPro with AutoIncrement                    | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
+| 32      | Visual FoxPro with field type Varchar or Varbinary  | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
+| 7b      | dBase IV with memo file                             | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - |
+| 83      | dBase III with memo file                            | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
+| 87      | Visual Objects 1.x with memo file                   | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
+| 8b      | dBase IV with memo file                             | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
+| 8e      | dBase IV with SQL table                             | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
+| f5      | FoxPro with memo file                               | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
+| fb      | FoxPro without memo file                            | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
+
+Data type descriptions
+
+* C = Character
+* N = Number
+* L = Logical
+* D = Date
+* M = Memo
+* F = Float
+* B = Binary
+* G = General
+* P = Picture
+* Y = Currency
+* T = DateTime
+* I = Integer
+* V = VariField
+* X = SQL compat
+* @ = Timestamp
+* O = Double
+* + = Autoincrement
 
 ## Limitations
 
@@ -276,7 +369,7 @@ for a full list of supported column types.
 
 ## License
 
-Copyright (c) 2006-2016 Keith Morrison <<keithm@infused.org>>
+Copyright (c) 2006-2021 Keith Morrison <<keithm@infused.org>>
 
 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
diff --git a/Rakefile b/Rakefile
index 16a7d83..b48c653 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,16 +3,16 @@ Bundler.setup(:default, :development)
 
 require 'rspec/core/rake_task'
 RSpec::Core::RakeTask.new :spec do |t|
-  t.rspec_opts = %w(--color)
+  t.rspec_opts = %w[--color]
 end
 
 RSpec::Core::RakeTask.new :specdoc do |t|
-  t.rspec_opts = %w(-fl)
+  t.rspec_opts = %w[-fl]
 end
 
 task default: :spec
 
-desc "Open an irb session preloaded with this library"
+desc 'Open an irb session preloaded with this library'
 task :console do
-  sh "irb -rubygems -I lib -r dbf.rb"
+  sh 'irb -r rubygems -I lib -r dbf.rb'
 end
diff --git a/bin/dbf b/bin/dbf
index 1c57414..f450413 100755
--- a/bin/dbf
+++ b/bin/dbf
@@ -1,5 +1,7 @@
 #!/usr/bin/env ruby
 
+Signal.trap('PIPE', 'SYSTEM_DEFAULT')
+
 require 'dbf'
 require 'dbf/version'
 require 'optparse'
@@ -9,18 +11,18 @@ params = ARGV.getopts('h', 's', 'a', 'c', 'r', 'v')
 if params['v']
   puts "dbf version: #{DBF::VERSION}"
 
-elsif params['h'] then
+elsif params['h']
   puts "usage: #{File.basename(__FILE__)} [-h|-s|-a|-c|-r] filename"
-  puts "  -h = print this message"
-  puts "  -v = print the DBF gem version"
-  puts "  -s = print summary information"
-  puts "  -a = create an ActiveRecord::Schema"
-  puts "  -r = create a Sequel migration"
-  puts "  -c = create a CSV file"
+  puts '  -h = print this message'
+  puts '  -v = print the DBF gem version'
+  puts '  -s = print summary information'
+  puts '  -a = create an ActiveRecord::Schema'
+  puts '  -r = create a Sequel migration'
+  puts '  -c = export as CSV'
 else
 
   filename = ARGV.shift
-  abort "You must supply a filename on the command line" unless filename
+  abort 'You must supply a filename on the command line' unless filename
 
   # create an ActiveRecord::Schema
   if params['a']
@@ -43,10 +45,10 @@ else
     puts "Records: #{table.record_count}"
 
     puts "\nFields:"
-    puts "Name             Type       Length     Decimal"
-    puts "-" * 78
+    puts 'Name             Type       Length     Decimal'
+    puts '-' * 78
     table.columns.each do |f|
-      puts "%-16s %-10s %-10s %-10s" % [f.name, f.type, f.length, f.decimal]
+      puts format('%-16s %-10s %-10s %-10s', f.name, f.type, f.length, f.decimal)
     end
   end
 
diff --git a/ci_A.dbf b/ci_A.dbf
new file mode 100644
index 0000000..29f7e23
Binary files /dev/null and b/ci_A.dbf differ
diff --git a/dbf.gemspec b/dbf.gemspec
index de16487..5444505 100644
--- a/dbf.gemspec
+++ b/dbf.gemspec
@@ -1,4 +1,4 @@
-lib = File.expand_path('../lib/', __FILE__)
+lib = File.expand_path('lib', __dir__)
 $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
 require 'dbf/version'
 
@@ -19,4 +19,5 @@ Gem::Specification.new do |s|
   s.test_files = Dir.glob('spec/**/*_spec.rb')
   s.require_paths = ['lib']
   s.required_rubygems_version = Gem::Requirement.new('>= 1.3.0')
+  s.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
 end
diff --git a/debian/changelog b/debian/changelog
index 836cffc..f2768c9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,10 +1,12 @@
-ruby-dbf (3.0.5-3) UNRELEASED; urgency=medium
+ruby-dbf (4.2.2-1) UNRELEASED; urgency=medium
 
   * Remove constraints unnecessary since buster (oldstable):
     + Build-Depends: Drop versioned constraint on rake.
   * Update standards version to 4.6.2, no changes needed.
+  * New upstream release.
+  * Drop patch 0002-Fix-Ruby-3-compatibility.patch, present upstream.
 
- -- Debian Janitor <janitor@jelmer.uk>  Thu, 13 Oct 2022 02:07:05 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 11 Jan 2023 04:13:58 -0000
 
 ruby-dbf (3.0.5-2) unstable; urgency=medium
 
diff --git a/debian/patches/0001-default-external-encoding.patch b/debian/patches/0001-default-external-encoding.patch
index ac0a1b4..6f47f0c 100644
--- a/debian/patches/0001-default-external-encoding.patch
+++ b/debian/patches/0001-default-external-encoding.patch
@@ -8,10 +8,10 @@ Last-Update: 2016-07-08
  spec/spec_helper.rb | 2 ++
  1 file changed, 2 insertions(+)
 
-diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
-index d30f00d..b2bcdbf 100644
---- a/spec/spec_helper.rb
-+++ b/spec/spec_helper.rb
+Index: ruby-dbf.git/spec/spec_helper.rb
+===================================================================
+--- ruby-dbf.git.orig/spec/spec_helper.rb
++++ ruby-dbf.git/spec/spec_helper.rb
 @@ -4,6 +4,8 @@ begin
  rescue LoadError
  end
diff --git a/debian/patches/0002-Fix-Ruby-3-compatibility.patch b/debian/patches/0002-Fix-Ruby-3-compatibility.patch
deleted file mode 100644
index 1bb8595..0000000
--- a/debian/patches/0002-Fix-Ruby-3-compatibility.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From: Keith Morrison <keithm@infused.org>
-Date: Mon, 6 Apr 2020 14:20:01 -0700
-Subject: [PATCH] use 2.7.1 for dev
-
-Reviewed-By: Daniel Leidert <dleidert@debian.org>
-Bug-Debian: https://bugs.debian.org/996215
-Origin: https://github.com/infused/dbf/commit/c05fdddd4dc88f066040d903f7db8d6b3c576997.patch
-Forwarded: not-needed
----
- lib/dbf/column_type.rb | 9 ++-------
- 1 file changed, 2 insertions(+), 7 deletions(-)
-
-diff --git a/lib/dbf/column_type.rb b/lib/dbf/column_type.rb
-index fd7e10d..b2b1f66 100644
---- a/lib/dbf/column_type.rb
-+++ b/lib/dbf/column_type.rb
-@@ -1,11 +1,6 @@
- module DBF
-   module ColumnType
-     class Base
--      ENCODING_ARGS = [
--        Encoding.default_external,
--        {undef: :replace, invalid: :replace}
--      ]
--
-       attr_reader :decimal, :encoding
- 
-       def initialize(decimal, encoding)
-@@ -79,7 +74,7 @@ module DBF
-     class Memo < Base
-       def type_cast(value)
-         if encoding && !value.nil?
--          value.force_encoding(@encoding).encode(*ENCODING_ARGS)
-+          value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace)
-         else
-           value
-         end
-@@ -95,7 +90,7 @@ module DBF
-     class String < Base
-       def type_cast(value)
-         value = value.strip
--        @encoding ? value.force_encoding(@encoding).encode(*ENCODING_ARGS) : value
-+        @encoding ? value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace) : value
-       end
-     end
- 
diff --git a/debian/patches/series b/debian/patches/series
index 40b81e3..cc00942 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1 @@
 0001-default-external-encoding.patch
-0002-Fix-Ruby-3-compatibility.patch
diff --git a/docs/supported_encodings.csv b/docs/supported_encodings.csv
deleted file mode 100644
index 97084a4..0000000
--- a/docs/supported_encodings.csv
+++ /dev/null
@@ -1,60 +0,0 @@
-"Code Page", "Encoding", "Description"
-"01", "cp437", "U.S. MS–DOS"
-"02", "cp850", "International MS–DOS"
-"03", "cp1252", "Windows ANSI"
-"08", "cp865", "Danish OEM"
-"09", "cp437", "Dutch OEM"
-"0a", "cp850", "Dutch OEM*"
-"0b", "cp437", "Finnish OEM"
-"0d", "cp437", "French OEM"
-"0e", "cp850", "French OEM*"
-"0f", "cp437", "German OEM"
-"10", "cp850", "German OEM*"
-"11", "cp437", "Italian OEM"
-"12", "cp850", "Italian OEM*"
-"13", "cp932", "Japanese Shift-JIS"
-"14", "cp850", "Spanish OEM*"
-"15", "cp437", "Swedish OEM"
-"16", "cp850", "Swedish OEM*"
-"17", "cp865", "Norwegian OEM"
-"18", "cp437", "Spanish OEM"
-"19", "cp437", "English OEM (Britain)"
-"1a", "cp850", "English OEM (Britain)*"
-"1b", "cp437", "English OEM (U.S.)"
-"1c", "cp863", "French OEM (Canada)"
-"1d", "cp850", "French OEM*"
-"1f", "cp852", "Czech OEM"
-"22", "cp852", "Hungarian OEM"
-"23", "cp852", "Polish OEM"
-"24", "cp860", "Portuguese OEM"
-"25", "cp850", "Portuguese OEM*"
-"26", "cp866", "Russian OEM"
-"37", "cp850", "English OEM (U.S.)*"
-"40", "cp852", "Romanian OEM"
-"4d", "cp936", "Chinese GBK (PRC)"
-"4e", "cp949", "Korean (ANSI/OEM)"
-"4f", "cp950", "Chinese Big5 (Taiwan)"
-"50", "cp874", "Thai (ANSI/OEM)"
-"57", "cp1252", "ANSI"
-"58", "cp1252", "Western European ANSI"
-"59", "cp1252", "Spanish ANSI"
-"64", "cp852", "Eastern European MS–DOS"
-"65", "cp866", "Russian MS–DOS"
-"66", "cp865", "Nordic MS–DOS"
-"67", "cp861", "Icelandic MS–DOS"
-"6a", "cp737", "Greek MS–DOS (437G)"
-"6b", "cp857", "Turkish MS–DOS"
-"6c", "cp863", "French–Canadian MS–DOS"
-"78", "cp950", "Taiwan Big 5"
-"79", "cp949", "Hangul (Wansung)"
-"7a", "cp936", "PRC GBK"
-"7b", "cp932", "Japanese Shift-JIS"
-"7c", "cp874", "Thai Windows/MS–DOS"
-"86", "cp737", "Greek OEM"
-"87", "cp852", "Slovenian OEM"
-"88", "cp857", "Turkish OEM"
-"c8", "cp1250", "Eastern European Windows"
-"c9", "cp1251", "Russian Windows"
-"ca", "cp1254", "Turkish Windows"
-"cb", "cp1253", "Greek Windows"
-"cc", "cp1257", "Baltic Windows"
diff --git a/docs/supported_types.markdown b/docs/supported_types.markdown
deleted file mode 100644
index 508c7ed..0000000
--- a/docs/supported_types.markdown
+++ /dev/null
@@ -1,53 +0,0 @@
-# DBF supported data types
-
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | Version | Description                       | C | N | L | D | M | F | B | G | P | Y | T | I | V | X | @ | O | + |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 02      | FoxBase                           | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 03      | dBase III without memo file       | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 04      | dBase IV without memo file        | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 05      | dBase V without memo file         | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 07      | Visual Objects 1.x                | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 30      | Visual FoxPro                     | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 31      | Visual FoxPro with AutoIncrement  | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 7b      | dBase IV with memo file           | Y | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 83      | dBase III with memo file          | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 87      | Visual Objects 1.x with memo file | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 8b      | dBase IV with memo file           | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | 8e      | dBase IV with SQL table           | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | N | - | - | - |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | f5      | FoxPro with memo file             | Y | Y | Y | Y | Y | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    | fb      | FoxPro without memo file          | Y | Y | Y | Y | - | Y | Y | Y | N | Y | N | Y | N | N | N | N | N |
-    +---------+-----------------------------------+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
-
-Data type descriptions
-
-* C = Character
-* N = Number
-* L = Logical
-* D = Date
-* M = Memo
-* F = Float
-* B = Binary
-* G = General
-* P = Picture
-* Y = Currency
-* T = DateTime
-* I = Integer
-* V = VariField
-* X = SQL compat
-* @ = Timestamp
-* O = Double
-* + = Autoincrement
diff --git a/lib/dbf.rb b/lib/dbf.rb
index 79740dd..2d54e5d 100644
--- a/lib/dbf.rb
+++ b/lib/dbf.rb
@@ -1,7 +1,8 @@
-require 'date'
-
 require 'csv'
+require 'date'
+require 'forwardable'
 require 'json'
+require 'time'
 
 require 'dbf/version'
 require 'dbf/schema'
diff --git a/lib/dbf/column.rb b/lib/dbf/column.rb
index 3896e61..aa90aa3 100644
--- a/lib/dbf/column.rb
+++ b/lib/dbf/column.rb
@@ -1,12 +1,15 @@
 module DBF
   class Column
-    # Raised if length is less than 1
-    class LengthError < StandardError; end
+    extend Forwardable
 
-    # Raised if name is empty
-    class NameError < StandardError; end
+    class LengthError < StandardError
+    end
+
+    class NameError < StandardError
+    end
 
     attr_reader :table, :name, :type, :length, :decimal
+    def_delegator :type_cast_class, :type_cast
 
     TYPE_CAST_CLASS = {
       N: ColumnType::Number,
@@ -18,16 +21,19 @@ module DBF
       L: ColumnType::Boolean,
       M: ColumnType::Memo,
       B: ColumnType::Double,
-      G: ColumnType::General
+      G: ColumnType::General,
+      '+'.to_sym => ColumnType::SignedLong2
     }
     TYPE_CAST_CLASS.default = ColumnType::String
+    TYPE_CAST_CLASS.freeze
 
     # Initialize a new DBF::Column
     #
-    # @param [String] name
-    # @param [String] type
-    # @param [Fixnum] length
-    # @param [Fixnum] decimal
+    # @param table [String]
+    # @param name [String]
+    # @param type [String]
+    # @param length [Integer]
+    # @param decimal [Integer]
     def initialize(table, name, type, length, decimal)
       @table = table
       @name = clean(name)
@@ -41,14 +47,6 @@ module DBF
       validate_name
     end
 
-    # Cast value to native type
-    #
-    # @param [String] value
-    # @return [Fixnum, Float, Date, DateTime, Boolean, String]
-    def type_cast(value)
-      type_cast_class.type_cast(value)
-    end
-
     # Returns true if the column is a memo
     #
     # @return [Boolean]
@@ -56,18 +54,11 @@ module DBF
       @memo ||= type == 'M'
     end
 
-    # Schema definition
+    # Returns a Hash with :name, :type, :length, and :decimal keys
     #
-    # @return [String]
-    def schema_definition
-      "\"#{underscored_name}\", #{schema_data_type}\n"
-    end
-
-    # Sequel Schema definition
-    #
-    # @return [String]
-    def sequel_schema_definition
-      ":#{underscored_name}, #{schema_data_type(:sequel)}\n"
+    # @return [Hash]
+    def to_hash
+      {name: name, type: type, length: length, decimal: decimal}
     end
 
     # Underscored name
@@ -78,88 +69,50 @@ module DBF
     # @return [String]
     def underscored_name
       @underscored_name ||= begin
-        un = name.dup
-        un.gsub!(/::/, '/')
-        un.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
-        un.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-        un.tr!('-', '_')
-        un.downcase!
-        un
+        name.gsub(/::/, '/')
+          .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
+          .gsub(/([a-z\d])([A-Z])/, '\1_\2')
+          .tr('-', '_')
+          .downcase
       end
     end
 
-    def to_hash
-      {name: name, type: type, length: length, decimal: decimal}
-    end
-
     private
 
-    def type_cast_class # nodoc
-      @type_cast_class ||= begin
-        klass = @length == 0 ? ColumnType::Nil : TYPE_CAST_CLASS[type.to_sym]
-        klass.new(@decimal, @encoding)
-      end
+    def clean(value) # :nodoc:
+      value.strip.gsub("\x00", '').gsub(/[^\x20-\x7E]/, '')
     end
 
-    def encode(value, strip_output = false) # nodoc
-      return value if !value.respond_to?(:encoding)
+    def encode(value, strip_output = false) # :nodoc:
+      return value unless value.respond_to?(:encoding)
 
       output = @encoding ? encode_string(value) : value
       strip_output ? output.strip : output
     end
 
-    def encode_string(string)
-      string.force_encoding(@encoding).encode(*encoding_args)
-    end
-
-    def encoding_args # nodoc
+    def encoding_args # :nodoc:
       @encoding_args ||= [
         Encoding.default_external,
         {undef: :replace, invalid: :replace}
       ]
     end
 
-    def schema_data_type(format = :activerecord) # nodoc
-      case type
-      when 'N', 'F'
-        decimal > 0 ? ':float' : ':integer'
-      when 'I'
-        ':integer'
-      when 'Y'
-        ':decimal, :precision => 15, :scale => 4'
-      when 'D'
-        ':date'
-      when 'T'
-        ':datetime'
-      when 'L'
-        ':boolean'
-      when 'M'
-        ':text'
-      when 'B'
-        if DBF::Table::FOXPRO_VERSIONS.keys.include?(@version)
-          ':float'
-        else
-          ':text'
-        end
-      else
-        if format == :sequel
-          ":varchar, :size => #{length}"
-        else
-          ":string, :limit => #{length}"
-        end
-      end
+    def encode_string(string) # :nodoc:
+      string.force_encoding(@encoding).encode(*encoding_args)
     end
 
-    def clean(value) # nodoc
-      truncated_value = value.strip.partition("\x00").first
-      truncated_value.gsub(/[^\x20-\x7E]/, '')
+    def type_cast_class # :nodoc:
+      @type_cast_class ||= begin
+        klass = @length == 0 ? ColumnType::Nil : TYPE_CAST_CLASS[type.to_sym]
+        klass.new(@decimal, @encoding)
+      end
     end
 
-    def validate_length # nodoc
+    def validate_length # :nodoc:
       raise LengthError, 'field length must be 0 or greater' if length < 0
     end
 
-    def validate_name # nodoc
+    def validate_name # :nodoc:
       raise NameError, 'column name cannot be empty' if @name.empty?
     end
   end
diff --git a/lib/dbf/column_type.rb b/lib/dbf/column_type.rb
index fd7e10d..9c433a1 100644
--- a/lib/dbf/column_type.rb
+++ b/lib/dbf/column_type.rb
@@ -1,13 +1,10 @@
 module DBF
   module ColumnType
     class Base
-      ENCODING_ARGS = [
-        Encoding.default_external,
-        {undef: :replace, invalid: :replace}
-      ]
-
       attr_reader :decimal, :encoding
 
+      # @param decimal [Integer]
+      # @param encoding [String, Encoding]
       def initialize(decimal, encoding)
         @decimal = decimal
         @encoding = encoding
@@ -15,71 +12,90 @@ module DBF
     end
 
     class Nil < Base
-      def type_cast(value)
+      # @param _value [String]
+      def type_cast(_value)
         nil
       end
     end
 
     class Number < Base
+      # @param value [String]
       def type_cast(value)
         return nil if value.strip.empty?
+
         @decimal.zero? ? value.to_i : value.to_f
       end
     end
 
     class Currency < Base
+      # @param value [String]
       def type_cast(value)
-        (value.unpack('q<')[0] / 10_000.0).to_f
+        (value.unpack1('q<') / 10_000.0).to_f
       end
     end
 
     class SignedLong < Base
+      # @param value [String]
+      def type_cast(value)
+        value.unpack1('l<')
+      end
+    end
+
+    class SignedLong2 < Base
+      # @param value [String]
       def type_cast(value)
-        value.unpack('l<')[0]
+        s = value.unpack1('B*')
+        sign_multiplier = s[0] == '0' ? -1 : 1
+        s[1, 31].to_i(2) * sign_multiplier
       end
     end
 
     class Float < Base
+      # @param value [String]
       def type_cast(value)
         value.to_f
       end
     end
 
     class Double < Base
+      # @param value [String]
       def type_cast(value)
-        value.unpack('E')[0]
+        value.unpack1('E')
       end
     end
 
     class Boolean < Base
+      # @param value [String]
       def type_cast(value)
-        value.strip =~ /^(y|t)$/i ? true : false
+        value.strip.match?(/^(y|t)$/i)
       end
     end
 
     class Date < Base
+      # @param value [String]
       def type_cast(value)
-        v = value.tr(' ', '0')
-        v !~ /\S/ ? nil : ::Date.parse(v)
-      rescue
+        value.match?(/\d{8}/) && ::Date.strptime(value, '%Y%m%d')
+      rescue StandardError
         nil
       end
     end
 
     class DateTime < Base
+      # @param value [String]
       def type_cast(value)
         days, msecs = value.unpack('l2')
         secs = (msecs / 1000).to_i
-        ::DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60)
-      rescue
+        ::DateTime.jd(days, (secs / 3600).to_i, (secs / 60).to_i % 60, secs % 60).to_time
+      rescue StandardError
         nil
       end
     end
 
     class Memo < Base
+      # @param value [String]
       def type_cast(value)
         if encoding && !value.nil?
-          value.force_encoding(@encoding).encode(*ENCODING_ARGS)
+          value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace)
         else
           value
         end
@@ -87,17 +103,18 @@ module DBF
     end
 
     class General < Base
+      # @param value [String]
       def type_cast(value)
         value
       end
     end
 
     class String < Base
+      # @param value [String]
       def type_cast(value)
         value = value.strip
-        @encoding ? value.force_encoding(@encoding).encode(*ENCODING_ARGS) : value
+        @encoding ? value.force_encoding(@encoding).encode(Encoding.default_external, undef: :replace, invalid: :replace) : value
       end
     end
-
   end
 end
diff --git a/lib/dbf/database/foxpro.rb b/lib/dbf/database/foxpro.rb
index 09a50f8..fcc65c3 100644
--- a/lib/dbf/database/foxpro.rb
+++ b/lib/dbf/database/foxpro.rb
@@ -16,12 +16,13 @@ module DBF
       #
       #  # Calling a table
       #  contacts = db.contacts.record(0)
+      #
+      # @param path [String]
       def initialize(path)
         @path = path
         @dirname = File.dirname(@path)
         @db = DBF::Table.new(@path)
         @tables = extract_dbc_data
-
       rescue Errno::ENOENT
         raise DBF::FileNotFoundError, "file not found: #{data}"
       end
@@ -31,9 +32,11 @@ module DBF
       end
 
       # Returns table with given name
-      # @return Table
+      #
+      # @param name [String]
+      # @return [DBF::Table]
       def table(name)
-        Table.new(table_path name) do |table|
+        Table.new table_path(name) do |table|
           table.long_names = @tables[name]
         end
       end
@@ -41,25 +44,23 @@ module DBF
       # Searches the database directory for the table's dbf file
       # and returns the absolute path. Ensures case-insensitivity
       # on any platform.
-      # @return String
+      # @param name [String]
+      # @return [String]
       def table_path(name)
-        example = File.join(@dirname, "#{name}.dbf")
-        glob = File.join(@dirname, '*')
-        path = Dir.glob(glob).find { |match| match.downcase == example.downcase }
+        glob = File.join(@dirname, "#{name}.dbf")
+        path = Dir.glob(glob, File::FNM_CASEFOLD).first
 
-        unless path && File.exist?(path)
-          raise DBF::FileNotFoundError, "related table not found: #{name}"
-        end
+        raise DBF::FileNotFoundError, "related table not found: #{name}" unless path && File.exist?(path)
 
         path
       end
 
-      def method_missing(method, *args) # nodoc
-        if table_names.index(method.to_s)
-          table method.to_s
-        else
-          super
-        end
+      def method_missing(method, *args) # :nodoc:
+        table_names.index(method.to_s) ? table(method.to_s) : super
+      end
+
+      def respond_to_missing?(method, *)
+        table_names.index(method.to_s) || super
       end
 
       private
@@ -68,7 +69,7 @@ module DBF
       # just an ordinary table with a treelike structure. Field definitions
       # are in the same order as in the linked tables but only the long name
       # is provided.
-      def extract_dbc_data # nodoc
+      def extract_dbc_data # :nodoc:
         data = {}
         @db.each do |record|
           next unless record
@@ -111,7 +112,7 @@ module DBF
     class Table < DBF::Table
       attr_accessor :long_names
 
-      def build_columns # nodoc
+      def build_columns # :nodoc:
         columns = super
 
         # modify the column definitions to use the long names as the
diff --git a/lib/dbf/encodings.rb b/lib/dbf/encodings.rb
index 58d7aa2..040b254 100644
--- a/lib/dbf/encodings.rb
+++ b/lib/dbf/encodings.rb
@@ -58,6 +58,6 @@ module DBF
     'c9' => 'cp1251',      # Russian Windows
     'ca' => 'cp1254',      # Turkish Windows
     'cb' => 'cp1253',      # Greek Windows
-    'cc' => 'cp1257',      # Baltic Windows
-  }
+    'cc' => 'cp1257'       # Baltic Windows
+  }.freeze
 end
diff --git a/lib/dbf/header.rb b/lib/dbf/header.rb
index 0bc9696..968debb 100644
--- a/lib/dbf/header.rb
+++ b/lib/dbf/header.rb
@@ -9,12 +9,20 @@ module DBF
 
     def initialize(data)
       @data = data
-      @version, @record_count, @header_length, @record_length, @encoding_key = unpack_header
-      @encoding = DBF::ENCODINGS[@encoding_key]
+      unpack_header
     end
 
     def unpack_header
-      @data.unpack('H2 x3 V v2 x17H2')
+      @version = @data.unpack('H2').first
+
+      case @version
+      when '02'
+        @record_count, @record_length = @data.unpack('x v x3 v')
+        @header_length = 521
+      else
+        @record_count, @header_length, @record_length, @encoding_key = @data.unpack('x x3 V v2 x17 H2')
+        @encoding = DBF::ENCODINGS[@encoding_key]
+      end
     end
   end
 end
diff --git a/lib/dbf/memo/base.rb b/lib/dbf/memo/base.rb
index 6d99999..0179d08 100644
--- a/lib/dbf/memo/base.rb
+++ b/lib/dbf/memo/base.rb
@@ -15,6 +15,7 @@ module DBF
 
       def get(start_block)
         return nil unless start_block > 0
+
         build_memo start_block
       end
 
@@ -28,19 +29,19 @@ module DBF
 
       private
 
-      def offset(start_block) # nodoc
+      def offset(start_block) # :nodoc:
         start_block * block_size
       end
 
-      def content_size(memo_size) # nodoc
+      def content_size(memo_size) # :nodoc:
         (memo_size - block_size) + BLOCK_HEADER_SIZE
       end
 
-      def block_content_size # nodoc
+      def block_content_size # :nodoc:
         @block_content_size ||= block_size - BLOCK_HEADER_SIZE
       end
 
-      def block_size #nodoc
+      def block_size # :nodoc:
         BLOCK_SIZE
       end
     end
diff --git a/lib/dbf/memo/dbase3.rb b/lib/dbf/memo/dbase3.rb
index 41188c5..bb1c286 100644
--- a/lib/dbf/memo/dbase3.rb
+++ b/lib/dbf/memo/dbase3.rb
@@ -1,7 +1,7 @@
 module DBF
   module Memo
     class Dbase3 < Base
-      def build_memo(start_block) # nodoc
+      def build_memo(start_block) # :nodoc:
         @data.seek offset(start_block)
         memo_string = ''
         loop do
diff --git a/lib/dbf/memo/dbase4.rb b/lib/dbf/memo/dbase4.rb
index 7b47f40..1ae5b57 100644
--- a/lib/dbf/memo/dbase4.rb
+++ b/lib/dbf/memo/dbase4.rb
@@ -1,9 +1,9 @@
 module DBF
   module Memo
     class Dbase4 < Base
-      def build_memo(start_block) # nodoc
+      def build_memo(start_block) # :nodoc:
         @data.seek offset(start_block)
-        @data.read(@data.read(BLOCK_HEADER_SIZE).unpack('x4L').first)
+        @data.read(@data.read(BLOCK_HEADER_SIZE).unpack1('x4L'))
       end
     end
   end
diff --git a/lib/dbf/memo/foxpro.rb b/lib/dbf/memo/foxpro.rb
index 90e55bd..68fe7fd 100644
--- a/lib/dbf/memo/foxpro.rb
+++ b/lib/dbf/memo/foxpro.rb
@@ -3,7 +3,7 @@ module DBF
     class Foxpro < Base
       FPT_HEADER_SIZE = 512
 
-      def build_memo(start_block) # nodoc
+      def build_memo(start_block) # :nodoc:
         @data.seek offset(start_block)
 
         memo_type, memo_size, memo_string = @data.read(block_size).unpack('NNa*')
@@ -15,17 +15,16 @@ module DBF
           memo_string = memo_string[0, memo_size]
         end
         memo_string
-
-      rescue
+      rescue StandardError
         nil
       end
 
       private
 
-      def block_size # nodoc
+      def block_size # :nodoc:
         @block_size ||= begin
           @data.rewind
-          @data.read(FPT_HEADER_SIZE).unpack('x6n').first || 0
+          @data.read(FPT_HEADER_SIZE).unpack1('x6n') || 0
         end
       end
     end
diff --git a/lib/dbf/record.rb b/lib/dbf/record.rb
index 19efda3..b7430b1 100644
--- a/lib/dbf/record.rb
+++ b/lib/dbf/record.rb
@@ -3,10 +3,10 @@ module DBF
   class Record
     # Initialize a new DBF::Record
     #
-    # @data [String, StringIO] data
-    # @columns [Column]
-    # @version [String]
-    # @memo [DBF::Memo]
+    # @param data [String, StringIO] data
+    # @param columns [Column]
+    # @param version [String]
+    # @param memo [DBF::Memo]
     def initialize(data, columns, version, memo)
       @data = StringIO.new(data)
       @columns = columns
@@ -22,24 +22,9 @@ module DBF
       other.respond_to?(:attributes) && other.attributes == attributes
     end
 
-    # Maps a row to an array of values
-    #
-    # @return [Array]
-    def to_a
-      @columns.map { |column| attributes[column.name] }
-    end
-
-    # Do all search parameters match?
-    #
-    # @param [Hash] options
-    # @return [Boolean]
-    def match?(options)
-      options.all? { |key, value| self[key] == value }
-    end
-
     # Reads attributes by column name
     #
-    # @param [String, Symbol] key
+    # @param name [String, Symbol] key
     def [](name)
       key = name.to_s
       if attributes.key?(key)
@@ -53,48 +38,35 @@ module DBF
     #
     # @return [Hash]
     def attributes
-      @attributes ||= Hash[attribute_map]
+      @attributes ||= Hash[column_names.zip(to_a)]
     end
 
-    # Overrides standard Object.respond_to? to return true if a
-    # matching column name is found.
+    # Do all search parameters match?
     #
-    # @param [String, Symbol] method
+    # @param [Hash] options
     # @return [Boolean]
-    def respond_to?(method, *args)
-      underscored_column_names.include?(method.to_s) || super
-    end
-
-    private
-
-    def attribute_map # nodoc
-      @columns.map { |column| [column.name, init_attribute(column)] }
+    def match?(options)
+      options.all? { |key, value| self[key] == value }
     end
 
-    def file_offset(attribute_name) # nodoc
-      column = @columns.detect { |c| c.name == attribute_name.to_s }
-      index = @columns.index(column)
-      @columns[0, index + 1].compact.reduce(0) { |x, c| x += c.length }
+    # Maps a row to an array of values
+    #
+    # @return [Array]
+    def to_a
+      @to_a ||= @columns.map { |column| init_attribute(column) }
     end
 
-    def method_missing(method, *args) # nodoc
-      if (index = underscored_column_names.index(method.to_s))
-        attributes[@columns[index].name]
-      else
-        super
-      end
-    end
+    private
 
-    def underscored_column_names # nodoc
-      @underscored_column_names ||= @columns.map(&:underscored_name)
+    def column_names # :nodoc:
+      @column_names ||= @columns.map { |column| column.name }
     end
 
-    def init_attribute(column) # nodoc
-      value = column.memo? ? memo(column) : get_data(column)
-      column.type_cast(value)
+    def get_data(column) # :nodoc:
+      @data.read(column.length)
     end
 
-    def memo(column) # nodoc
+    def get_memo(column) # :nodoc:
       if @memo
         @memo.get(memo_start_block(column))
       else
@@ -104,16 +76,31 @@ module DBF
       end
     end
 
-    def memo_start_block(column) # nodoc
+    def init_attribute(column) # :nodoc:
+      value = column.memo? ? get_memo(column) : get_data(column)
+      column.type_cast(value)
+    end
+
+    def memo_start_block(column) # :nodoc:
       data = get_data(column)
-      if %w(30 31).include?(@version)
-        data = data.unpack('V').first
-      end
+      data = data.unpack1('V') if %w[30 31].include?(@version)
       data.to_i
     end
 
-    def get_data(column) # nodoc
-      @data.read(column.length)
+    def method_missing(method, *args) # :nodoc:
+      if (index = underscored_column_names.index(method.to_s))
+        attributes[@columns[index].name]
+      else
+        super
+      end
+    end
+
+    def respond_to_missing?(method, *) # :nodoc:
+      underscored_column_names.include?(method.to_s) || super
+    end
+
+    def underscored_column_names # :nodoc:
+      @underscored_column_names ||= @columns.map(&:underscored_name)
     end
   end
 end
diff --git a/lib/dbf/schema.rb b/lib/dbf/schema.rb
index fa53b3c..7f67462 100644
--- a/lib/dbf/schema.rb
+++ b/lib/dbf/schema.rb
@@ -1,6 +1,17 @@
 module DBF
   # The Schema module is mixin for the Table class
   module Schema
+    FORMATS = [:activerecord, :json, :sequel].freeze
+
+    OTHER_DATA_TYPES = {
+      'Y' => ':decimal, :precision => 15, :scale => 4',
+      'D' => ':date',
+      'T' => ':datetime',
+      'L' => ':boolean',
+      'M' => ':text',
+      'B' => ':binary'
+    }.freeze
+
     # Generate an ActiveRecord::Schema
     #
     # xBase data types are converted to generic types as follows:
@@ -21,43 +32,85 @@ module DBF
     #     t.column :notes, :text
     #   end
     #
-    # @param [Symbol] format Valid options are :activerecord and :json
+    # @param format [Symbol] format Valid options are :activerecord and :json
+    # @param table_only [Boolean]
     # @return [String]
     def schema(format = :activerecord, table_only = false)
-      supported_formats = [:activerecord, :json, :sequel]
-      if supported_formats.include?(format)
-        send("#{format}_schema", table_only)
-      else
-        raise ArgumentError
-      end
+      schema_method_name = schema_name(format)
+      send(schema_method_name, table_only)
+    rescue NameError
+      raise ArgumentError, ":#{format} is not a valid schema. Valid schemas are: #{FORMATS.join(', ')}."
+    end
+
+    def schema_name(format) # :nodoc:
+      "#{format}_schema"
     end
 
-    def activerecord_schema(_table_only = false)
+    def activerecord_schema(_table_only = false) # :nodoc:
       s = "ActiveRecord::Schema.define do\n"
       s << "  create_table \"#{name}\" do |t|\n"
       columns.each do |column|
-        s << "    t.column #{column.schema_definition}"
+        s << "    t.column #{activerecord_schema_definition(column)}"
       end
       s << "  end\nend"
       s
     end
 
-    def sequel_schema(table_only = false)
+    def sequel_schema(table_only = false) # :nodoc:
       s = ''
       s << "Sequel.migration do\n" unless table_only
       s << "  change do\n " unless table_only
       s << "    create_table(:#{name}) do\n"
       columns.each do |column|
-        s << "      column #{column.sequel_schema_definition}"
+        s << "      column #{sequel_schema_definition(column)}"
       end
       s << "    end\n"
-      s << "  end\n"  unless table_only
-      s << "end\n"  unless table_only
+      s << "  end\n" unless table_only
+      s << "end\n" unless table_only
       s
     end
 
-    def json_schema(_table_only = false)
+    def json_schema(_table_only = false) # :nodoc:
       columns.map(&:to_hash).to_json
     end
+
+    # ActiveRecord schema definition
+    #
+    # @param column [DBF::Column]
+    # @return [String]
+    def activerecord_schema_definition(column)
+      "\"#{column.underscored_name}\", #{schema_data_type(column, :activerecord)}\n"
+    end
+
+    # Sequel schema definition
+    #
+    # @param column [DBF::Column]
+    # @return [String]
+    def sequel_schema_definition(column)
+      ":#{column.underscored_name}, #{schema_data_type(column, :sequel)}\n"
+    end
+
+    def schema_data_type(column, format = :activerecord) # :nodoc:
+      case column.type
+      when *%w[N F I]
+        number_data_type(column)
+      when *%w[Y D T L M B]
+        OTHER_DATA_TYPES[column.type]
+      else
+        string_data_format(format, column)
+      end
+    end
+
+    def number_data_type(column)
+      column.decimal > 0 ? ':float' : ':integer'
+    end
+
+    def string_data_format(format, column)
+      if format == :sequel
+        ":varchar, :size => #{column.length}"
+      else
+        ":string, :limit => #{column.length}"
+      end
+    end
   end
 end
diff --git a/lib/dbf/table.rb b/lib/dbf/table.rb
index 1fedda9..bafe693 100644
--- a/lib/dbf/table.rb
+++ b/lib/dbf/table.rb
@@ -2,13 +2,19 @@ module DBF
   class FileNotFoundError < StandardError
   end
 
+  class NoColumnsDefined < StandardError
+  end
+
   # DBF::Table is the primary interface to a single DBF file and provides
   # methods for enumerating and searching the records.
   class Table
+    extend Forwardable
     include Enumerable
-    include Schema
+    include ::DBF::Schema
 
-    DBF_HEADER_SIZE = 32
+    DBASE2_HEADER_SIZE = 8
+    DBASE3_HEADER_SIZE = 32
+    DBASE7_HEADER_SIZE = 68
 
     VERSIONS = {
       '02' => 'FoxBase',
@@ -17,6 +23,7 @@ module DBF
       '05' => 'dBase V without memo file',
       '07' => 'Visual Objects 1.x',
       '30' => 'Visual FoxPro',
+      '32' => 'Visual FoxPro with field type Varchar or Varbinary',
       '31' => 'Visual FoxPro with AutoIncrement field',
       '43' => 'dBASE IV SQL table files, no memo',
       '63' => 'dBASE IV SQL system files, no memo',
@@ -24,23 +31,28 @@ module DBF
       '83' => 'dBase III with memo file',
       '87' => 'Visual Objects 1.x with memo file',
       '8b' => 'dBase IV with memo file',
+      '8c' => 'dBase 7',
       '8e' => 'dBase IV with SQL table',
       'cb' => 'dBASE IV SQL table files, with memo',
       'f5' => 'FoxPro with memo file',
       'fb' => 'FoxPro without memo file'
-    }
+    }.freeze
 
     FOXPRO_VERSIONS = {
       '30' => 'Visual FoxPro',
       '31' => 'Visual FoxPro with AutoIncrement field',
       'f5' => 'FoxPro with memo file',
       'fb' => 'FoxPro without memo file'
-    }
+    }.freeze
 
-    attr_reader :header
     attr_accessor :encoding
     attr_writer :name
 
+    def_delegator :header, :header_length
+    def_delegator :header, :record_count
+    def_delegator :header, :record_length
+    def_delegator :header, :version
+
     # Opens a DBF::Table
     # Examples:
     #   # working with a file stored on the filesystem
@@ -59,23 +71,14 @@ module DBF
     #   table = DBF::Table.new 'data.dbf', nil, 'cp437'
     #   table = DBF::Table.new 'data.dbf', 'memo.dbt', Encoding::US_ASCII
     #
-    # @param [String, StringIO] data Path to the dbf file or a StringIO object
-    # @param [optional String, StringIO] memo Path to the memo file or a StringIO object
-    # @param [optional String, Encoding] encoding Name of the encoding or an Encoding object
+    # @param data [String, StringIO] data Path to the dbf file or a StringIO object
+    # @param memo [optional String, StringIO] memo Path to the memo file or a StringIO object
+    # @param encoding [optional String, Encoding] encoding Name of the encoding or an Encoding object
     def initialize(data, memo = nil, encoding = nil)
       @data = open_data(data)
-      @data.rewind
-      @header = Header.new(@data.read DBF_HEADER_SIZE)
       @encoding = encoding || header.encoding
       @memo = open_memo(data, memo)
       yield self if block_given?
-    rescue Errno::ENOENT
-      raise DBF::FileNotFoundError, "file not found: #{data}"
-    end
-
-    # @return [TrueClass, FalseClass]
-    def has_memo_file?
-      !!@memo
     end
 
     # Closes the table and memo file
@@ -95,14 +98,18 @@ module DBF
       end
     end
 
-    # @return String
-    def filename
-      File.basename @data.path if @data.respond_to?(:path)
+    # Column names
+    #
+    # @return [String]
+    def column_names
+      @column_names ||= columns.map(&:name)
     end
 
-    # @return String
-    def name
-      @name ||= filename && File.basename(filename, ".*")
+    # All columns
+    #
+    # @return [Array]
+    def columns
+      @columns ||= build_columns
     end
 
     # Calls block once for each record in the table. The record may be nil
@@ -110,53 +117,14 @@ module DBF
     #
     # @yield [nil, DBF::Record]
     def each
-      header.record_count.times { |i| yield record(i) }
-    end
-
-    # Retrieve a record by index number.
-    # The record will be nil if it has been deleted, but not yet pruned from
-    # the database.
-    #
-    # @param [Fixnum] index
-    # @return [DBF::Record, NilClass]
-    def record(index)
-      seek_to_record(index)
-      return nil if deleted_record?
-      DBF::Record.new(@data.read(header.record_length), columns, version, @memo)
+      record_count.times { |i| yield record(i) }
     end
 
-    alias_method :row, :record
-
-    # Internal dBase version number
-    #
     # @return [String]
-    def version
-      @version ||= header.version
-    end
-
-    # Total number of records
-    #
-    # @return [Fixnum]
-    def record_count
-      @record_count ||= header.record_count
-    end
-
-    # Human readable version description
-    #
-    # @return [String]
-    def version_description
-      VERSIONS[version]
-    end
+    def filename
+      return unless @data.respond_to?(:path)
 
-    # Dumps all records to a CSV file.  If no filename is given then CSV is
-    # output to STDOUT.
-    #
-    # @param [optional String] path Defaults to STDOUT
-    def to_csv(path = nil)
-      out_io = path ? File.open(path, 'w') : $stdout
-      csv = CSV.new(out_io, force_quotes: true)
-      csv << column_names
-      each { |record| csv << record.to_a }
+      File.basename(@data.path)
     end
 
     # Find records using a simple ActiveRecord-like syntax.
@@ -181,12 +149,12 @@ module DBF
     # returned.  The equivalent SQL would be "WHERE key1 = 'value1'
     # AND key2 = 'value2'".
     #
-    # @param [Fixnum, Symbol] command
-    # @param [optional, Hash] options Hash of search parameters
+    # @param command [Integer, Symbol] command
+    # @param options [optional, Hash] options Hash of search parameters
     # @yield [optional, DBF::Record, NilClass]
     def find(command, options = {}, &block)
       case command
-      when Fixnum
+      when Integer
         record(command)
       when Array
         command.map { |i| record(i) }
@@ -197,45 +165,119 @@ module DBF
       end
     end
 
-    # All columns
+    # @return [TrueClass, FalseClass]
+    def has_memo_file?
+      !!@memo
+    end
+
+    # @return [String]
+    def name
+      @name ||= filename && File.basename(filename, '.*')
+    end
+
+    # Retrieve a record by index number.
+    # The record will be nil if it has been deleted, but not yet pruned from
+    # the database.
     #
-    # @return [Array]
-    def columns
-      @columns ||= build_columns
+    # @param [Integer] index
+    # @return [DBF::Record, NilClass]
+    def record(index)
+      raise DBF::NoColumnsDefined, 'The DBF file has no columns defined' if columns.empty?
+
+      seek_to_record(index)
+      return nil if deleted_record?
+
+      record_data = @data.read(record_length)
+      DBF::Record.new(record_data, columns, version, @memo)
     end
 
-    # Column names
+    alias row record
+
+    # Dumps all records to a CSV file.  If no filename is given then CSV is
+    # output to STDOUT.
+    #
+    # @param [optional String] path Defaults to STDOUT
+    def to_csv(path = nil)
+      out_io = path ? File.open(path, 'w') : $stdout
+      csv = CSV.new(out_io, force_quotes: true)
+      csv << column_names
+      each { |record| csv << record.to_a }
+    end
+
+    # Human readable version description
     #
     # @return [String]
-    def column_names
-      columns.map(&:name)
+    def version_description
+      VERSIONS[version]
     end
 
     private
 
-    def build_columns # nodoc
-      @data.seek(DBF_HEADER_SIZE)
-      columns = []
-      until end_of_record?
-        column_data = @data.read(DBF_HEADER_SIZE)
-        name, type, length, decimal = column_data.unpack('a10 x a x4 C2')
-        columns << Column.new(self, name, type, length, decimal)
+    def build_columns # :nodoc:
+      safe_seek do
+        @data.seek(header_size)
+        [].tap do |columns|
+          until end_of_record?
+            args = case version
+            when '02'
+              [self, *@data.read(header_size * 2).unpack('A11 a C'), 0]
+            when '04', '8c'
+              [self, *@data.read(48).unpack('A32 a C C x13')]
+            else
+              [self, *@data.read(header_size).unpack('A11 a x4 C2')]
+            end
+
+            columns << Column.new(*args)
+          end
+        end
       end
-      columns
     end
 
-    def end_of_record? # nodoc
-      original_pos = @data.pos
-      byte = @data.read(1)
-      @data.seek(original_pos)
-      byte.ord == 13
+    def header_size
+      case version
+      when '02'
+        DBASE2_HEADER_SIZE
+      when '04', '8c'
+        DBASE7_HEADER_SIZE
+      else 
+        DBASE3_HEADER_SIZE
+      end
+    end
+
+    def deleted_record? # :nodoc:
+      flag = @data.read(1)
+      flag ? flag.unpack1('a') == '*' : true
+    end
+
+    def end_of_record? # :nodoc:
+      safe_seek { @data.read(1).ord == 13 }
+    end
+
+    def find_all(options) # :nodoc:
+      select do |record|
+        next unless record && record.match?(options)
+
+        yield record if block_given?
+        record
+      end
+    end
+
+    def find_first(options) # :nodoc:
+      detect { |record| record && record.match?(options) }
+    end
+
+    def foxpro? # :nodoc:
+      FOXPRO_VERSIONS.key?(version)
     end
 
-    def foxpro? # nodoc
-      FOXPRO_VERSIONS.keys.include? version
+    def header # :nodoc:
+      @header ||= safe_seek do
+        @data.seek(0)
+        Header.new(@data.read(DBASE3_HEADER_SIZE))
+      end
     end
 
-    def memo_class # nodoc
+    def memo_class # :nodoc:
       @memo_class ||= begin
         if foxpro?
           Memo::Foxpro
@@ -245,49 +287,39 @@ module DBF
       end
     end
 
-    def open_data(data) # nodoc
+    def memo_search_path(io) # :nodoc:
+      dirname = File.dirname(io)
+      basename = File.basename(io, '.*')
+      "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
+    end
+
+    def open_data(data) # :nodoc:
       data.is_a?(StringIO) ? data : File.open(data, 'rb')
+    rescue Errno::ENOENT
+      raise DBF::FileNotFoundError, "file not found: #{data}"
     end
 
-    def open_memo(data, memo = nil) # nodoc
+    def open_memo(data, memo = nil) # :nodoc:
       if memo
         meth = memo.is_a?(StringIO) ? :new : :open
         memo_class.send(meth, memo, version)
-      elsif !data.is_a? StringIO
-        files = Dir.glob(memo_search_path data)
+      elsif !data.is_a?(StringIO)
+        files = Dir.glob(memo_search_path(data))
         files.any? ? memo_class.open(files.first, version) : nil
       end
     end
 
-    def memo_search_path(io) # nodoc
-      dirname = File.dirname(io)
-      basename = File.basename(io, '.*')
-      "#{dirname}/#{basename}*.{fpt,FPT,dbt,DBT}"
-    end
-
-    def find_all(options) # nodoc
-      map do |record|
-        if record && record.match?(options)
-          yield record if block_given?
-          record
-        end
-      end.compact
-    end
-
-    def find_first(options) # nodoc
-      detect { |record| record && record.match?(options) }
-    end
-
-    def deleted_record? # nodoc
-      @data.read(1).unpack('a') == ['*']
+    def safe_seek # :nodoc:
+      original_pos = @data.pos
+      yield.tap { @data.seek(original_pos) }
     end
 
-    def seek(offset) # nodoc
-      @data.seek header.header_length + offset
+    def seek(offset) # :nodoc:
+      @data.seek(header_length + offset)
     end
 
-    def seek_to_record(index) # nodoc
-      seek(index * header.record_length)
+    def seek_to_record(index) # :nodoc:
+      seek(index * record_length)
     end
   end
 end
diff --git a/lib/dbf/version.rb b/lib/dbf/version.rb
index 204d08c..b1b072b 100644
--- a/lib/dbf/version.rb
+++ b/lib/dbf/version.rb
@@ -1,3 +1,3 @@
 module DBF
-  VERSION = '3.0.5'
+  VERSION = '4.2.2'.freeze
 end
diff --git a/nBOOKS.DBF b/nBOOKS.DBF
new file mode 100644
index 0000000..4d3aea8
Binary files /dev/null and b/nBOOKS.DBF differ
diff --git a/spec/dbf/column_spec.rb b/spec/dbf/column_spec.rb
index ee88e60..12f5eb8 100644
--- a/spec/dbf/column_spec.rb
+++ b/spec/dbf/column_spec.rb
@@ -3,7 +3,7 @@
 require 'spec_helper'
 
 RSpec.describe DBF::Column do
-  let(:table) { DBF::Table.new fixture('dbase_30.dbf')}
+  let(:table) { DBF::Table.new fixture('dbase_30.dbf') }
 
   context 'when initialized' do
     let(:column) { DBF::Column.new table, 'ColumnName', 'N', 1, 0 }
@@ -42,7 +42,7 @@ RSpec.describe DBF::Column do
     end
   end
 
-  context '#type_cast' do
+  describe '#type_cast' do
     context 'with type N (number)' do
       context 'when value is empty' do
         it 'returns nil' do
@@ -52,28 +52,28 @@ RSpec.describe DBF::Column do
         end
       end
 
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'N', 0, 0
           expect(column.type_cast('')).to be_nil
         end
       end
 
-      context 'and 0 decimals' do
-        it 'casts value to Fixnum' do
+      context 'with 0 decimals' do
+        it 'casts value to Integer' do
           value = '135'
           column = DBF::Column.new table, 'ColumnName', 'N', 3, 0
           expect(column.type_cast(value)).to eq 135
         end
 
-        it 'supports negative Fixnum' do
+        it 'supports negative Integer' do
           value = '-135'
           column = DBF::Column.new table, 'ColumnName', 'N', 3, 0
-          expect(column.type_cast(value)).to eq (-135)
+          expect(column.type_cast(value)).to eq(-135)
         end
       end
 
-      context 'and more than 0 decimals' do
+      context 'with more than 0 decimals' do
         it 'casts value to Float' do
           value = '13.5'
           column = DBF::Column.new table, 'ColumnName', 'N', 2, 1
@@ -83,13 +83,13 @@ RSpec.describe DBF::Column do
         it 'casts negative value to Float' do
           value = '-13.5'
           column = DBF::Column.new table, 'ColumnName', 'N', 2, 1
-          expect(column.type_cast(value)).to eq (-13.5)
+          expect(column.type_cast(value)).to eq(-13.5)
         end
       end
     end
 
     context 'with type F (float)' do
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'F', 0, 0
           expect(column.type_cast('')).to be_nil
@@ -105,7 +105,7 @@ RSpec.describe DBF::Column do
       it 'casts negative value to Float' do
         value = '-135'
         column = DBF::Column.new table, 'ColumnName', 'F', 3, 0
-        expect(column.type_cast(value)).to eq (-135.0)
+        expect(column.type_cast(value)).to eq(-135.0)
       end
     end
 
@@ -126,29 +126,29 @@ RSpec.describe DBF::Column do
         it 'supports negative binary' do
           column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
           expect(column.type_cast("\x00\x00\x00\x00\x00\xC0\x65\xC0")).to be_a(Float)
-          expect(column.type_cast("\x00\x00\x00\x00\x00\xC0\x65\xC0")).to eq (-174.0)
+          expect(column.type_cast("\x00\x00\x00\x00\x00\xC0\x65\xC0")).to eq(-174.0)
         end
       end
     end
 
     context 'with type I (integer)' do
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'I', 0, 0
           expect(column.type_cast('')).to be_nil
         end
       end
 
-      it 'casts value to Fixnum' do
+      it 'casts value to Integer' do
         value = "\203\171\001\000"
         column = DBF::Column.new table, 'ColumnName', 'I', 3, 0
-        expect(column.type_cast(value)).to eq 96643
+        expect(column.type_cast(value)).to eq 96_643
       end
 
-      it 'supports negative Fixnum' do
+      it 'supports negative Integer' do
         value = "\x24\xE1\xFF\xFF"
         column = DBF::Column.new table, 'ColumnName', 'I', 3, 0
-        expect(column.type_cast(value)).to eq (-7900)
+        expect(column.type_cast(value)).to eq(-7900)
       end
     end
 
@@ -167,7 +167,7 @@ RSpec.describe DBF::Column do
         expect(column.type_cast('n')).to be false
       end
 
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'L', 0, 0
           expect(column.type_cast('')).to be_nil
@@ -180,7 +180,7 @@ RSpec.describe DBF::Column do
 
       context 'with valid datetime' do
         it 'casts to DateTime' do
-          expect(column.type_cast("Nl%\000\300Z\252\003")).to eq DateTime.parse('2002-10-10T17:04:56+00:00')
+          expect(column.type_cast("Nl%\000\300Z\252\003")).to eq Time.parse('2002-10-10T17:04:56+00:00')
         end
       end
 
@@ -190,7 +190,7 @@ RSpec.describe DBF::Column do
         end
       end
 
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'T', 0, 0
           expect(column.type_cast('')).to be_nil
@@ -203,17 +203,17 @@ RSpec.describe DBF::Column do
 
       context 'with valid date' do
         it 'casts to Date' do
-          expect(column.type_cast('20050712')).to eq Date.new(2005,7,12)
+          expect(column.type_cast('20050712')).to eq Date.new(2005, 7, 12)
         end
       end
 
       context 'with invalid date' do
         it 'casts to nil' do
-          expect(column.type_cast('0')).to be_nil
+          expect(column.type_cast('000000000')).to be_nil
         end
       end
 
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'D', 0, 0
           expect(column.type_cast('')).to be_nil
@@ -232,7 +232,7 @@ RSpec.describe DBF::Column do
         expect(column.type_cast(nil)).to be_nil
       end
 
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'M', 0, 0
           expect(column.type_cast('')).to be_nil
@@ -252,7 +252,7 @@ RSpec.describe DBF::Column do
         expect(column.type_cast(nil)).to be_nil
       end
 
-      context 'and 0 length' do
+      context 'with 0 length' do
         it 'returns nil' do
           column = DBF::Column.new table, 'ColumnName', 'G', 0, 0
           expect(column.type_cast('')).to be_nil
@@ -269,14 +269,14 @@ RSpec.describe DBF::Column do
     end
 
     it 'supports negative currency' do
-      expect(column.type_cast("\xFC\xF0\xF0\xFE\xFF\xFF\xFF\xFF")).to eq (-1776.41)
+      expect(column.type_cast("\xFC\xF0\xF0\xFE\xFF\xFF\xFF\xFF")).to eq(-1776.41)
     end
 
     it 'supports 64bit negative currency' do
-      expect(column.type_cast("pN'9\xFF\xFF\xFF\xFF")).to eq (-333609.0)
+      expect(column.type_cast("pN'9\xFF\xFF\xFF\xFF")).to eq(-333_609.0)
     end
 
-    context 'and 0 length' do
+    context 'with 0 length' do
       it 'returns nil' do
         column = DBF::Column.new table, 'ColumnName', 'Y', 0, 0
         expect(column.type_cast('')).to be_nil
@@ -284,63 +284,15 @@ RSpec.describe DBF::Column do
     end
   end
 
-  context '#schema_definition' do
-    context 'with type N (number)' do
-      it 'outputs an integer column' do
-        column = DBF::Column.new table, 'ColumnName', 'N', 1, 0
-        expect(column.schema_definition).to eq "\"column_name\", :integer\n"
-      end
-    end
-
-    context 'with type B (binary)' do
-      context 'with Foxpro dbf' do
-        it 'outputs a float column' do
-          column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
-          expect(column.schema_definition).to eq "\"column_name\", :float\n"
-        end
-      end
-    end
-
-    it 'defines a float colmn if type is (N)umber with more than 0 decimals' do
-      column = DBF::Column.new table, 'ColumnName', 'N', 1, 2
-      expect(column.schema_definition).to eq "\"column_name\", :float\n"
-    end
-
-    it 'defines a date column if type is (D)ate' do
-      column = DBF::Column.new table, 'ColumnName', 'D', 8, 0
-      expect(column.schema_definition).to eq "\"column_name\", :date\n"
-    end
-
-    it 'defines a datetime column if type is (D)ate' do
-      column = DBF::Column.new table, 'ColumnName', 'T', 16, 0
-      expect(column.schema_definition).to eq "\"column_name\", :datetime\n"
-    end
-
-    it 'defines a boolean column if type is (L)ogical' do
-      column = DBF::Column.new table, 'ColumnName', 'L', 1, 0
-      expect(column.schema_definition).to eq "\"column_name\", :boolean\n"
-    end
-
-    it 'defines a text column if type is (M)emo' do
-      column = DBF::Column.new table, 'ColumnName', 'M', 1, 0
-      expect(column.schema_definition).to eq "\"column_name\", :text\n"
-    end
-
-    it 'defines a string column with length for any other data types' do
-      column = DBF::Column.new table, 'ColumnName', 'X', 20, 0
-      expect(column.schema_definition).to eq "\"column_name\", :string, :limit => 20\n"
-    end
-  end
-
-  context '#name' do
+  describe '#name' do
     it 'contains only ASCII characters' do
-      column = DBF::Column.new table, "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--", 'N', 1, 0
+      column = DBF::Column.new table, "--\x1F-\x68\x65\x6C\x6C\x6F \x00world-\x80--", 'N', 1, 0
       expect(column.name).to eq '---hello world---'
     end
 
     it 'is truncated at the null character' do
-      column = DBF::Column.new table, "--\x1F-\x68\x65\x6C\x6C\x6F \x00 world-\x80--", 'N', 1, 0
-      expect(column.name).to eq '---hello '
+      column = DBF::Column.new table, "--\x1F-\x68\x65\x6C\x6C\x6F \x00world-\x80--", 'N', 1, 0
+      expect(column.name).to eq '---hello world---'
     end
   end
 end
diff --git a/spec/dbf/database_spec.rb b/spec/dbf/database/foxpro_spec.rb
similarity index 91%
rename from spec/dbf/database_spec.rb
rename to spec/dbf/database/foxpro_spec.rb
index 7bf1bb4..2cd805b 100644
--- a/spec/dbf/database_spec.rb
+++ b/spec/dbf/database/foxpro_spec.rb
@@ -19,13 +19,13 @@ RSpec.describe DBF::Database::Foxpro do
 
     describe 'it loads the example db correctly' do
       it 'shows a correct list of tables' do
-        expect(db.table_names.sort).to eq(%w(contacts calls setup types).sort)
+        expect(db.table_names.sort).to eq(%w[contacts calls setup types].sort)
       end
     end
   end
 
   describe '#table' do
-    describe "when accessing related tables" do
+    describe 'when accessing related tables' do
       let(:db) { DBF::Database::Foxpro.new(dbf_path) }
 
       it 'loads an existing related table' do
diff --git a/spec/dbf/file_formats_spec.rb b/spec/dbf/file_formats_spec.rb
index c519b30..f14fb62 100644
--- a/spec/dbf/file_formats_spec.rb
+++ b/spec/dbf/file_formats_spec.rb
@@ -1,167 +1,219 @@
-require "spec_helper"
+require 'spec_helper'
 
 RSpec.shared_examples_for 'DBF' do
-  specify "sum of column lengths should equal record length specified in header plus one" do
-    header_record_length = table.instance_eval {@header.record_length}
-    sum_of_column_lengths = table.columns.inject(1) {|sum, column| sum += column.length}
+  let(:header_record_length) { table.instance_eval { header.record_length } }
+  let(:sum_of_column_lengths) { table.columns.inject(1) { |sum, column| sum + column.length } }
 
+  specify 'sum of column lengths should equal record length specified in header plus one' do
     expect(header_record_length).to eq sum_of_column_lengths
   end
 
-  specify "records should be instances of DBF::Record" do
-    table.each do |record|
-      expect(record).to be_kind_of(DBF::Record)
-    end
+  specify 'records should be instances of DBF::Record' do
+    expect(table).to all be_kind_of(DBF::Record)
   end
 
-  specify "record count should be the same as reported in the header" do
+  specify 'record count should be the same as reported in the header' do
     expect(table.entries.size).to eq table.record_count
   end
 
-  specify "column names should not be blank" do
+  specify 'column names should not be blank' do
     table.columns.each do |column|
-      expect(column.name).not_to be_empty
+      expect(column.name).to_not be_empty
     end
   end
 
-  specify "column types should be valid" do
-    valid_column_types = %w(C N L D M F B G P Y T I V X @ O + 0)
+  specify 'column types should be valid' do
+    valid_column_types = %w[C N L D M F B G P Y T I V X @ O + 0]
     table.columns.each do |column|
       expect(valid_column_types).to include(column.type)
     end
   end
 
-  specify "column lengths should be instances of Fixnum" do
+  specify 'column lengths should be instances of Integer' do
     table.columns.each do |column|
-      expect(column.length).to be_kind_of(Fixnum)
+      expect(column.length).to be_kind_of(Integer)
     end
   end
 
-  specify "column lengths should be larger than 0" do
+  specify 'column lengths should be larger than 0' do
     table.columns.each do |column|
       expect(column.length).to be > 0
     end
   end
 
-  specify "column decimals should be instances of Fixnum" do
+  specify 'column decimals should be instances of Integer' do
     table.columns.each do |column|
-      expect(column.decimal).to be_kind_of(Fixnum)
+      expect(column.decimal).to be_kind_of(Integer)
     end
   end
 end
 
-RSpec.describe DBF, "of type 03 (dBase III without memo file)" do
+RSpec.describe DBF, 'of type 02 (FoxBase)' do
+  let(:table) { DBF::Table.new fixture('dbase_02.dbf') }
+
+  it_behaves_like 'DBF'
+
+  it 'reports the correct version number' do
+    expect(table.version).to eq '02'
+  end
+
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'FoxBase'
+  end
+
+  it 'determines the number of records' do
+    expect(table.record_count).to eq 9
+  end
+end
+
+RSpec.describe DBF, 'of type 03 (dBase III without memo file)' do
   let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
 
-  it_should_behave_like "DBF"
+  it_behaves_like 'DBF'
 
-  it "should report the correct version number" do
-    expect(table.version).to eq "03"
+  it 'reports the correct version number' do
+    expect(table.version).to eq '03'
   end
 
-  it "should report the correct version description" do
-    expect(table.version_description).to eq "dBase III without memo file"
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'dBase III without memo file'
   end
 
-  it "should determine the number of records" do
+  it 'determines the number of records' do
     expect(table.record_count).to eq 14
   end
 end
 
-RSpec.describe DBF, "of type 30 (Visual FoxPro)" do
+RSpec.describe DBF, 'of type 30 (Visual FoxPro)' do
   let(:table) { DBF::Table.new fixture('dbase_30.dbf') }
 
-  it_should_behave_like "DBF"
+  it_behaves_like 'DBF'
 
-  it "should report the correct version number" do
-    expect(table.version).to eq "30"
+  it 'reports the correct version number' do
+    expect(table.version).to eq '30'
   end
 
-  it "should report the correct version description" do
-    expect(table.version_description).to eq "Visual FoxPro"
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'Visual FoxPro'
   end
 
-  it "should determine the number of records" do
+  it 'determines the number of records' do
     expect(table.record_count).to eq 34
   end
 
-  it "reads memo data" do
+  it 'reads memo data' do
     expect(table.record(3).classes).to match(/\AAgriculture.*Farming\r\n\Z/m)
   end
 end
 
-RSpec.describe DBF, "of type 31 (Visual FoxPro with AutoIncrement field)" do
+RSpec.describe DBF, 'of type 31 (Visual FoxPro with AutoIncrement field)' do
   let(:table) { DBF::Table.new fixture('dbase_31.dbf') }
 
-  it_should_behave_like "DBF"
+  it_behaves_like 'DBF'
 
-  it "should have a dBase version of 31" do
-    expect(table.version).to eq "31"
+  it 'has a dBase version of 31' do
+    expect(table.version).to eq '31'
   end
 
-  it "should report the correct version description" do
-    expect(table.version_description).to eq "Visual FoxPro with AutoIncrement field"
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'Visual FoxPro with AutoIncrement field'
   end
 
-  it "should determine the number of records" do
+  it 'determines the number of records' do
     expect(table.record_count).to eq 77
   end
 end
 
-RSpec.describe DBF, "of type 83 (dBase III with memo file)" do
+RSpec.describe DBF, 'of type 32 (Visual FoxPro with field type Varchar or Varbinary)' do
+  let(:table) { DBF::Table.new fixture('dbase_32.dbf') }
+
+  it_behaves_like 'DBF'
+
+  it 'has a dBase version of 32' do
+    expect(table.version).to eq '32'
+  end
+
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'Visual FoxPro with field type Varchar or Varbinary'
+  end
+
+  it 'determines the number of records' do
+    expect(table.record_count).to eq 1
+  end
+end
+
+RSpec.describe DBF, 'of type 83 (dBase III with memo file)' do
   let(:table) { DBF::Table.new fixture('dbase_83.dbf') }
 
-  it_should_behave_like "DBF"
+  it_behaves_like 'DBF'
 
-  it "should report the correct version number" do
-    expect(table.version).to eq "83"
+  it 'reports the correct version number' do
+    expect(table.version).to eq '83'
   end
 
-  it "should report the correct version description" do
-    expect(table.version_description).to eq "dBase III with memo file"
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'dBase III with memo file'
   end
 
-  it "should determine the number of records" do
+  it 'determines the number of records' do
     expect(table.record_count).to eq 67
   end
 end
 
-RSpec.describe DBF, "of type 8b (dBase IV with memo file)" do
+RSpec.describe DBF, 'of type 8b (dBase IV with memo file)' do
   let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
 
-  it_should_behave_like "DBF"
+  it_behaves_like 'DBF'
+
+  it 'reports the correct version number' do
+    expect(table.version).to eq '8b'
+  end
+
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'dBase IV with memo file'
+  end
+
+  it 'determines the number of records' do
+    expect(table.record_count).to eq 10
+  end
+end
+
+RSpec.describe DBF, 'of type 8c (unknown)' do
+  let(:table) { DBF::Table.new fixture('dbase_8c.dbf') }
+
+  it_behaves_like 'DBF'
 
-  it "should report the correct version number" do
-    expect(table.version).to eq "8b"
+  it 'reports the correct version number' do
+    expect(table.version).to eq '8c'
   end
 
-  it "should report the correct version description" do
-    expect(table.version_description).to eq "dBase IV with memo file"
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'dBase 7'
   end
 
-  it "should determine the number of records" do
+  it 'determines the number of records' do
     expect(table.record_count).to eq 10
   end
 end
 
-RSpec.describe DBF, "of type f5 (FoxPro with memo file)" do
+RSpec.describe DBF, 'of type f5 (FoxPro with memo file)' do
   let(:table) { DBF::Table.new fixture('dbase_f5.dbf') }
 
-  it_should_behave_like "DBF"
+  it_behaves_like 'DBF'
 
-  it "should report the correct version number" do
-    expect(table.version).to eq "f5"
+  it 'reports the correct version number' do
+    expect(table.version).to eq 'f5'
   end
 
-  it "should report the correct version description" do
-    expect(table.version_description).to eq "FoxPro with memo file"
+  it 'reports the correct version description' do
+    expect(table.version_description).to eq 'FoxPro with memo file'
   end
 
-  it "should determine the number of records" do
+  it 'determines the number of records' do
     expect(table.record_count).to eq 975
   end
 
-  it "reads memo data" do
+  it 'reads memo data' do
     expect(table.record(3).datn.to_s).to eq '1870-06-30'
   end
 end
diff --git a/spec/dbf/record_spec.rb b/spec/dbf/record_spec.rb
index 9830912..382c198 100644
--- a/spec/dbf/record_spec.rb
+++ b/spec/dbf/record_spec.rb
@@ -1,4 +1,4 @@
-require "spec_helper"
+require 'spec_helper'
 
 RSpec.describe DBF::Record do
   describe '#to_a' do
@@ -6,7 +6,7 @@ RSpec.describe DBF::Record do
     let(:record_0) { YAML.load_file(fixture('dbase_83_record_0.yml')) }
     let(:record_9) { YAML.load_file(fixture('dbase_83_record_9.yml')) }
 
-    it 'should return an ordered array of attribute values' do
+    it 'returns an ordered array of attribute values' do
       record = table.record(0)
       expect(record.to_a).to eq record_0
 
@@ -44,13 +44,13 @@ RSpec.describe DBF::Record do
 
     describe 'when other does not have attributes' do
       it 'returns false' do
-        expect((record == double('other'))).to be_falsey
+        expect((record == instance_double('DBF::Record'))).to be_falsey
       end
     end
 
     describe 'if other attributes match' do
       let(:attributes) { {x: 1, y: 2} }
-      let(:other) { double('object', attributes: attributes) }
+      let(:other) { instance_double('DBF::Record', attributes: attributes) }
 
       before do
         allow(record).to receive(:attributes).and_return(attributes)
@@ -67,8 +67,7 @@ RSpec.describe DBF::Record do
     let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
     let(:record) { table.find(0) }
 
-    %w(character numerical date logical float memo).each do |column_name|
-
+    %w[character numerical date logical float memo].each do |column_name|
       it "defines accessor method for '#{column_name}' column" do
         expect(record).to respond_to(column_name.to_sym)
       end
@@ -81,23 +80,23 @@ RSpec.describe DBF::Record do
       let(:table) { DBF::Table.new fixture('cp1251.dbf') }
       let(:record) { table.find(0) }
 
-      it 'should automatically encodes to default system encoding' do
+      it 'encodes to default system encoding' do
         expect(record.name.encoding).to eq Encoding.default_external
 
         # russian a
-        expect(record.name.encode("UTF-8").unpack("H4")).to eq ["d0b0"]
+        expect(record.name.encode('UTF-8').unpack1('H4')).to eq 'd0b0'
       end
     end
 
     describe 'overriding specified in dbf encoding' do
-      let(:table) { DBF::Table.new fixture('cp1251.dbf'), nil, 'cp866'}
+      let(:table) { DBF::Table.new fixture('cp1251.dbf'), nil, 'cp866' }
       let(:record) { table.find(0) }
 
-      it 'should transcode from manually specified encoding to default system encoding' do
+      it 'transcodes from manually specified encoding to default system encoding' do
         expect(record.name.encoding).to eq Encoding.default_external
 
         # russian а encoded in cp1251 and read as if it was encoded in cp866
-        expect(record.name.encode("UTF-8").unpack("H4")).to eq ["d180"]
+        expect(record.name.encode('UTF-8').unpack1('H4')).to eq 'd180'
       end
     end
   end
@@ -112,7 +111,7 @@ RSpec.describe DBF::Record do
     end
 
     it 'has only original field names as keys' do
-      original_field_names = %w(CHARACTER DATE FLOAT LOGICAL MEMO NUMERICAL)
+      original_field_names = %w[CHARACTER DATE FLOAT LOGICAL MEMO NUMERICAL]
       expect(record.attributes.keys.sort).to eq original_field_names
     end
   end
diff --git a/spec/dbf/table_spec.rb b/spec/dbf/table_spec.rb
index 477d207..7d93c19 100644
--- a/spec/dbf/table_spec.rb
+++ b/spec/dbf/table_spec.rb
@@ -6,7 +6,11 @@ RSpec.describe DBF::Table do
   let(:table) { DBF::Table.new dbf_path }
 
   specify 'foxpro versions' do
-    expect(DBF::Table::FOXPRO_VERSIONS.keys.sort).to eq %w(30 31 f5 fb).sort
+    expect(DBF::Table::FOXPRO_VERSIONS.keys.sort).to eq %w[30 31 f5 fb].sort
+  end
+
+  specify 'row is an alias of record' do
+    expect(table.record(1)).to eq table.row(1)
   end
 
   describe '#initialize' do
@@ -40,7 +44,7 @@ RSpec.describe DBF::Table do
     end
   end
 
-  context '#close' do
+  describe '#close' do
     before { table.close }
 
     it 'closes the io' do
@@ -50,11 +54,18 @@ RSpec.describe DBF::Table do
 
   describe '#schema' do
     describe 'when data is IO' do
-      let(:control_schema) { File.read(fixture('dbase_83_schema.txt')) }
+      let(:control_schema) { File.read(fixture('dbase_83_schema_ar.txt')) }
 
       it 'matches the test schema fixture' do
         expect(table.schema).to eq control_schema
       end
+
+      it 'raises ArgumentError if there is no matching schema' do
+        expect { table.schema(:invalid) }.to raise_error(
+          ArgumentError,
+          ':invalid is not a valid schema. Valid schemas are: activerecord, json, sequel.'
+        )
+      end
     end
 
     describe 'when data is StringIO' do
@@ -62,7 +73,7 @@ RSpec.describe DBF::Table do
       let(:memo) { StringIO.new File.read(memo_path) }
       let(:table) { DBF::Table.new data }
 
-      let(:control_schema) { File.read(fixture('dbase_83_schema.txt')) }
+      let(:control_schema) { File.read(fixture('dbase_83_schema_ar.txt')) }
 
       it 'matches the test schema fixture' do
         table.name = 'dbase_83'
@@ -72,52 +83,14 @@ RSpec.describe DBF::Table do
   end
 
   describe '#sequel_schema' do
-    it 'should return a valid Sequel migration by default' do
-      expect(table.sequel_schema).to eq <<-SCHEMA
-Sequel.migration do
-  change do
-     create_table(:dbase_83) do
-      column :id, :integer
-      column :catcount, :integer
-      column :agrpcount, :integer
-      column :pgrpcount, :integer
-      column :order, :integer
-      column :code, :varchar, :size => 50
-      column :name, :varchar, :size => 100
-      column :thumbnail, :varchar, :size => 254
-      column :image, :varchar, :size => 254
-      column :price, :float
-      column :cost, :float
-      column :desc, :text
-      column :weight, :float
-      column :taxable, :boolean
-      column :active, :boolean
+    it 'returns a valid Sequel migration by default' do
+      control_schema = File.read(fixture('dbase_83_schema_sq.txt'))
+      expect(table.sequel_schema).to eq control_schema
     end
-  end
-end
-SCHEMA
-    end
-
-    it 'should return a limited Sequel migration when passed true' do
-      expect(table.sequel_schema(true)).to eq <<-SCHEMA
-    create_table(:dbase_83) do
-      column :id, :integer
-      column :catcount, :integer
-      column :agrpcount, :integer
-      column :pgrpcount, :integer
-      column :order, :integer
-      column :code, :varchar, :size => 50
-      column :name, :varchar, :size => 100
-      column :thumbnail, :varchar, :size => 254
-      column :image, :varchar, :size => 254
-      column :price, :float
-      column :cost, :float
-      column :desc, :text
-      column :weight, :float
-      column :taxable, :boolean
-      column :active, :boolean
-    end
-SCHEMA
+
+    it 'returns a limited Sequel migration when passed true' do
+      control_schema = File.read(fixture('dbase_83_schema_sq_lim.txt'))
+      expect(table.sequel_schema).to eq control_schema
     end
 
   end
@@ -156,13 +129,7 @@ SCHEMA
 
     describe 'when no path param passed' do
       it 'writes to STDOUT' do
-        begin
-          $stdout = StringIO.new
-          table.to_csv
-          expect($stdout.string).not_to be_empty
-        ensure
-          $stdout = STDOUT
-        end
+        expect { table.to_csv }.to output.to_stdout
       end
     end
 
@@ -170,7 +137,7 @@ SCHEMA
       before { table.to_csv('test.csv') }
 
       it 'creates a custom csv file' do
-        expect(File.exist?('test.csv')).to be_truthy
+        expect(File).to be_exist('test.csv')
       end
     end
   end
@@ -180,10 +147,18 @@ SCHEMA
       allow(table).to receive(:deleted_record?).and_return(true)
       expect(table.record(5)).to be_nil
     end
+
+    describe 'when dbf has no column definitions' do
+      let(:dbf_path) { fixture('polygon.dbf') }
+
+      it 'raises a DBF::NoColumnsDefined error' do
+        expect { DBF::Table.new(dbf_path).record(1) }.to raise_error(DBF::NoColumnsDefined, 'The DBF file has no columns defined')
+      end
+    end
   end
 
   describe '#current_record' do
-    it 'should return nil for deleted records' do
+    it 'returns nil for deleted records' do
       allow(table).to receive(:deleted_record?).and_return(true)
       expect(table.record(0)).to be_nil
     end
@@ -204,13 +179,20 @@ SCHEMA
 
     describe 'with :all' do
       let(:records) do
-        table.find(:all, weight: 0.0).inject([]) do |records, record|
-          records << record
-        end
+        table.find(:all, weight: 0.0)
+      end
+
+      it 'retrieves only matching records' do
+        expect(records.size).to eq 66
       end
 
-      it 'accepts a block' do
-        expect(records).to eq table.find(:all, weight: 0.0)
+      it 'yields to a block if given' do
+        record_count = 0
+        table.find(:all, weight: 0.0) do |record|
+          record_count += 1
+          expect(record).to be_a DBF::Record
+        end
+        expect(record_count).to eq 66
       end
 
       it 'returns all records if options are empty' do
@@ -218,27 +200,27 @@ SCHEMA
       end
 
       it 'returns matching records when used with options' do
-        expect(table.find(:all, 'WEIGHT' => 0.0)).to eq table.select {|r| r['weight'] == 0.0}
+        expect(table.find(:all, 'WEIGHT' => 0.0)).to eq table.select { |r| r['weight'] == 0.0 }
       end
 
-      it 'should AND multiple search terms' do
+      it 'ANDS multiple search terms' do
         expect(table.find(:all, 'ID' => 30, :IMAGE => 'graphics/00000001/TBC01.jpg')).to be_empty
       end
 
-      it 'should match original column names' do
-        expect(table.find(:all, 'WEIGHT' => 0.0)).not_to be_empty
+      it 'matches original column names' do
+        expect(table.find(:all, 'WEIGHT' => 0.0)).to_not be_empty
       end
 
       it 'matches symbolized column names' do
-        expect(table.find(:all, :WEIGHT => 0.0)).not_to be_empty
+        expect(table.find(:all, WEIGHT: 0.0)).to_not be_empty
       end
 
       it 'matches downcased column names' do
-        expect(table.find(:all, 'weight' => 0.0)).not_to be_empty
+        expect(table.find(:all, 'weight' => 0.0)).to_not be_empty
       end
 
       it 'matches symbolized downcased column names' do
-        expect(table.find(:all, :weight => 0.0)).not_to be_empty
+        expect(table.find(:all, weight: 0.0)).to_not be_empty
       end
     end
 
@@ -296,13 +278,13 @@ SCHEMA
       let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
 
       it 'is false' do
-        expect(table.has_memo_file?).to be_falsey
+        expect(table).to_not have_memo_file
       end
     end
 
     describe 'with a memo file' do
       it 'is true' do
-        expect(table.has_memo_file?).to be_truthy
+        expect(table).to have_memo_file
       end
     end
   end
@@ -313,17 +295,76 @@ SCHEMA
     it 'is an array of Columns' do
       expect(columns).to be_an(Array)
       expect(columns).to_not be_empty
-      expect(columns.all? {|c| c.class == DBF::Column}).to be_truthy
+      expect(columns).to be_all { |c| c.is_a? DBF::Column }
     end
   end
 
   describe '#column_names' do
     let(:column_names) do
-      %w(ID CATCOUNT AGRPCOUNT PGRPCOUNT ORDER CODE NAME THUMBNAIL IMAGE PRICE COST DESC WEIGHT TAXABLE ACTIVE)
+      %w[ID CATCOUNT AGRPCOUNT PGRPCOUNT ORDER CODE NAME THUMBNAIL IMAGE PRICE COST DESC WEIGHT TAXABLE ACTIVE]
+    end
+
+    describe 'when data is an IO' do
+      it 'is an array of all column names' do
+        expect(table.column_names).to eq column_names
+      end
+    end
+
+    describe 'when data is a StringIO' do
+      let(:data) { StringIO.new File.read(dbf_path) }
+      let(:table) { DBF::Table.new data, nil, Encoding::US_ASCII }
+
+      it 'is an array of all column names' do
+        expect(table.column_names).to eq column_names
+      end
+    end
+  end
+
+  describe '#activerecord_schema_definition' do
+    context 'with type N (number)' do
+      it 'outputs an integer column' do
+        column = DBF::Column.new table, 'ColumnName', 'N', 1, 0
+        expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :integer\n"
+      end
+    end
+
+    describe 'with type B (binary)' do
+      context 'with Foxpro dbf' do
+        it 'outputs a float column' do
+          column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
+          expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :binary\n"
+        end
+      end
+    end
+
+    it 'defines a float colmn if type is (N)umber with more than 0 decimals' do
+      column = DBF::Column.new table, 'ColumnName', 'N', 1, 2
+      expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :float\n"
+    end
+
+    it 'defines a date column if type is (D)ate' do
+      column = DBF::Column.new table, 'ColumnName', 'D', 8, 0
+      expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :date\n"
+    end
+
+    it 'defines a datetime column if type is (D)ate' do
+      column = DBF::Column.new table, 'ColumnName', 'T', 16, 0
+      expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :datetime\n"
+    end
+
+    it 'defines a boolean column if type is (L)ogical' do
+      column = DBF::Column.new table, 'ColumnName', 'L', 1, 0
+      expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :boolean\n"
+    end
+
+    it 'defines a text column if type is (M)emo' do
+      column = DBF::Column.new table, 'ColumnName', 'M', 1, 0
+      expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :text\n"
     end
 
-    it 'is an array of all column names' do
-      expect(table.column_names).to eq column_names
+    it 'defines a string column with length for any other data types' do
+      column = DBF::Column.new table, 'ColumnName', 'X', 20, 0
+      expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :string, :limit => 20\n"
     end
   end
 end
diff --git a/spec/fixtures/dbase_02.dbf b/spec/fixtures/dbase_02.dbf
new file mode 100644
index 0000000..1b59152
Binary files /dev/null and b/spec/fixtures/dbase_02.dbf differ
diff --git a/spec/fixtures/dbase_02_summary.txt b/spec/fixtures/dbase_02_summary.txt
new file mode 100644
index 0000000..6857c06
--- /dev/null
+++ b/spec/fixtures/dbase_02_summary.txt
@@ -0,0 +1,23 @@
+
+Database: dbase_02.dbf
+Type: (02) FoxBase
+Memo File: false
+Records: 9
+
+Fields:
+Name             Type       Length     Decimal
+------------------------------------------------------------------------------
+EMP:NMBR         N          3          0         
+LAST             C          10         0         
+FIRST            C          10         0         
+ADDR             C          20         0         
+CITY             C          15         0         
+ZIP:CODE         C          10         0         
+PHONE            C          9          0         
+SSN              C          11         0         
+HIREDATE         C          8          0         
+TERMDATE         C          8          0         
+CLASS            C          3          0         
+DEPT             C          3          0         
+PAYRATE          N          8          0         
+START:PAY        N          8          0         
diff --git a/spec/fixtures/dbase_32.dbf b/spec/fixtures/dbase_32.dbf
new file mode 100644
index 0000000..ec5affb
Binary files /dev/null and b/spec/fixtures/dbase_32.dbf differ
diff --git a/spec/fixtures/dbase_32_summary.txt b/spec/fixtures/dbase_32_summary.txt
new file mode 100644
index 0000000..bb60c85
--- /dev/null
+++ b/spec/fixtures/dbase_32_summary.txt
@@ -0,0 +1,11 @@
+
+Database: dbase_32.dbf
+Type: (32) Visual FoxPro with field type Varchar or Varbinary
+Memo File: false
+Records: 1
+
+Fields:
+Name             Type       Length     Decimal
+------------------------------------------------------------------------------
+NAME             V          250        0         
+_NullFlags       0          1          0         
diff --git a/spec/fixtures/dbase_83_schema.txt b/spec/fixtures/dbase_83_schema_ar.txt
similarity index 100%
rename from spec/fixtures/dbase_83_schema.txt
rename to spec/fixtures/dbase_83_schema_ar.txt
diff --git a/spec/fixtures/dbase_83_schema_sq.txt b/spec/fixtures/dbase_83_schema_sq.txt
new file mode 100644
index 0000000..dda8429
--- /dev/null
+++ b/spec/fixtures/dbase_83_schema_sq.txt
@@ -0,0 +1,21 @@
+Sequel.migration do
+  change do
+     create_table(:dbase_83) do
+      column :id, :integer
+      column :catcount, :integer
+      column :agrpcount, :integer
+      column :pgrpcount, :integer
+      column :order, :integer
+      column :code, :varchar, :size => 50
+      column :name, :varchar, :size => 100
+      column :thumbnail, :varchar, :size => 254
+      column :image, :varchar, :size => 254
+      column :price, :float
+      column :cost, :float
+      column :desc, :text
+      column :weight, :float
+      column :taxable, :boolean
+      column :active, :boolean
+    end
+  end
+end
diff --git a/spec/fixtures/dbase_83_schema_sq_lim.txt b/spec/fixtures/dbase_83_schema_sq_lim.txt
new file mode 100644
index 0000000..dda8429
--- /dev/null
+++ b/spec/fixtures/dbase_83_schema_sq_lim.txt
@@ -0,0 +1,21 @@
+Sequel.migration do
+  change do
+     create_table(:dbase_83) do
+      column :id, :integer
+      column :catcount, :integer
+      column :agrpcount, :integer
+      column :pgrpcount, :integer
+      column :order, :integer
+      column :code, :varchar, :size => 50
+      column :name, :varchar, :size => 100
+      column :thumbnail, :varchar, :size => 254
+      column :image, :varchar, :size => 254
+      column :price, :float
+      column :cost, :float
+      column :desc, :text
+      column :weight, :float
+      column :taxable, :boolean
+      column :active, :boolean
+    end
+  end
+end
diff --git a/spec/fixtures/dbase_8c.dbf b/spec/fixtures/dbase_8c.dbf
new file mode 100755
index 0000000..03c4b47
Binary files /dev/null and b/spec/fixtures/dbase_8c.dbf differ
diff --git a/spec/fixtures/polygon.dbf b/spec/fixtures/polygon.dbf
new file mode 100644
index 0000000..32798ce
Binary files /dev/null and b/spec/fixtures/polygon.dbf differ
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index d30f00d..1260727 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,6 +1,6 @@
 begin
-  require 'codeclimate-test-reporter'
-  CodeClimate::TestReporter.start
+  require 'simplecov'
+  SimpleCov.start
 rescue LoadError
 end
 
@@ -24,7 +24,7 @@ RSpec.configure do |config|
 end
 
 def fixture_path
-  @fixture_path ||= File.join(File.dirname(__FILE__), '/fixtures')
+  @fixture_path ||= File.join(File.dirname(__FILE__), 'fixtures')
 end
 
 def fixture(filename)

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/column.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/column_type.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/database/foxpro.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/encodings.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/header.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/memo/base.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/memo/dbase3.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/memo/dbase4.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/memo/foxpro.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/record.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/schema.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/table.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/lib/dbf/version.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/dbf-4.2.2.gemspec
-rwxr-xr-x  root/root   /usr/share/rubygems-integration/all/gems/dbf-4.2.2/bin/dbf

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/docs/supported_encodings.csv
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/docs/supported_types.markdown
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/column.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/column_type.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/database/foxpro.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/encodings.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/header.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/memo/base.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/memo/dbase3.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/memo/dbase4.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/memo/foxpro.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/record.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/schema.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/table.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/lib/dbf/version.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/dbf-3.0.5.gemspec
-rwxr-xr-x  root/root   /usr/share/rubygems-integration/all/gems/dbf-3.0.5/bin/dbf

No differences were encountered in the control files

More details

Full run details