infused-dbf 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +55 -0
- data/Manifest.txt +31 -0
- data/README.txt +115 -0
- data/Rakefile +37 -0
- data/bin/dbf +40 -0
- data/dbf.gemspec +37 -0
- data/lib/dbf/column.rb +85 -0
- data/lib/dbf/globals.rb +27 -0
- data/lib/dbf/record.rb +99 -0
- data/lib/dbf/table.rb +243 -0
- data/lib/dbf.rb +7 -0
- data/spec/fixtures/dbase_03.dbf +0 -0
- data/spec/fixtures/dbase_30.dbf +0 -0
- data/spec/fixtures/dbase_30.fpt +0 -0
- data/spec/fixtures/dbase_83.dbf +0 -0
- data/spec/fixtures/dbase_83.dbt +0 -0
- data/spec/fixtures/dbase_83_schema.txt +19 -0
- data/spec/fixtures/dbase_8b.dbf +0 -0
- data/spec/fixtures/dbase_8b.dbt +0 -0
- data/spec/fixtures/dbase_f5.dbf +0 -0
- data/spec/fixtures/dbase_f5.fpt +0 -0
- data/spec/functional/dbf_shared.rb +45 -0
- data/spec/functional/format_03_spec.rb +23 -0
- data/spec/functional/format_30_spec.rb +23 -0
- data/spec/functional/format_83_spec.rb +23 -0
- data/spec/functional/format_8b_spec.rb +23 -0
- data/spec/functional/format_f5_spec.rb +23 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/unit/column_spec.rb +124 -0
- data/spec/unit/record_spec.rb +99 -0
- data/spec/unit/table_spec.rb +162 -0
- metadata +103 -0
data/lib/dbf/table.rb
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
module DBF
|
2
|
+
|
3
|
+
class Table
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :column_count # The total number of columns (columns)
|
7
|
+
attr_reader :columns # An array of DBF::Column
|
8
|
+
attr_reader :version # Internal dBase version number
|
9
|
+
attr_reader :last_updated # Last updated datetime
|
10
|
+
attr_reader :memo_file_format # :fpt or :dpt
|
11
|
+
attr_reader :memo_block_size # The block size for memo records
|
12
|
+
attr_reader :options # The options hash that was used to initialize the table
|
13
|
+
attr_reader :data # DBF file handle
|
14
|
+
attr_reader :memo # Memo file handle
|
15
|
+
|
16
|
+
# Initializes a new DBF::Table
|
17
|
+
# Example:
|
18
|
+
# table = DBF::Table.new 'data.dbf'
|
19
|
+
def initialize(filename, options = {})
|
20
|
+
@data = File.open(filename, 'rb')
|
21
|
+
@memo = open_memo(filename)
|
22
|
+
@options = options
|
23
|
+
reload!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Reloads the database and memo files
|
27
|
+
def reload!
|
28
|
+
@records = nil
|
29
|
+
get_header_info
|
30
|
+
get_memo_header_info if @memo
|
31
|
+
get_column_descriptors
|
32
|
+
build_db_index
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if there is a corresponding memo file
|
36
|
+
def has_memo_file?
|
37
|
+
@memo ? true : false
|
38
|
+
end
|
39
|
+
|
40
|
+
# The total number of active records.
|
41
|
+
def record_count
|
42
|
+
@db_index.size
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an instance of DBF::Column for <b>column_name</b>. The <b>column_name</b>
|
46
|
+
# can be a specified as either a symbol or string.
|
47
|
+
def column(column_name)
|
48
|
+
@columns.detect {|f| f.name == column_name.to_s}
|
49
|
+
end
|
50
|
+
|
51
|
+
# An array of all the records contained in the database file. Each record is an instance
|
52
|
+
# of DBF::Record (or nil if the record is marked for deletion).
|
53
|
+
def records
|
54
|
+
self.to_a
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :rows, :records
|
58
|
+
|
59
|
+
def each
|
60
|
+
0.upto(@record_count - 1) do |n|
|
61
|
+
seek_to_record(n)
|
62
|
+
unless deleted_record?
|
63
|
+
yield DBF::Record.new(self)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# def get_record_from_file(index)
|
69
|
+
# seek_to_record(@db_index[index])
|
70
|
+
# Record.new(self)
|
71
|
+
# end
|
72
|
+
|
73
|
+
# Returns a DBF::Record (or nil if the record has been marked for deletion) for the record at <tt>index</tt>.
|
74
|
+
def record(index)
|
75
|
+
records[index]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Find records using a simple ActiveRecord-like syntax.
|
79
|
+
#
|
80
|
+
# Examples:
|
81
|
+
# table = DBF::Table.new 'mydata.dbf'
|
82
|
+
#
|
83
|
+
# # Find record number 5
|
84
|
+
# table.find(5)
|
85
|
+
#
|
86
|
+
# # Find all records for Keith Morrison
|
87
|
+
# table.find :all, :first_name => "Keith", :last_name => "Morrison"
|
88
|
+
#
|
89
|
+
# # Find first record
|
90
|
+
# table.find :first, :first_name => "Keith"
|
91
|
+
#
|
92
|
+
# The <b>command</b> can be an id, :all, or :first.
|
93
|
+
# <b>options</b> is optional and, if specified, should be a hash where the keys correspond
|
94
|
+
# to column names in the database. The values will be matched exactly with the value
|
95
|
+
# in the database. If you specify more than one key, all values must match in order
|
96
|
+
# for the record to be returned. The equivalent SQL would be "WHERE key1 = 'value1'
|
97
|
+
# AND key2 = 'value2'".
|
98
|
+
def find(command, options = {})
|
99
|
+
results = options.empty? ? records : records.select {|record| all_values_match?(record, options)}
|
100
|
+
|
101
|
+
case command
|
102
|
+
when Fixnum
|
103
|
+
record(command)
|
104
|
+
when :all
|
105
|
+
results
|
106
|
+
when :first
|
107
|
+
results.first
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
alias_method :row, :record
|
112
|
+
|
113
|
+
# Returns a description of the current database file.
|
114
|
+
def version_description
|
115
|
+
VERSION_DESCRIPTIONS[version]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns a database schema in the portable ActiveRecord::Schema format.
|
119
|
+
#
|
120
|
+
# xBase data types are converted to generic types as follows:
|
121
|
+
# - Number columns are converted to :integer if there are no decimals, otherwise
|
122
|
+
# they are converted to :float
|
123
|
+
# - Date columns are converted to :datetime
|
124
|
+
# - Logical columns are converted to :boolean
|
125
|
+
# - Memo columns are converted to :text
|
126
|
+
# - Character columns are converted to :string and the :limit option is set
|
127
|
+
# to the length of the character column
|
128
|
+
#
|
129
|
+
# Example:
|
130
|
+
# create_table "mydata" do |t|
|
131
|
+
# t.column :name, :string, :limit => 30
|
132
|
+
# t.column :last_update, :datetime
|
133
|
+
# t.column :is_active, :boolean
|
134
|
+
# t.column :age, :integer
|
135
|
+
# t.column :notes, :text
|
136
|
+
# end
|
137
|
+
def schema(path = nil)
|
138
|
+
s = "ActiveRecord::Schema.define do\n"
|
139
|
+
s << " create_table \"#{File.basename(@data.path, ".*")}\" do |t|\n"
|
140
|
+
columns.each do |column|
|
141
|
+
s << " t.column #{column.schema_definition}"
|
142
|
+
end
|
143
|
+
s << " end\nend"
|
144
|
+
|
145
|
+
if path
|
146
|
+
File.open(path, 'w') {|f| f.puts(s)}
|
147
|
+
else
|
148
|
+
s
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the record at <tt>index</tt> by seeking to the record in the
|
153
|
+
# physical database file. See the documentation for the records method for
|
154
|
+
# information on how these two methods differ.
|
155
|
+
def get_record_from_file(index)
|
156
|
+
seek_to_record(@db_index[index])
|
157
|
+
Record.new(self)
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def open_memo(file)
|
163
|
+
%w(fpt FPT dbt DBT).each do |extname|
|
164
|
+
filename = replace_extname(file, extname)
|
165
|
+
if File.exists?(filename)
|
166
|
+
@memo_file_format = extname.downcase.to_sym
|
167
|
+
return File.open(filename, 'rb')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def replace_extname(filename, extension)
|
174
|
+
filename.sub(/#{File.extname(filename)[1..-1]}$/, extension)
|
175
|
+
end
|
176
|
+
|
177
|
+
def deleted_record?
|
178
|
+
if @data.read(1).unpack('a') == ['*']
|
179
|
+
@data.rewind
|
180
|
+
true
|
181
|
+
else
|
182
|
+
false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_header_info
|
187
|
+
@data.rewind
|
188
|
+
@version, @record_count, @header_length, @record_length = @data.read(DBF_HEADER_SIZE).unpack('H2 x3 V v2')
|
189
|
+
@column_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_column_descriptors
|
193
|
+
@columns = []
|
194
|
+
@column_count.times do
|
195
|
+
name, type, length, decimal = @data.read(32).unpack('a10 x a x4 C2')
|
196
|
+
if length > 0
|
197
|
+
@columns << Column.new(name.strip, type, length, decimal)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
# Reset the column count in case any were skipped
|
201
|
+
@column_count = @columns.size
|
202
|
+
|
203
|
+
@columns
|
204
|
+
end
|
205
|
+
|
206
|
+
def get_memo_header_info
|
207
|
+
@memo.rewind
|
208
|
+
if @memo_file_format == :fpt
|
209
|
+
@memo_next_available_block, @memo_block_size = @memo.read(FPT_HEADER_SIZE).unpack('N x2 n')
|
210
|
+
@memo_block_size = 0 if @memo_block_size.nil?
|
211
|
+
else
|
212
|
+
@memo_block_size = 512
|
213
|
+
@memo_next_available_block = File.size(@memo.path) / @memo_block_size
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def seek(offset)
|
218
|
+
@data.seek(@header_length + offset)
|
219
|
+
end
|
220
|
+
|
221
|
+
def seek_to_record(index)
|
222
|
+
seek(index * @record_length)
|
223
|
+
end
|
224
|
+
|
225
|
+
def build_db_index
|
226
|
+
@db_index = []
|
227
|
+
@deleted_records = []
|
228
|
+
0.upto(@record_count - 1) do |n|
|
229
|
+
seek_to_record(n)
|
230
|
+
if deleted_record?
|
231
|
+
@deleted_records << n
|
232
|
+
else
|
233
|
+
@db_index << n
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def all_values_match?(record, options)
|
239
|
+
options.map {|key, value| record.attributes[key.to_s.underscore] == value}.all?
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
data/lib/dbf.rb
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,19 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table "dbase_83" do |t|
|
3
|
+
t.column "id", :integer
|
4
|
+
t.column "catcount", :integer
|
5
|
+
t.column "agrpcount", :integer
|
6
|
+
t.column "pgrpcount", :integer
|
7
|
+
t.column "order", :integer
|
8
|
+
t.column "code", :string, :limit => 50
|
9
|
+
t.column "name", :string, :limit => 100
|
10
|
+
t.column "thumbnail", :string, :limit => 254
|
11
|
+
t.column "image", :string, :limit => 254
|
12
|
+
t.column "price", :float
|
13
|
+
t.column "cost", :float
|
14
|
+
t.column "desc", :text
|
15
|
+
t.column "weight", :float
|
16
|
+
t.column "taxable", :boolean
|
17
|
+
t.column "active", :boolean
|
18
|
+
end
|
19
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,45 @@
|
|
1
|
+
describe DBF, :shared => true do
|
2
|
+
specify "sum of column lengths should equal record length specified in header" do
|
3
|
+
header_record_length = @table.instance_eval {@record_length}
|
4
|
+
sum_of_column_lengths = @table.columns.inject(1) {|sum, column| sum + column.length}
|
5
|
+
|
6
|
+
header_record_length.should == sum_of_column_lengths
|
7
|
+
end
|
8
|
+
|
9
|
+
specify "records should be instances of DBF::Record" do
|
10
|
+
@table.records.all? {|record| record.should be_an_instance_of(DBF::Record)}
|
11
|
+
end
|
12
|
+
|
13
|
+
specify "columns should be instances of DBF::Column" do
|
14
|
+
@table.columns.all? {|column| column.should be_an_instance_of(DBF::Column)}
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "column names should not be blank" do
|
18
|
+
@table.columns.all? {|column| column.name.should_not be_empty}
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "column types should be valid" do
|
22
|
+
valid_column_types = %w(C N L D M F B G P Y T I V X @ O +)
|
23
|
+
@table.columns.all? {|column| valid_column_types.should include(column.type)}
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "column lengths should be instances of Fixnum" do
|
27
|
+
@table.columns.all? {|column| column.length.should be_an_instance_of(Fixnum)}
|
28
|
+
end
|
29
|
+
|
30
|
+
specify "column lengths should be larger than 0" do
|
31
|
+
@table.columns.all? {|column| column.length.should > 0}
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "column decimals should be instances of Fixnum" do
|
35
|
+
@table.columns.all? {|column| column.decimal.should be_an_instance_of(Fixnum)}
|
36
|
+
end
|
37
|
+
|
38
|
+
specify "column read accessors should return the attribute after typecast" do
|
39
|
+
@table.columns do |column|
|
40
|
+
record = table.records.first
|
41
|
+
record.send(column.name).should == record[column.name]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
require File.dirname(__FILE__) + "/dbf_shared"
|
3
|
+
|
4
|
+
describe DBF, "of type 03 (dBase III without memo file)" do
|
5
|
+
before(:each) do
|
6
|
+
@table = DBF::Table.new "#{DB_PATH}/dbase_03.dbf"
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "DBF"
|
10
|
+
|
11
|
+
it "should report the correct version number" do
|
12
|
+
@table.version.should == "03"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a memo file" do
|
16
|
+
@table.should_not have_memo_file
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should report the correct memo type" do
|
20
|
+
@table.memo_file_format.should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
require File.dirname(__FILE__) + "/dbf_shared"
|
3
|
+
|
4
|
+
describe DBF, "of type 30 (Visual FoxPro)" do
|
5
|
+
before(:each) do
|
6
|
+
@table = DBF::Table.new "#{DB_PATH}/dbase_30.dbf"
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "DBF"
|
10
|
+
|
11
|
+
it "should report the correct version number" do
|
12
|
+
@table.version.should == "30"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a memo file" do
|
16
|
+
@table.should have_memo_file
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should report the correct memo type" do
|
20
|
+
@table.memo_file_format.should == :fpt
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
require File.dirname(__FILE__) + "/dbf_shared"
|
3
|
+
|
4
|
+
describe DBF, "of type 83 (dBase III with memo file)" do
|
5
|
+
before(:each) do
|
6
|
+
@table = DBF::Table.new "#{DB_PATH}/dbase_83.dbf"
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "DBF"
|
10
|
+
|
11
|
+
it "should report the correct version number" do
|
12
|
+
@table.version.should == "83"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a memo file" do
|
16
|
+
@table.should have_memo_file
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should report the correct memo type" do
|
20
|
+
@table.memo_file_format.should == :dbt
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
require File.dirname(__FILE__) + "/dbf_shared"
|
3
|
+
|
4
|
+
describe DBF, "of type 8b (dBase IV with memo file)" do
|
5
|
+
before(:each) do
|
6
|
+
@table = DBF::Table.new "#{DB_PATH}/dbase_8b.dbf"
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "DBF"
|
10
|
+
|
11
|
+
it "should report the correct version number" do
|
12
|
+
@table.version.should == "8b"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a memo file" do
|
16
|
+
@table.should have_memo_file
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should report the correct memo type" do
|
20
|
+
@table.memo_file_format.should == :dbt
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
require File.dirname(__FILE__) + "/dbf_shared"
|
3
|
+
|
4
|
+
describe DBF, "of type f5 (FoxPro with memo file)" do
|
5
|
+
before(:each) do
|
6
|
+
@table = DBF::Table.new "#{DB_PATH}/dbase_f5.dbf"
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "DBF"
|
10
|
+
|
11
|
+
it "should report the correct version number" do
|
12
|
+
@table.version.should == "f5"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a memo file" do
|
16
|
+
@table.should have_memo_file
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should report the correct memo type" do
|
20
|
+
@table.memo_file_format.should == :fpt
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe DBF::Column do
|
4
|
+
|
5
|
+
context "when initialized" do
|
6
|
+
before do
|
7
|
+
@column = DBF::Column.new "ColumnName", "N", 1, 0
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should set the #name accessor" do
|
11
|
+
@column.name.should == "ColumnName"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should set the #type accessor" do
|
15
|
+
@column.type.should == "N"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the #length accessor" do
|
19
|
+
@column.length.should == 1
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set the #decimal accessor" do
|
23
|
+
@column.decimal.should == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise an error if length is greater than 0" do
|
27
|
+
lambda { column = DBF::Column.new "ColumnName", "N", -1, 0 }.should raise_error(DBF::ColumnLengthError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "#type_cast" do
|
32
|
+
it "should cast numbers with decimals to Float" do
|
33
|
+
value = "13.5"
|
34
|
+
column = DBF::Column.new "ColumnName", "N", 2, 1
|
35
|
+
column.type_cast(value).should == 13.5
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should cast numbers with no decimals to Integer" do
|
39
|
+
value = "135"
|
40
|
+
column = DBF::Column.new "ColumnName", "N", 3, 0
|
41
|
+
column.type_cast(value).should == 13105
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should cast :integer to Integer" do
|
45
|
+
value = "135"
|
46
|
+
column = DBF::Column.new "ColumnName", "I", 3, 0
|
47
|
+
column.type_cast(value).should == 13105
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should cast boolean to True" do
|
51
|
+
value = "y"
|
52
|
+
column = DBF::Column.new "ColumnName", "L", 1, 0
|
53
|
+
column.type_cast(value).should == true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should cast boolean to False" do
|
57
|
+
value = "n"
|
58
|
+
column = DBF::Column.new "ColumnName", "L", 1, 0
|
59
|
+
column.type_cast(value).should == false
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should cast :datetime columns to DateTime" do
|
63
|
+
value = "Nl%\000\300Z\252\003"
|
64
|
+
column = DBF::Column.new "ColumnName", "T", 16, 0
|
65
|
+
column.type_cast(value).should == "2002-10-10T17:04:56+00:00"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should cast :date columns to Date" do
|
69
|
+
value = "20050712"
|
70
|
+
column = DBF::Column.new "ColumnName", "D", 8, 0
|
71
|
+
column.type_cast(value).should == Date.new(2005,7,12)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "#schema_definition" do
|
76
|
+
it "should define an integer column if type is (N)umber with 9 decimals" do
|
77
|
+
column = DBF::Column.new "ColumnName", "N", 1, 0
|
78
|
+
column.schema_definition.should == "\"column_name\", :integer\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should define a float colmn if type is (N)umber with more than 0 decimals" do
|
82
|
+
column = DBF::Column.new "ColumnName", "N", 1, 2
|
83
|
+
column.schema_definition.should == "\"column_name\", :float\n"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should define a date column if type is (D)ate" do
|
87
|
+
column = DBF::Column.new "ColumnName", "D", 8, 0
|
88
|
+
column.schema_definition.should == "\"column_name\", :date\n"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should define a datetime column if type is (D)ate" do
|
92
|
+
column = DBF::Column.new "ColumnName", "T", 16, 0
|
93
|
+
column.schema_definition.should == "\"column_name\", :datetime\n"
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should define a boolean column if type is (L)ogical" do
|
97
|
+
column = DBF::Column.new "ColumnName", "L", 1, 0
|
98
|
+
column.schema_definition.should == "\"column_name\", :boolean\n"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should define a text column if type is (M)emo" do
|
102
|
+
column = DBF::Column.new "ColumnName", "M", 1, 0
|
103
|
+
column.schema_definition.should == "\"column_name\", :text\n"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should define a string column with length for any other data types" do
|
107
|
+
column = DBF::Column.new "ColumnName", "X", 20, 0
|
108
|
+
column.schema_definition.should == "\"column_name\", :string, :limit => 20\n"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "#strip_non_ascii_chars" do
|
113
|
+
it "should strip characters below decimal 32 and above decimal 127" do
|
114
|
+
column = DBF::Column.new "ColumnName", "N", 1, 0
|
115
|
+
column.send(:strip_non_ascii_chars, "--\x1F-\x68\x65\x6C\x6C\x6F world-\x80--").should == "---hello world---"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should truncate characters with decimal 0" do
|
119
|
+
column = DBF::Column.new "ColumnName", "N", 1, 0
|
120
|
+
column.send(:strip_non_ascii_chars, "--\x1F-\x68\x65\x6C\x6C\x6F \x00 world-\x80--").should == "---hello "
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|