dbf 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +4 -2
- data/dbf.gemspec +6 -9
- data/lib/dbf/column.rb +19 -15
- data/lib/dbf/column_builder.rb +31 -0
- data/lib/dbf/column_type.rb +61 -15
- data/lib/dbf/database/foxpro.rb +21 -32
- data/lib/dbf/encodings.rb +2 -0
- data/lib/dbf/file_handler.rb +36 -0
- data/lib/dbf/find.rb +54 -0
- data/lib/dbf/header.rb +7 -8
- data/lib/dbf/memo/base.rb +4 -0
- data/lib/dbf/memo/dbase3.rb +5 -3
- data/lib/dbf/memo/dbase4.rb +4 -2
- data/lib/dbf/memo/foxpro.rb +16 -7
- data/lib/dbf/record.rb +62 -34
- data/lib/dbf/record_context.rb +5 -0
- data/lib/dbf/record_iterator.rb +35 -0
- data/lib/dbf/schema.rb +23 -21
- data/lib/dbf/table.rb +44 -178
- data/lib/dbf/version.rb +3 -1
- data/lib/dbf/version_config.rb +79 -0
- data/lib/dbf.rb +8 -0
- metadata +15 -64
- data/spec/dbf/column_spec.rb +0 -286
- data/spec/dbf/database/foxpro_spec.rb +0 -51
- data/spec/dbf/encoding_spec.rb +0 -47
- data/spec/dbf/file_formats_spec.rb +0 -219
- data/spec/dbf/record_spec.rb +0 -114
- data/spec/dbf/table_spec.rb +0 -375
- data/spec/fixtures/cp1251.dbf +0 -0
- data/spec/fixtures/cp1251_summary.txt +0 -12
- data/spec/fixtures/dbase_02.dbf +0 -0
- data/spec/fixtures/dbase_02_summary.txt +0 -23
- data/spec/fixtures/dbase_03.dbf +0 -0
- data/spec/fixtures/dbase_03_cyrillic.dbf +0 -0
- data/spec/fixtures/dbase_03_cyrillic_summary.txt +0 -11
- data/spec/fixtures/dbase_03_summary.txt +0 -40
- data/spec/fixtures/dbase_30.dbf +0 -0
- data/spec/fixtures/dbase_30.fpt +0 -0
- data/spec/fixtures/dbase_30_summary.txt +0 -154
- data/spec/fixtures/dbase_31.dbf +0 -0
- data/spec/fixtures/dbase_31_summary.txt +0 -20
- data/spec/fixtures/dbase_32.dbf +0 -0
- data/spec/fixtures/dbase_32_summary.txt +0 -11
- data/spec/fixtures/dbase_83.dbf +0 -0
- data/spec/fixtures/dbase_83.dbt +0 -0
- data/spec/fixtures/dbase_83_missing_memo.dbf +0 -0
- data/spec/fixtures/dbase_83_missing_memo_record_0.yml +0 -16
- data/spec/fixtures/dbase_83_record_0.yml +0 -16
- data/spec/fixtures/dbase_83_record_9.yml +0 -23
- data/spec/fixtures/dbase_83_schema_ar.txt +0 -19
- data/spec/fixtures/dbase_83_schema_sq.txt +0 -21
- data/spec/fixtures/dbase_83_schema_sq_lim.txt +0 -21
- data/spec/fixtures/dbase_83_summary.txt +0 -24
- data/spec/fixtures/dbase_8b.dbf +0 -0
- data/spec/fixtures/dbase_8b.dbt +0 -0
- data/spec/fixtures/dbase_8b_summary.txt +0 -15
- data/spec/fixtures/dbase_8c.dbf +0 -0
- data/spec/fixtures/dbase_f5.dbf +0 -0
- data/spec/fixtures/dbase_f5.fpt +0 -0
- data/spec/fixtures/dbase_f5_summary.txt +0 -68
- data/spec/fixtures/foxprodb/FOXPRO-DB-TEST.DBC +0 -0
- data/spec/fixtures/foxprodb/FOXPRO-DB-TEST.DCT +0 -0
- data/spec/fixtures/foxprodb/FOXPRO-DB-TEST.DCX +0 -0
- data/spec/fixtures/foxprodb/calls.CDX +0 -0
- data/spec/fixtures/foxprodb/calls.FPT +0 -0
- data/spec/fixtures/foxprodb/calls.dbf +0 -0
- data/spec/fixtures/foxprodb/contacts.CDX +0 -0
- data/spec/fixtures/foxprodb/contacts.FPT +0 -0
- data/spec/fixtures/foxprodb/contacts.dbf +0 -0
- data/spec/fixtures/foxprodb/setup.CDX +0 -0
- data/spec/fixtures/foxprodb/setup.dbf +0 -0
- data/spec/fixtures/foxprodb/types.CDX +0 -0
- data/spec/fixtures/foxprodb/types.dbf +0 -0
- data/spec/fixtures/polygon.dbf +0 -0
- data/spec/spec_helper.rb +0 -33
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
RSpec.shared_examples_for 'DBF' do
|
|
4
|
-
let(:header_record_length) { table.instance_eval { header.record_length } }
|
|
5
|
-
let(:sum_of_column_lengths) { table.columns.inject(1) { |sum, column| sum + column.length } }
|
|
6
|
-
|
|
7
|
-
specify 'sum of column lengths should equal record length specified in header plus one' do
|
|
8
|
-
expect(header_record_length).to eq sum_of_column_lengths
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
specify 'records should be instances of DBF::Record' do
|
|
12
|
-
expect(table).to all be_a(DBF::Record)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
specify 'record count should be the same as reported in the header' do
|
|
16
|
-
expect(table.entries.size).to eq table.record_count
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
specify 'column names should not be blank' do
|
|
20
|
-
table.columns.each do |column|
|
|
21
|
-
expect(column.name).to_not be_empty
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
specify 'column types should be valid' do
|
|
26
|
-
valid_column_types = %w[C N L D M F B G P Y T I V X @ O + 0]
|
|
27
|
-
table.columns.each do |column|
|
|
28
|
-
expect(valid_column_types).to include(column.type)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
specify 'column lengths should be instances of Integer' do
|
|
33
|
-
table.columns.each do |column|
|
|
34
|
-
expect(column.length).to be_a(Integer)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
specify 'column lengths should be larger than 0' do
|
|
39
|
-
table.columns.each do |column|
|
|
40
|
-
expect(column.length).to be > 0
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
specify 'column decimals should be instances of Integer' do
|
|
45
|
-
table.columns.each do |column|
|
|
46
|
-
expect(column.decimal).to be_a(Integer)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
RSpec.describe DBF, 'of type 02 (FoxBase)' do
|
|
52
|
-
let(:table) { DBF::Table.new fixture('dbase_02.dbf') }
|
|
53
|
-
|
|
54
|
-
it_behaves_like 'DBF'
|
|
55
|
-
|
|
56
|
-
it 'reports the correct version number' do
|
|
57
|
-
expect(table.version).to eq '02'
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it 'reports the correct version description' do
|
|
61
|
-
expect(table.version_description).to eq 'FoxBase'
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it 'determines the number of records' do
|
|
65
|
-
expect(table.record_count).to eq 9
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
RSpec.describe DBF, 'of type 03 (dBase III without memo file)' do
|
|
70
|
-
let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
|
|
71
|
-
|
|
72
|
-
it_behaves_like 'DBF'
|
|
73
|
-
|
|
74
|
-
it 'reports the correct version number' do
|
|
75
|
-
expect(table.version).to eq '03'
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it 'reports the correct version description' do
|
|
79
|
-
expect(table.version_description).to eq 'dBase III without memo file'
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it 'determines the number of records' do
|
|
83
|
-
expect(table.record_count).to eq 14
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
RSpec.describe DBF, 'of type 30 (Visual FoxPro)' do
|
|
88
|
-
let(:table) { DBF::Table.new fixture('dbase_30.dbf') }
|
|
89
|
-
|
|
90
|
-
it_behaves_like 'DBF'
|
|
91
|
-
|
|
92
|
-
it 'reports the correct version number' do
|
|
93
|
-
expect(table.version).to eq '30'
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it 'reports the correct version description' do
|
|
97
|
-
expect(table.version_description).to eq 'Visual FoxPro'
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
it 'determines the number of records' do
|
|
101
|
-
expect(table.record_count).to eq 34
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
it 'reads memo data' do
|
|
105
|
-
expect(table.record(3).classes).to match(/\AAgriculture.*Farming\r\n\Z/m)
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
RSpec.describe DBF, 'of type 31 (Visual FoxPro with AutoIncrement field)' do
|
|
110
|
-
let(:table) { DBF::Table.new fixture('dbase_31.dbf') }
|
|
111
|
-
|
|
112
|
-
it_behaves_like 'DBF'
|
|
113
|
-
|
|
114
|
-
it 'has a dBase version of 31' do
|
|
115
|
-
expect(table.version).to eq '31'
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
it 'reports the correct version description' do
|
|
119
|
-
expect(table.version_description).to eq 'Visual FoxPro with AutoIncrement field'
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
it 'determines the number of records' do
|
|
123
|
-
expect(table.record_count).to eq 77
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
RSpec.describe DBF, 'of type 32 (Visual FoxPro with field type Varchar or Varbinary)' do
|
|
128
|
-
let(:table) { DBF::Table.new fixture('dbase_32.dbf') }
|
|
129
|
-
|
|
130
|
-
it_behaves_like 'DBF'
|
|
131
|
-
|
|
132
|
-
it 'has a dBase version of 32' do
|
|
133
|
-
expect(table.version).to eq '32'
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
it 'reports the correct version description' do
|
|
137
|
-
expect(table.version_description).to eq 'Visual FoxPro with field type Varchar or Varbinary'
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
it 'determines the number of records' do
|
|
141
|
-
expect(table.record_count).to eq 1
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
RSpec.describe DBF, 'of type 83 (dBase III with memo file)' do
|
|
146
|
-
let(:table) { DBF::Table.new fixture('dbase_83.dbf') }
|
|
147
|
-
|
|
148
|
-
it_behaves_like 'DBF'
|
|
149
|
-
|
|
150
|
-
it 'reports the correct version number' do
|
|
151
|
-
expect(table.version).to eq '83'
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
it 'reports the correct version description' do
|
|
155
|
-
expect(table.version_description).to eq 'dBase III with memo file'
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
it 'determines the number of records' do
|
|
159
|
-
expect(table.record_count).to eq 67
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
RSpec.describe DBF, 'of type 8b (dBase IV with memo file)' do
|
|
164
|
-
let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
|
|
165
|
-
|
|
166
|
-
it_behaves_like 'DBF'
|
|
167
|
-
|
|
168
|
-
it 'reports the correct version number' do
|
|
169
|
-
expect(table.version).to eq '8b'
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
it 'reports the correct version description' do
|
|
173
|
-
expect(table.version_description).to eq 'dBase IV with memo file'
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
it 'determines the number of records' do
|
|
177
|
-
expect(table.record_count).to eq 10
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
RSpec.describe DBF, 'of type 8c (unknown)' do
|
|
182
|
-
let(:table) { DBF::Table.new fixture('dbase_8c.dbf') }
|
|
183
|
-
|
|
184
|
-
it_behaves_like 'DBF'
|
|
185
|
-
|
|
186
|
-
it 'reports the correct version number' do
|
|
187
|
-
expect(table.version).to eq '8c'
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
it 'reports the correct version description' do
|
|
191
|
-
expect(table.version_description).to eq 'dBase 7'
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
it 'determines the number of records' do
|
|
195
|
-
expect(table.record_count).to eq 10
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
RSpec.describe DBF, 'of type f5 (FoxPro with memo file)' do
|
|
200
|
-
let(:table) { DBF::Table.new fixture('dbase_f5.dbf') }
|
|
201
|
-
|
|
202
|
-
it_behaves_like 'DBF'
|
|
203
|
-
|
|
204
|
-
it 'reports the correct version number' do
|
|
205
|
-
expect(table.version).to eq 'f5'
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
it 'reports the correct version description' do
|
|
209
|
-
expect(table.version_description).to eq 'FoxPro with memo file'
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
it 'determines the number of records' do
|
|
213
|
-
expect(table.record_count).to eq 975
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
it 'reads memo data' do
|
|
217
|
-
expect(table.record(3).datn.to_s).to eq '1870-06-30'
|
|
218
|
-
end
|
|
219
|
-
end
|
data/spec/dbf/record_spec.rb
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
RSpec.describe DBF::Record do
|
|
4
|
-
describe '#to_a' do
|
|
5
|
-
let(:table) { DBF::Table.new fixture('dbase_83.dbf') }
|
|
6
|
-
|
|
7
|
-
it 'returns an ordered array of attribute values' do
|
|
8
|
-
record = table.record(0)
|
|
9
|
-
expect(record.to_a).to eq YAML.load_file(fixture('dbase_83_record_0.yml'))
|
|
10
|
-
|
|
11
|
-
record = table.record(9)
|
|
12
|
-
expect(record.to_a).to eq YAML.load_file(fixture('dbase_83_record_9.yml'))
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
describe 'with missing memo file' do
|
|
16
|
-
describe 'when opening a path' do
|
|
17
|
-
let(:table) { DBF::Table.new fixture('dbase_83_missing_memo.dbf') }
|
|
18
|
-
|
|
19
|
-
it 'returns nil values for memo fields' do
|
|
20
|
-
record = table.record(0)
|
|
21
|
-
expect(record.to_a).to eq YAML.load_file(fixture('dbase_83_missing_memo_record_0.yml'))
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
describe 'when opening StringIO' do
|
|
27
|
-
let(:data) { StringIO.new(File.read(fixture('dbase_83_missing_memo.dbf'))) }
|
|
28
|
-
let(:table) { DBF::Table.new(data) }
|
|
29
|
-
|
|
30
|
-
it 'returns nil values for memo fields' do
|
|
31
|
-
record = table.record(0)
|
|
32
|
-
expect(record.to_a).to eq YAML.load_file(fixture('dbase_83_missing_memo_record_0.yml'))
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
describe '#==' do
|
|
38
|
-
let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
|
|
39
|
-
let(:record) { table.record(9) }
|
|
40
|
-
|
|
41
|
-
describe 'when other does not have attributes' do
|
|
42
|
-
it 'returns false' do
|
|
43
|
-
expect(record == instance_double(DBF::Record)).to be_falsey
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
describe 'if other attributes match' do
|
|
48
|
-
let(:attributes) { {x: 1, y: 2} }
|
|
49
|
-
let(:other) { instance_double(DBF::Record, attributes: attributes) }
|
|
50
|
-
|
|
51
|
-
before do
|
|
52
|
-
allow(record).to receive(:attributes).and_return(attributes)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'returns true' do
|
|
56
|
-
expect(record == other).to be_truthy
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
describe 'column accessors' do
|
|
63
|
-
let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
|
|
64
|
-
let(:record) { table.find(0) }
|
|
65
|
-
|
|
66
|
-
%w[character numerical date logical float memo].each do |column_name|
|
|
67
|
-
it "defines accessor method for '#{column_name}' column" do
|
|
68
|
-
expect(record).to respond_to(column_name.to_sym)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
describe 'column data for table' do
|
|
75
|
-
describe 'using specified in dbf encoding' do
|
|
76
|
-
let(:table) { DBF::Table.new fixture('cp1251.dbf') }
|
|
77
|
-
let(:record) { table.find(0) }
|
|
78
|
-
|
|
79
|
-
it 'encodes to default system encoding' do
|
|
80
|
-
expect(record.name.encoding).to eq Encoding.default_external
|
|
81
|
-
|
|
82
|
-
# russian a
|
|
83
|
-
expect(record.name.encode('UTF-8').unpack1('H4')).to eq 'd0b0'
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
describe 'overriding specified in dbf encoding' do
|
|
88
|
-
let(:table) { DBF::Table.new fixture('cp1251.dbf'), nil, 'cp866' }
|
|
89
|
-
let(:record) { table.find(0) }
|
|
90
|
-
|
|
91
|
-
it 'transcodes from manually specified encoding to default system encoding' do
|
|
92
|
-
expect(record.name.encoding).to eq Encoding.default_external
|
|
93
|
-
|
|
94
|
-
# russian а encoded in cp1251 and read as if it was encoded in cp866
|
|
95
|
-
expect(record.name.encode('UTF-8').unpack1('H4')).to eq 'd180'
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
describe '#attributes' do
|
|
101
|
-
let(:table) { DBF::Table.new fixture('dbase_8b.dbf') }
|
|
102
|
-
let(:record) { table.find(0) }
|
|
103
|
-
|
|
104
|
-
it 'is a hash of attribute name/value pairs' do
|
|
105
|
-
expect(record.attributes).to be_a(Hash)
|
|
106
|
-
expect(record.attributes['CHARACTER']).to eq 'One'
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
it 'has only original field names as keys' do
|
|
110
|
-
original_field_names = %w[CHARACTER DATE FLOAT LOGICAL MEMO NUMERICAL]
|
|
111
|
-
expect(record.attributes.keys.sort).to eq original_field_names
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
data/spec/dbf/table_spec.rb
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
RSpec.describe DBF::Table do
|
|
4
|
-
let(:dbf_path) { fixture('dbase_83.dbf') }
|
|
5
|
-
let(:memo_path) { fixture('dbase_83.dbt') }
|
|
6
|
-
let(:table) { DBF::Table.new dbf_path }
|
|
7
|
-
|
|
8
|
-
specify 'foxpro versions' do
|
|
9
|
-
expect(DBF::Table::FOXPRO_VERSIONS.keys.sort).to eq %w[30 31 f5 fb].sort
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
specify 'row is an alias of record' do
|
|
13
|
-
expect(table.record(1)).to eq table.row(1)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
describe '#initialize' do
|
|
17
|
-
let(:data) { StringIO.new File.read(dbf_path) }
|
|
18
|
-
let(:memo) { StringIO.new File.read(memo_path) }
|
|
19
|
-
|
|
20
|
-
describe 'when given a path to an existing dbf file' do
|
|
21
|
-
it 'does not raise an error' do
|
|
22
|
-
expect { DBF::Table.new dbf_path }.to_not raise_error
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
describe 'when given a path to a non-existent dbf file' do
|
|
27
|
-
it 'raises a DBF::FileNotFound error' do
|
|
28
|
-
expect { DBF::Table.new 'x' }.to raise_error(DBF::FileNotFoundError, 'file not found: x')
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
describe 'when data is nil' do
|
|
33
|
-
it 'raises ArgumentError' do
|
|
34
|
-
expect { DBF::Table.new nil }.to raise_error(ArgumentError, 'data must be a file path or StringIO object')
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
describe 'when given paths to existing dbf and memo files' do
|
|
39
|
-
it 'does not raise an error' do
|
|
40
|
-
expect { DBF::Table.new dbf_path, memo_path }.to_not raise_error
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it 'accepts an io-like data object' do
|
|
45
|
-
expect { DBF::Table.new data }.to_not raise_error
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
it 'accepts an io-like data and memo object' do
|
|
49
|
-
expect { DBF::Table.new data, memo }.to_not raise_error
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
describe '#close' do
|
|
54
|
-
before { table.close }
|
|
55
|
-
|
|
56
|
-
it 'closes the io' do
|
|
57
|
-
expect { table.record(1) }.to raise_error(IOError)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
describe '#schema' do
|
|
62
|
-
describe 'when data is IO' do
|
|
63
|
-
let(:control_schema) { File.read(fixture('dbase_83_schema_ar.txt')) }
|
|
64
|
-
|
|
65
|
-
it 'matches the test schema fixture' do
|
|
66
|
-
expect(table.schema).to eq control_schema
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it 'raises ArgumentError if there is no matching schema' do
|
|
70
|
-
expect { table.schema(:invalid) }.to raise_error(
|
|
71
|
-
ArgumentError,
|
|
72
|
-
':invalid is not a valid schema. Valid schemas are: activerecord, json, sequel.'
|
|
73
|
-
)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
describe 'when data is StringIO' do
|
|
78
|
-
let(:data) { StringIO.new File.read(dbf_path) }
|
|
79
|
-
let(:table) { DBF::Table.new data }
|
|
80
|
-
|
|
81
|
-
let(:control_schema) { File.read(fixture('dbase_83_schema_ar.txt')) }
|
|
82
|
-
|
|
83
|
-
it 'matches the test schema fixture' do
|
|
84
|
-
table.name = 'dbase_83'
|
|
85
|
-
expect(table.schema).to eq control_schema
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
describe '#sequel_schema' do
|
|
91
|
-
it 'returns a valid Sequel migration by default' do
|
|
92
|
-
control_schema = File.read(fixture('dbase_83_schema_sq.txt'))
|
|
93
|
-
expect(table.sequel_schema).to eq control_schema
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it 'returns a limited Sequel migration when passed true' do
|
|
97
|
-
control_schema = File.read(fixture('dbase_83_schema_sq_lim.txt'))
|
|
98
|
-
expect(table.sequel_schema).to eq control_schema
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
describe '#json_schema' do
|
|
104
|
-
it 'is valid JSON' do
|
|
105
|
-
expect { JSON.parse(table.json_schema) }.to_not raise_error
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it 'matches the test fixture' do
|
|
109
|
-
data = JSON.parse(table.json_schema)
|
|
110
|
-
expect(data).to eq [
|
|
111
|
-
{'name' => 'ID', 'type' => 'N', 'length' => 19, 'decimal' => 0},
|
|
112
|
-
{'name' => 'CATCOUNT', 'type' => 'N', 'length' => 19, 'decimal' => 0},
|
|
113
|
-
{'name' => 'AGRPCOUNT', 'type' => 'N', 'length' => 19, 'decimal' => 0},
|
|
114
|
-
{'name' => 'PGRPCOUNT', 'type' => 'N', 'length' => 19, 'decimal' => 0},
|
|
115
|
-
{'name' => 'ORDER', 'type' => 'N', 'length' => 19, 'decimal' => 0},
|
|
116
|
-
{'name' => 'CODE', 'type' => 'C', 'length' => 50, 'decimal' => 0},
|
|
117
|
-
{'name' => 'NAME', 'type' => 'C', 'length' => 100, 'decimal' => 0},
|
|
118
|
-
{'name' => 'THUMBNAIL', 'type' => 'C', 'length' => 254, 'decimal' => 0},
|
|
119
|
-
{'name' => 'IMAGE', 'type' => 'C', 'length' => 254, 'decimal' => 0},
|
|
120
|
-
{'name' => 'PRICE', 'type' => 'N', 'length' => 13, 'decimal' => 2},
|
|
121
|
-
{'name' => 'COST', 'type' => 'N', 'length' => 13, 'decimal' => 2},
|
|
122
|
-
{'name' => 'DESC', 'type' => 'M', 'length' => 10, 'decimal' => 0},
|
|
123
|
-
{'name' => 'WEIGHT', 'type' => 'N', 'length' => 13, 'decimal' => 2},
|
|
124
|
-
{'name' => 'TAXABLE', 'type' => 'L', 'length' => 1, 'decimal' => 0},
|
|
125
|
-
{'name' => 'ACTIVE', 'type' => 'L', 'length' => 1, 'decimal' => 0}
|
|
126
|
-
]
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
describe '#to_csv' do
|
|
131
|
-
after do
|
|
132
|
-
FileUtils.rm_f 'test.csv'
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
describe 'when no path param passed' do
|
|
136
|
-
it 'writes to STDOUT' do
|
|
137
|
-
expect { table.to_csv }.to output.to_stdout
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
describe 'when path param passed' do
|
|
142
|
-
before { table.to_csv('test.csv') }
|
|
143
|
-
|
|
144
|
-
it 'creates a custom csv file' do
|
|
145
|
-
expect(File).to exist('test.csv')
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
describe '#record' do
|
|
151
|
-
it 'return nil for deleted records' do
|
|
152
|
-
allow(table).to receive(:deleted_record?).and_return(true)
|
|
153
|
-
expect(table.record(5)).to be_nil
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
describe 'when dbf has no column definitions' do
|
|
157
|
-
let(:dbf_path) { fixture('polygon.dbf') }
|
|
158
|
-
|
|
159
|
-
it 'raises a DBF::NoColumnsDefined error' do
|
|
160
|
-
expect { DBF::Table.new(dbf_path).record(1) }.to raise_error(DBF::NoColumnsDefined, 'The DBF file has no columns defined')
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
describe '#current_record' do
|
|
166
|
-
it 'returns nil for deleted records' do
|
|
167
|
-
allow(table).to receive(:deleted_record?).and_return(true)
|
|
168
|
-
expect(table.record(0)).to be_nil
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
describe '#find' do
|
|
173
|
-
describe 'with index' do
|
|
174
|
-
it 'returns the correct record' do
|
|
175
|
-
expect(table.find(5)).to eq table.record(5)
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
describe 'with array of indexes' do
|
|
180
|
-
it 'returns the correct records' do
|
|
181
|
-
expect(table.find([1, 5, 10])).to eq [table.record(1), table.record(5), table.record(10)]
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
describe 'with :all' do
|
|
186
|
-
let(:records) do
|
|
187
|
-
table.find(:all, weight: 0.0)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
it 'retrieves only matching records' do
|
|
191
|
-
expect(records.size).to eq 66
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
it 'yields to a block if given' do
|
|
195
|
-
record_count = 0
|
|
196
|
-
table.find(:all, weight: 0.0) do |record|
|
|
197
|
-
record_count += 1
|
|
198
|
-
expect(record).to be_a DBF::Record
|
|
199
|
-
end
|
|
200
|
-
expect(record_count).to eq 66
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
it 'returns all records if options are empty' do
|
|
204
|
-
expect(table.find(:all)).to eq table.to_a
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
it 'returns matching records when used with options' do
|
|
208
|
-
expect(table.find(:all, 'WEIGHT' => 0.0)).to eq(table.select { |r| r['weight'] == 0.0 })
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
it 'ANDS multiple search terms' do
|
|
212
|
-
expect(table.find(:all, 'ID' => 30, :IMAGE => 'graphics/00000001/TBC01.jpg')).to be_empty
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
it 'matches original column names' do
|
|
216
|
-
expect(table.find(:all, 'WEIGHT' => 0.0)).to_not be_empty
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
it 'matches symbolized column names' do
|
|
220
|
-
expect(table.find(:all, WEIGHT: 0.0)).to_not be_empty
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
it 'matches downcased column names' do
|
|
224
|
-
expect(table.find(:all, 'weight' => 0.0)).to_not be_empty
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
it 'matches symbolized downcased column names' do
|
|
228
|
-
expect(table.find(:all, weight: 0.0)).to_not be_empty
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
describe 'with :first' do
|
|
233
|
-
it 'returns the first record if options are empty' do
|
|
234
|
-
expect(table.find(:first)).to eq table.record(0)
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
it 'returns the first matching record when used with options' do
|
|
238
|
-
expect(table.find(:first, 'CODE' => 'C')).to eq table.record(5)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
it 'ANDs multiple search terms' do
|
|
242
|
-
expect(table.find(:first, 'ID' => 30, 'IMAGE' => 'graphics/00000001/TBC01.jpg')).to be_nil
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
describe '#filename' do
|
|
248
|
-
it 'returns the filename as a string' do
|
|
249
|
-
expect(table.filename).to eq 'dbase_83.dbf'
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
describe '#name' do
|
|
254
|
-
describe 'when data is an IO' do
|
|
255
|
-
it 'defaults to the filename less extension' do
|
|
256
|
-
expect(table.name).to eq 'dbase_83'
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
it 'is mutable' do
|
|
260
|
-
table.name = 'database_83'
|
|
261
|
-
expect(table.name).to eq 'database_83'
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
describe 'when data is a StringIO' do
|
|
266
|
-
let(:data) { StringIO.new File.read(dbf_path) }
|
|
267
|
-
let(:memo) { StringIO.new File.read(memo_path) }
|
|
268
|
-
let(:table) { DBF::Table.new data }
|
|
269
|
-
|
|
270
|
-
it 'is nil' do
|
|
271
|
-
expect(table.name).to be_nil
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
it 'is mutable' do
|
|
275
|
-
table.name = 'database_83'
|
|
276
|
-
expect(table.name).to eq 'database_83'
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
describe '#has_memo_file?' do
|
|
282
|
-
describe 'without a memo file' do
|
|
283
|
-
let(:table) { DBF::Table.new fixture('dbase_03.dbf') }
|
|
284
|
-
|
|
285
|
-
it 'is false' do
|
|
286
|
-
expect(table).to_not have_memo_file
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
describe 'with a memo file' do
|
|
291
|
-
it 'is true' do
|
|
292
|
-
expect(table).to have_memo_file
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
describe '#columns' do
|
|
298
|
-
let(:columns) { table.columns }
|
|
299
|
-
|
|
300
|
-
it 'is an array of Columns' do
|
|
301
|
-
expect(columns).to be_an(Array)
|
|
302
|
-
expect(columns).to_not be_empty
|
|
303
|
-
expect(columns).to(be_all { |c| c.is_a? DBF::Column })
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
describe '#column_names' do
|
|
308
|
-
let(:column_names) do
|
|
309
|
-
%w[ID CATCOUNT AGRPCOUNT PGRPCOUNT ORDER CODE NAME THUMBNAIL IMAGE PRICE COST DESC WEIGHT TAXABLE ACTIVE]
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
describe 'when data is an IO' do
|
|
313
|
-
it 'is an array of all column names' do
|
|
314
|
-
expect(table.column_names).to eq column_names
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
describe 'when data is a StringIO' do
|
|
319
|
-
let(:data) { StringIO.new File.read(dbf_path) }
|
|
320
|
-
let(:table) { DBF::Table.new data, nil, Encoding::US_ASCII }
|
|
321
|
-
|
|
322
|
-
it 'is an array of all column names' do
|
|
323
|
-
expect(table.column_names).to eq column_names
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
describe '#activerecord_schema_definition' do
|
|
329
|
-
context 'with type N (number)' do
|
|
330
|
-
it 'outputs an integer column' do
|
|
331
|
-
column = DBF::Column.new table, 'ColumnName', 'N', 1, 0
|
|
332
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :integer\n"
|
|
333
|
-
end
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
describe 'with type B (binary)' do
|
|
337
|
-
context 'with Foxpro dbf' do
|
|
338
|
-
it 'outputs a float column' do
|
|
339
|
-
column = DBF::Column.new table, 'ColumnName', 'B', 1, 2
|
|
340
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :binary\n"
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
it 'defines a float colmn if type is (N)umber with more than 0 decimals' do
|
|
346
|
-
column = DBF::Column.new table, 'ColumnName', 'N', 1, 2
|
|
347
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :float\n"
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
it 'defines a date column if type is (D)ate' do
|
|
351
|
-
column = DBF::Column.new table, 'ColumnName', 'D', 8, 0
|
|
352
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :date\n"
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
it 'defines a datetime column if type is (D)ate' do
|
|
356
|
-
column = DBF::Column.new table, 'ColumnName', 'T', 16, 0
|
|
357
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :datetime\n"
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
it 'defines a boolean column if type is (L)ogical' do
|
|
361
|
-
column = DBF::Column.new table, 'ColumnName', 'L', 1, 0
|
|
362
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :boolean\n"
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
it 'defines a text column if type is (M)emo' do
|
|
366
|
-
column = DBF::Column.new table, 'ColumnName', 'M', 1, 0
|
|
367
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :text\n"
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
it 'defines a string column with length for any other data types' do
|
|
371
|
-
column = DBF::Column.new table, 'ColumnName', 'X', 20, 0
|
|
372
|
-
expect(table.activerecord_schema_definition(column)).to eq "\"column_name\", :string, :limit => 20\n"
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
end
|
data/spec/fixtures/cp1251.dbf
DELETED
|
Binary file
|