tableschema 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/README.md +8 -17
- data/lib/profiles/table-schema.json +51 -116
- data/lib/tableschema.rb +0 -2
- data/lib/tableschema/defaults.rb +4 -1
- data/lib/tableschema/field.rb +10 -5
- data/lib/tableschema/helpers.rb +0 -20
- data/lib/tableschema/schema.rb +175 -19
- data/lib/tableschema/table.rb +24 -17
- data/lib/tableschema/types/boolean.rb +11 -3
- data/lib/tableschema/types/integer.rb +4 -0
- data/lib/tableschema/types/number.rb +7 -19
- data/lib/tableschema/version.rb +1 -1
- data/tableschema.gemspec +1 -1
- metadata +4 -6
- data/lib/tableschema/model.rb +0 -96
- data/lib/tableschema/validate.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af9d2ac90e521ace72b172703da49b220a957978
|
4
|
+
data.tar.gz: 95a2e84830de62dbe00e0b4a107222b60d48bdd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9de112e6d3f5bc137fde4fd9e972ca2ff97a6b818e3a66e184bc3d0868c9e77e71221ad76bbad56a2014753a24bef71705607063b08a96afa730b6dbd4114d0
|
7
|
+
data.tar.gz: 5caf4a5bde09a437ccf4d7a6cb91ae44fc07395366179ac682b6ba9b0a90dc966a5d5c51f2f80b70ae0911250aa5244e59b0b77e9df8821901929663807f631b
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -46,11 +46,11 @@ The gem `jsontableschema` is no longer maintained. Here are the steps to transit
|
|
46
46
|
2. Replace module name `JsonTableSchema` with module name `TableSchema`. For example:
|
47
47
|
|
48
48
|
```ruby
|
49
|
-
JsonTableSchema::Table.
|
49
|
+
JsonTableSchema::Table.new(source, schema)
|
50
50
|
```
|
51
51
|
with
|
52
52
|
```ruby
|
53
|
-
TableSchema::Table.
|
53
|
+
TableSchema::Table.new(source, schema)
|
54
54
|
```
|
55
55
|
|
56
56
|
## Usage
|
@@ -91,9 +91,9 @@ table.read
|
|
91
91
|
```
|
92
92
|
|
93
93
|
Both `iter` and `read` take the optional parameters:
|
94
|
-
- `row_limit`: integer, default `nil` - stop at this many rows
|
95
|
-
- `cast`: boolean, default `true` - cast values for each row
|
96
94
|
- `keyed`: boolean, default: `false` - return the rows as Hashes with headers as keys
|
95
|
+
- `cast`: boolean, default `true` - cast values for each row
|
96
|
+
- `limit`: integer, default `nil` - stop at this many rows
|
97
97
|
|
98
98
|
### Infer a schema
|
99
99
|
|
@@ -102,7 +102,8 @@ If you don't have a schema for a CSV, and want to generate one, you can infer a
|
|
102
102
|
```ruby
|
103
103
|
csv = 'https://github.com/frictionlessdata/tableschema-rb/raw/master/spec/fixtures/simple_data.csv' # Can also be a url or array of arrays
|
104
104
|
|
105
|
-
table = TableSchema::Table.
|
105
|
+
table = TableSchema::Table.new(csv, nil)
|
106
|
+
table.infer()
|
106
107
|
table.schema
|
107
108
|
#=> {:fields=>[{:name=>"id", :title=>"", :description=>"", :type=>"integer", :format=>"default", :constraints=>{}}, {:name=>"title", :title=>"", :description=>"", :type=>"string", :format=>"default", :constraints=>{}}]}
|
108
109
|
```
|
@@ -169,26 +170,16 @@ schema_hash = {
|
|
169
170
|
}
|
170
171
|
schema = TableSchema::Schema.new(schema_hash)
|
171
172
|
|
172
|
-
schema.
|
173
|
+
schema.field_names
|
173
174
|
#=> ["id", "height"]
|
174
|
-
schema.required_headers
|
175
|
-
#=> ["id"]
|
176
175
|
schema.fields
|
177
176
|
#=> [{:name=>"id", :type=>"string", :constraints=>{:required=>true}, :format=>"default"}, {:name=>"height", :type=>"number", :format=>"default", :constraints=>{}}]
|
178
|
-
schema.
|
177
|
+
schema.primary_key
|
179
178
|
#=> ["id"]
|
180
179
|
schema.foreign_keys
|
181
180
|
# => [{:fields=>"state", :reference=>{:resource=>"the-resource", :fields=>"state_id"}}]
|
182
181
|
schema.get_field('id')
|
183
182
|
# => {:name=>"id", :type=>"string", :constraints=>{:required=>true}, :format=>"default"}
|
184
|
-
schema.has_field?('foo')
|
185
|
-
#=> false
|
186
|
-
schema.get_type('id')
|
187
|
-
#=> 'string'
|
188
|
-
schema.get_fields_by_type('string')
|
189
|
-
# => [{:name=>"id", :type=>"string", :constraints=>{:required=>true}, :format=>"default"}, {:name=>"state", :type=>"string", :format=>"default", :constraints=>{}}]
|
190
|
-
schema.get_constraints('id')
|
191
|
-
# => {:required=>true}
|
192
183
|
```
|
193
184
|
|
194
185
|
#### Cast row
|
@@ -24,13 +24,8 @@
|
|
24
24
|
"properties": {
|
25
25
|
"name": {
|
26
26
|
"title": "Name",
|
27
|
-
"description": "
|
28
|
-
"type": "string"
|
29
|
-
"pattern": "^([-a-z0-9._/])+$",
|
30
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
31
|
-
"examples": [
|
32
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
33
|
-
]
|
27
|
+
"description": "A name for this field.",
|
28
|
+
"type": "string"
|
34
29
|
},
|
35
30
|
"title": {
|
36
31
|
"title": "Title",
|
@@ -125,13 +120,8 @@
|
|
125
120
|
"properties": {
|
126
121
|
"name": {
|
127
122
|
"title": "Name",
|
128
|
-
"description": "
|
129
|
-
"type": "string"
|
130
|
-
"pattern": "^([-a-z0-9._/])+$",
|
131
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
132
|
-
"examples": [
|
133
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
134
|
-
]
|
123
|
+
"description": "A name for this field.",
|
124
|
+
"type": "string"
|
135
125
|
},
|
136
126
|
"title": {
|
137
127
|
"title": "Title",
|
@@ -162,6 +152,12 @@
|
|
162
152
|
],
|
163
153
|
"default": "default"
|
164
154
|
},
|
155
|
+
"bareNumber": {
|
156
|
+
"type": "boolean",
|
157
|
+
"title": "bareNumber",
|
158
|
+
"description": "a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `€95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.",
|
159
|
+
"default": true
|
160
|
+
},
|
165
161
|
"decimalChar": {
|
166
162
|
"type": "string",
|
167
163
|
"description": "A string whose value is used to represent a decimal point within the number. The default value is `.`."
|
@@ -170,10 +166,6 @@
|
|
170
166
|
"type": "string",
|
171
167
|
"description": "A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'."
|
172
168
|
},
|
173
|
-
"currency": {
|
174
|
-
"type": "string",
|
175
|
-
"description": "A number that may include additional currency symbols."
|
176
|
-
},
|
177
169
|
"constraints": {
|
178
170
|
"title": "Constraints",
|
179
171
|
"description": "The following constraints are supported for `number` fields.",
|
@@ -257,13 +249,8 @@
|
|
257
249
|
"properties": {
|
258
250
|
"name": {
|
259
251
|
"title": "Name",
|
260
|
-
"description": "
|
261
|
-
"type": "string"
|
262
|
-
"pattern": "^([-a-z0-9._/])+$",
|
263
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
264
|
-
"examples": [
|
265
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
266
|
-
]
|
252
|
+
"description": "A name for this field.",
|
253
|
+
"type": "string"
|
267
254
|
},
|
268
255
|
"title": {
|
269
256
|
"title": "Title",
|
@@ -294,6 +281,12 @@
|
|
294
281
|
],
|
295
282
|
"default": "default"
|
296
283
|
},
|
284
|
+
"bareNumber": {
|
285
|
+
"type": "boolean",
|
286
|
+
"title": "bareNumber",
|
287
|
+
"description": "a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `€95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.",
|
288
|
+
"default": true
|
289
|
+
},
|
297
290
|
"constraints": {
|
298
291
|
"title": "Constraints",
|
299
292
|
"description": "The following constraints are supported for `integer` fields.",
|
@@ -375,13 +368,8 @@
|
|
375
368
|
"properties": {
|
376
369
|
"name": {
|
377
370
|
"title": "Name",
|
378
|
-
"description": "
|
379
|
-
"type": "string"
|
380
|
-
"pattern": "^([-a-z0-9._/])+$",
|
381
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
382
|
-
"examples": [
|
383
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
384
|
-
]
|
371
|
+
"description": "A name for this field.",
|
372
|
+
"type": "string"
|
385
373
|
},
|
386
374
|
"title": {
|
387
375
|
"title": "Title",
|
@@ -462,13 +450,8 @@
|
|
462
450
|
"properties": {
|
463
451
|
"name": {
|
464
452
|
"title": "Name",
|
465
|
-
"description": "
|
466
|
-
"type": "string"
|
467
|
-
"pattern": "^([-a-z0-9._/])+$",
|
468
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
469
|
-
"examples": [
|
470
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
471
|
-
]
|
453
|
+
"description": "A name for this field.",
|
454
|
+
"type": "string"
|
472
455
|
},
|
473
456
|
"title": {
|
474
457
|
"title": "Title",
|
@@ -548,13 +531,8 @@
|
|
548
531
|
"properties": {
|
549
532
|
"name": {
|
550
533
|
"title": "Name",
|
551
|
-
"description": "
|
552
|
-
"type": "string"
|
553
|
-
"pattern": "^([-a-z0-9._/])+$",
|
554
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
555
|
-
"examples": [
|
556
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
557
|
-
]
|
534
|
+
"description": "A name for this field.",
|
535
|
+
"type": "string"
|
558
536
|
},
|
559
537
|
"title": {
|
560
538
|
"title": "Title",
|
@@ -634,13 +612,8 @@
|
|
634
612
|
"properties": {
|
635
613
|
"name": {
|
636
614
|
"title": "Name",
|
637
|
-
"description": "
|
638
|
-
"type": "string"
|
639
|
-
"pattern": "^([-a-z0-9._/])+$",
|
640
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
641
|
-
"examples": [
|
642
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
643
|
-
]
|
615
|
+
"description": "A name for this field.",
|
616
|
+
"type": "string"
|
644
617
|
},
|
645
618
|
"title": {
|
646
619
|
"title": "Title",
|
@@ -748,13 +721,8 @@
|
|
748
721
|
"properties": {
|
749
722
|
"name": {
|
750
723
|
"title": "Name",
|
751
|
-
"description": "
|
752
|
-
"type": "string"
|
753
|
-
"pattern": "^([-a-z0-9._/])+$",
|
754
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
755
|
-
"examples": [
|
756
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
757
|
-
]
|
724
|
+
"description": "A name for this field.",
|
725
|
+
"type": "string"
|
758
726
|
},
|
759
727
|
"title": {
|
760
728
|
"title": "Title",
|
@@ -841,13 +809,8 @@
|
|
841
809
|
"properties": {
|
842
810
|
"name": {
|
843
811
|
"title": "Name",
|
844
|
-
"description": "
|
845
|
-
"type": "string"
|
846
|
-
"pattern": "^([-a-z0-9._/])+$",
|
847
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
848
|
-
"examples": [
|
849
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
850
|
-
]
|
812
|
+
"description": "A name for this field.",
|
813
|
+
"type": "string"
|
851
814
|
},
|
852
815
|
"title": {
|
853
816
|
"title": "Title",
|
@@ -937,13 +900,8 @@
|
|
937
900
|
"properties": {
|
938
901
|
"name": {
|
939
902
|
"title": "Name",
|
940
|
-
"description": "
|
941
|
-
"type": "string"
|
942
|
-
"pattern": "^([-a-z0-9._/])+$",
|
943
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
944
|
-
"examples": [
|
945
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
946
|
-
]
|
903
|
+
"description": "A name for this field.",
|
904
|
+
"type": "string"
|
947
905
|
},
|
948
906
|
"title": {
|
949
907
|
"title": "Title",
|
@@ -1038,13 +996,8 @@
|
|
1038
996
|
"properties": {
|
1039
997
|
"name": {
|
1040
998
|
"title": "Name",
|
1041
|
-
"description": "
|
1042
|
-
"type": "string"
|
1043
|
-
"pattern": "^([-a-z0-9._/])+$",
|
1044
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
1045
|
-
"examples": [
|
1046
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
1047
|
-
]
|
999
|
+
"description": "A name for this field.",
|
1000
|
+
"type": "string"
|
1048
1001
|
},
|
1049
1002
|
"title": {
|
1050
1003
|
"title": "Title",
|
@@ -1146,13 +1099,8 @@
|
|
1146
1099
|
"properties": {
|
1147
1100
|
"name": {
|
1148
1101
|
"title": "Name",
|
1149
|
-
"description": "
|
1150
|
-
"type": "string"
|
1151
|
-
"pattern": "^([-a-z0-9._/])+$",
|
1152
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
1153
|
-
"examples": [
|
1154
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
1155
|
-
]
|
1102
|
+
"description": "A name for this field.",
|
1103
|
+
"type": "string"
|
1156
1104
|
},
|
1157
1105
|
"title": {
|
1158
1106
|
"title": "Title",
|
@@ -1250,13 +1198,8 @@
|
|
1250
1198
|
"properties": {
|
1251
1199
|
"name": {
|
1252
1200
|
"title": "Name",
|
1253
|
-
"description": "
|
1254
|
-
"type": "string"
|
1255
|
-
"pattern": "^([-a-z0-9._/])+$",
|
1256
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
1257
|
-
"examples": [
|
1258
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
1259
|
-
]
|
1201
|
+
"description": "A name for this field.",
|
1202
|
+
"type": "string"
|
1260
1203
|
},
|
1261
1204
|
"title": {
|
1262
1205
|
"title": "Title",
|
@@ -1352,13 +1295,8 @@
|
|
1352
1295
|
"properties": {
|
1353
1296
|
"name": {
|
1354
1297
|
"title": "Name",
|
1355
|
-
"description": "
|
1356
|
-
"type": "string"
|
1357
|
-
"pattern": "^([-a-z0-9._/])+$",
|
1358
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
1359
|
-
"examples": [
|
1360
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
1361
|
-
]
|
1298
|
+
"description": "A name for this field.",
|
1299
|
+
"type": "string"
|
1362
1300
|
},
|
1363
1301
|
"title": {
|
1364
1302
|
"title": "Title",
|
@@ -1439,13 +1377,8 @@
|
|
1439
1377
|
"properties": {
|
1440
1378
|
"name": {
|
1441
1379
|
"title": "Name",
|
1442
|
-
"description": "
|
1443
|
-
"type": "string"
|
1444
|
-
"pattern": "^([-a-z0-9._/])+$",
|
1445
|
-
"context": "This is ideally a url-usable and human-readable name. Name `SHOULD` be invariant, meaning it `SHOULD NOT` change when its parent descriptor is updated.",
|
1446
|
-
"examples": [
|
1447
|
-
"{\n \"name\": \"my-nice-name\"\n}\n"
|
1448
|
-
]
|
1380
|
+
"description": "A name for this field.",
|
1381
|
+
"type": "string"
|
1449
1382
|
},
|
1450
1383
|
"title": {
|
1451
1384
|
"title": "Title",
|
@@ -1539,9 +1472,9 @@
|
|
1539
1472
|
"fields",
|
1540
1473
|
"reference"
|
1541
1474
|
],
|
1542
|
-
"
|
1543
|
-
|
1544
|
-
{
|
1475
|
+
"oneOf": [
|
1476
|
+
{
|
1477
|
+
"properties": {
|
1545
1478
|
"fields": {
|
1546
1479
|
"type": "array",
|
1547
1480
|
"items": {
|
@@ -1572,8 +1505,10 @@
|
|
1572
1505
|
}
|
1573
1506
|
}
|
1574
1507
|
}
|
1575
|
-
}
|
1576
|
-
|
1508
|
+
}
|
1509
|
+
},
|
1510
|
+
{
|
1511
|
+
"properties": {
|
1577
1512
|
"fields": {
|
1578
1513
|
"type": "string",
|
1579
1514
|
"description": "Fields that make up the primary key."
|
@@ -1595,8 +1530,8 @@
|
|
1595
1530
|
}
|
1596
1531
|
}
|
1597
1532
|
}
|
1598
|
-
|
1599
|
-
|
1533
|
+
}
|
1534
|
+
]
|
1600
1535
|
},
|
1601
1536
|
"examples": [
|
1602
1537
|
"{\n \"foreignKeys\": [\n {\n \"fields\": \"state\",\n \"reference\": {\n \"resource\": \"the-resource\",\n \"fields\": \"state_id\"\n }\n }\n ]\n}\n",
|
data/lib/tableschema.rb
CHANGED
@@ -32,8 +32,6 @@ require "tableschema/types/duration"
|
|
32
32
|
require "tableschema/defaults"
|
33
33
|
|
34
34
|
require "tableschema/field"
|
35
|
-
require "tableschema/validate"
|
36
|
-
require "tableschema/model"
|
37
35
|
require "tableschema/schema"
|
38
36
|
require "tableschema/table"
|
39
37
|
require "tableschema/infer"
|
data/lib/tableschema/defaults.rb
CHANGED
data/lib/tableschema/field.rb
CHANGED
@@ -4,7 +4,9 @@ module TableSchema
|
|
4
4
|
class Field < Hash
|
5
5
|
include TableSchema::Helpers
|
6
6
|
|
7
|
-
|
7
|
+
# Public
|
8
|
+
|
9
|
+
attr_reader :name, :type, :format, :required, :constraints
|
8
10
|
|
9
11
|
def initialize(descriptor, missing_values=nil)
|
10
12
|
self.merge! deep_symbolize_keys(descriptor)
|
@@ -12,6 +14,7 @@ module TableSchema
|
|
12
14
|
@type = self[:type] = self.fetch(:type, TableSchema::DEFAULTS[:type])
|
13
15
|
@format = self[:format] = self.fetch(:format, TableSchema::DEFAULTS[:format])
|
14
16
|
@constraints = self[:constraints] = self.fetch(:constraints, {})
|
17
|
+
@required = @constraints.fetch(:required, false)
|
15
18
|
@missing_values = missing_values || default_missing_values
|
16
19
|
end
|
17
20
|
|
@@ -19,15 +22,15 @@ module TableSchema
|
|
19
22
|
self.to_h
|
20
23
|
end
|
21
24
|
|
22
|
-
def cast_value(value,
|
25
|
+
def cast_value(value, constraints: true)
|
23
26
|
cast_value = cast_type(value)
|
24
|
-
return cast_value if
|
27
|
+
return cast_value if constraints == false
|
25
28
|
TableSchema::Constraints.new(self, cast_value).validate!
|
26
29
|
cast_value
|
27
30
|
end
|
28
31
|
|
29
|
-
def test_value(value,
|
30
|
-
cast_value(value,
|
32
|
+
def test_value(value, constraints: true)
|
33
|
+
cast_value(value, constraints: constraints)
|
31
34
|
true
|
32
35
|
rescue TableSchema::Exception
|
33
36
|
false
|
@@ -41,6 +44,8 @@ module TableSchema
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
47
|
+
# Private
|
48
|
+
|
44
49
|
private
|
45
50
|
|
46
51
|
def default_missing_values
|
data/lib/tableschema/helpers.rb
CHANGED
@@ -16,26 +16,6 @@ module TableSchema
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def convert_to_boolean(value)
|
20
|
-
if value.is_a?(Boolean)
|
21
|
-
return value
|
22
|
-
elsif true_values.include?(value.to_s.downcase)
|
23
|
-
true
|
24
|
-
elsif false_values.include?(value.to_s.downcase)
|
25
|
-
false
|
26
|
-
else
|
27
|
-
nil
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def true_values
|
32
|
-
['yes', 'y', 'true', 't', '1']
|
33
|
-
end
|
34
|
-
|
35
|
-
def false_values
|
36
|
-
['no', 'n', 'false', 'f', '0']
|
37
|
-
end
|
38
|
-
|
39
19
|
def get_class_for_type(type)
|
40
20
|
"TableSchema::Types::#{type_class_lookup[type.to_sym] || 'String'}"
|
41
21
|
end
|
data/lib/tableschema/schema.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
require 'tableschema/defaults'
|
2
|
+
|
1
3
|
module TableSchema
|
2
4
|
class Schema < Hash
|
3
|
-
include TableSchema::Validate
|
4
|
-
include TableSchema::Model
|
5
5
|
include TableSchema::Helpers
|
6
6
|
|
7
|
+
# Public
|
8
|
+
|
7
9
|
attr_reader :errors
|
8
10
|
|
9
|
-
def initialize(descriptor,
|
11
|
+
def initialize(descriptor, strict: false, case_insensitive_headers: false)
|
10
12
|
self.merge! deep_symbolize_keys(parse_schema(descriptor))
|
11
13
|
@case_insensitive_headers = case_insensitive_headers
|
12
14
|
@strict = strict
|
@@ -17,26 +19,60 @@ module TableSchema
|
|
17
19
|
self
|
18
20
|
end
|
19
21
|
|
22
|
+
def validate
|
23
|
+
@errors = Set.new(JSON::Validator.fully_validate(@profile, self))
|
24
|
+
check_primary_key
|
25
|
+
check_foreign_keys
|
26
|
+
@errors.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate!
|
30
|
+
validate
|
31
|
+
raise SchemaException.new(@errors.first) unless @errors.empty?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
20
35
|
def descriptor
|
21
36
|
self.to_h
|
22
37
|
end
|
23
38
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
def primary_key
|
40
|
+
[self[:primaryKey]].flatten.reject { |k| k.nil? }
|
41
|
+
end
|
42
|
+
|
43
|
+
def foreign_keys
|
44
|
+
self[:foreignKeys] || []
|
45
|
+
end
|
46
|
+
|
47
|
+
def fields
|
48
|
+
self[:fields]
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_names
|
52
|
+
fields.map { |f| transform(f[:name]) }
|
53
|
+
rescue NoMethodError
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_field(field_name)
|
58
|
+
fields.find { |f| f[:name] == field_name }
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_field(descriptor)
|
62
|
+
self[:fields].push(descriptor)
|
63
|
+
validate!
|
64
|
+
descriptor
|
65
|
+
rescue TableSchema::SchemaException => e
|
66
|
+
self[:fields].pop
|
67
|
+
raise e if @strict
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def remove_field(field_name)
|
72
|
+
field = get_field(field_name)
|
73
|
+
self[:fields].reject!{ |f| f.name == field_name }
|
74
|
+
validate
|
75
|
+
field
|
40
76
|
end
|
41
77
|
|
42
78
|
def cast_row(row, fail_fast: true)
|
@@ -66,5 +102,125 @@ module TableSchema
|
|
66
102
|
true
|
67
103
|
end
|
68
104
|
|
105
|
+
# Deprecated
|
106
|
+
|
107
|
+
alias :headers :field_names
|
108
|
+
|
109
|
+
def missing_values
|
110
|
+
self.fetch(:missingValues, TableSchema::DEFAULTS[:missing_values])
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_type(field_name)
|
114
|
+
get_field(field_name)[:type]
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_constraints(field_name)
|
118
|
+
get_field(field_name)[:constraints] || {}
|
119
|
+
end
|
120
|
+
|
121
|
+
def required_headers
|
122
|
+
fields.select { |f| f.fetch(:constraints, {}).fetch(:required, nil).to_s == 'true' }
|
123
|
+
.map { |f| transform(f[:name]) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def unique_headers
|
127
|
+
fields.select { |f| f.fetch(:constraints, {}).fetch(:unique, nil).to_s == 'true' }
|
128
|
+
.map { |f| transform(f[:name]) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def has_field?(field_name)
|
132
|
+
get_field(field_name) != nil
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_fields_by_type(type)
|
136
|
+
fields.select { |f| f[:type] == type }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Private
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def parse_schema(descriptor)
|
144
|
+
if descriptor.class == Hash
|
145
|
+
descriptor
|
146
|
+
elsif descriptor.class == String
|
147
|
+
begin
|
148
|
+
JSON.parse(open(descriptor).read, symbolize_names: true)
|
149
|
+
rescue Errno::ENOENT
|
150
|
+
raise SchemaException.new("File not found at `#{descriptor}`")
|
151
|
+
rescue OpenURI::HTTPError => e
|
152
|
+
raise SchemaException.new("URL `#{descriptor}` returned #{e.message}")
|
153
|
+
rescue JSON::ParserError
|
154
|
+
raise SchemaException.new("File at `#{descriptor}` is not valid JSON")
|
155
|
+
end
|
156
|
+
else
|
157
|
+
raise SchemaException.new("A schema must be a hash, path or URL")
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def transform(name)
|
162
|
+
name.downcase! if @case_insensitive_headers == true
|
163
|
+
name
|
164
|
+
end
|
165
|
+
|
166
|
+
def expand!
|
167
|
+
(self[:fields] || []).each do |f|
|
168
|
+
f[:type] = TableSchema::DEFAULTS[:type] if f[:type] == nil
|
169
|
+
f[:format] = TableSchema::DEFAULTS[:format] if f[:format] == nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def load_fields!
|
174
|
+
self[:fields] = (self[:fields] || []).map { |f| TableSchema::Field.new(f, missing_values) }
|
175
|
+
end
|
176
|
+
|
177
|
+
def load_validator!
|
178
|
+
filepath = File.join(File.dirname(__FILE__), '..', 'profiles', 'table-schema.json')
|
179
|
+
@profile ||= JSON.parse(File.read(filepath), symbolize_names: true)
|
180
|
+
end
|
181
|
+
|
182
|
+
def check_primary_key
|
183
|
+
return if self[:primaryKey].nil?
|
184
|
+
primary_key.each { |pk| check_field_value(pk, 'primaryKey') }
|
185
|
+
end
|
186
|
+
|
187
|
+
def check_foreign_keys
|
188
|
+
return if self[:foreignKeys].nil?
|
189
|
+
self[:foreignKeys].each do |key|
|
190
|
+
if field_type_mismatch?(key)
|
191
|
+
add_error("A TableSchema `foreignKey.fields` value must be the same type as `foreignKey.reference.fields`")
|
192
|
+
end
|
193
|
+
if field_count_mismatch?(key)
|
194
|
+
add_error("A TableSchema `foreignKey.fields` must contain the same number of entries as `foreignKey.reference.fields`")
|
195
|
+
end
|
196
|
+
foreign_key_fields(key).each { |fk| check_field_value(fk, 'foreignKey.fields') }
|
197
|
+
if key.fetch(:reference).fetch(:resource).empty?
|
198
|
+
foreign_key_fields(key.fetch(:reference)).each { |fk| check_field_value(fk, 'foreignKey.reference.fields')}
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def check_field_value(key, type)
|
204
|
+
if headers.select { |f| key == f }.count == 0
|
205
|
+
add_error("The TableSchema #{type} value `#{key}` is not found in any of the schema's field names")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def foreign_key_fields(key)
|
210
|
+
[key.fetch(:fields)].flatten
|
211
|
+
end
|
212
|
+
|
213
|
+
def field_count_mismatch?(key)
|
214
|
+
foreign_key_fields(key).count != foreign_key_fields(key.fetch(:reference)).count
|
215
|
+
end
|
216
|
+
|
217
|
+
def field_type_mismatch?(key)
|
218
|
+
key.fetch(:fields).class.name != key.fetch(:reference).fetch(:fields).class.name
|
219
|
+
end
|
220
|
+
|
221
|
+
def add_error(error)
|
222
|
+
@errors << error
|
223
|
+
end
|
224
|
+
|
69
225
|
end
|
70
226
|
end
|
data/lib/tableschema/table.rb
CHANGED
@@ -1,27 +1,28 @@
|
|
1
1
|
module TableSchema
|
2
2
|
class Table
|
3
3
|
|
4
|
-
|
4
|
+
# Public
|
5
5
|
|
6
|
-
|
7
|
-
TableSchema::Table.new(csv, nil, csv_options)
|
8
|
-
end
|
6
|
+
attr_reader :headers, :schema
|
9
7
|
|
10
8
|
def initialize(csv, descriptor, csv_options: {})
|
11
9
|
@csv_options = csv_options.merge(headers: true)
|
10
|
+
@descriptor = descriptor
|
12
11
|
@csv = parse_csv(csv)
|
13
12
|
@headers = initialize_headers
|
14
|
-
|
15
|
-
|
13
|
+
if !descriptor.nil?
|
14
|
+
@schema = TableSchema::Schema.new(@descriptor)
|
15
|
+
initialize_unique_colums
|
16
|
+
end
|
16
17
|
end
|
17
18
|
|
18
|
-
def iter(
|
19
|
+
def iter(keyed: false, cast: true, limit: nil)
|
19
20
|
unless block_given?
|
20
|
-
return enum_for(:iter,
|
21
|
+
return enum_for(:iter, limit: limit, cast: cast, keyed: keyed)
|
21
22
|
end
|
22
23
|
|
23
24
|
@csv.each_with_index do |row, i|
|
24
|
-
break if
|
25
|
+
break if limit && (limit <= i)
|
25
26
|
if cast == true
|
26
27
|
cast_values = @schema.cast_row(row)
|
27
28
|
row = CSV::Row.new(@headers, cast_values)
|
@@ -38,11 +39,21 @@ module TableSchema
|
|
38
39
|
@csv.rewind
|
39
40
|
end
|
40
41
|
|
41
|
-
def read(
|
42
|
-
iterator = self.iter(
|
42
|
+
def read(keyed: false, cast: true, limit: nil)
|
43
|
+
iterator = self.iter(keyed: keyed, cast: cast, limit: limit)
|
43
44
|
iterator.to_a
|
44
45
|
end
|
45
46
|
|
47
|
+
def infer()
|
48
|
+
if !@schema
|
49
|
+
inferer = TableSchema::Infer.new(@headers, @csv)
|
50
|
+
@schema = inferer.schema
|
51
|
+
initialize_unique_colums
|
52
|
+
@csv.rewind
|
53
|
+
end
|
54
|
+
@schema.descriptor
|
55
|
+
end
|
56
|
+
|
46
57
|
def save(target)
|
47
58
|
CSV.open(target, "wb", @csv_options) do |csv|
|
48
59
|
csv << @headers
|
@@ -51,6 +62,8 @@ module TableSchema
|
|
51
62
|
true
|
52
63
|
end
|
53
64
|
|
65
|
+
# Private
|
66
|
+
|
54
67
|
private
|
55
68
|
|
56
69
|
def parse_csv(csv)
|
@@ -62,12 +75,6 @@ module TableSchema
|
|
62
75
|
array.map { |row| row.to_csv(row_sep: nil) }.join("\r\n")
|
63
76
|
end
|
64
77
|
|
65
|
-
def infer_schema
|
66
|
-
inferer = TableSchema::Infer.new(@headers, @csv)
|
67
|
-
@csv.rewind
|
68
|
-
inferer.schema
|
69
|
-
end
|
70
|
-
|
71
78
|
def initialize_headers
|
72
79
|
headers = @csv.first.to_h.keys
|
73
80
|
@csv.rewind
|
@@ -25,9 +25,17 @@ module TableSchema
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def cast_default(value)
|
28
|
-
|
29
|
-
|
30
|
-
value
|
28
|
+
true_values = @field.fetch(:trueValues, TableSchema::DEFAULTS[:true_values])
|
29
|
+
false_values = @field.fetch(:falseValues, TableSchema::DEFAULTS[:false_values])
|
30
|
+
if [true, false].include?(value)
|
31
|
+
return value
|
32
|
+
elsif true_values.include?(value)
|
33
|
+
return true
|
34
|
+
elsif false_values.include?(value)
|
35
|
+
return false
|
36
|
+
else
|
37
|
+
raise TableSchema::InvalidCast.new("#{value} is not a #{name}")
|
38
|
+
end
|
31
39
|
end
|
32
40
|
|
33
41
|
end
|
@@ -25,6 +25,10 @@ module TableSchema
|
|
25
25
|
if value.is_a?(type)
|
26
26
|
value
|
27
27
|
else
|
28
|
+
bare_number = @field.fetch(:bareNumber, TableSchema::DEFAULTS[:bare_number])
|
29
|
+
if !bare_number
|
30
|
+
value = value.gsub(/((^\D*)|(\D*$))/, '')
|
31
|
+
end
|
28
32
|
Integer(value)
|
29
33
|
end
|
30
34
|
rescue ArgumentError
|
@@ -48,28 +48,16 @@ module TableSchema
|
|
48
48
|
else
|
49
49
|
group_char = @field.fetch(:groupChar, TableSchema::DEFAULTS[:group_char])
|
50
50
|
decimal_char = @field.fetch(:decimalChar, TableSchema::DEFAULTS[:decimal_char])
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
Float(formatted_value)
|
51
|
+
bare_number = @field.fetch(:bareNumber, TableSchema::DEFAULTS[:bare_number])
|
52
|
+
formatted_value = value
|
53
|
+
formatted_value = formatted_value.gsub(group_char, '')
|
54
|
+
formatted_value = formatted_value.gsub(decimal_char, '.')
|
55
|
+
if !bare_number
|
56
|
+
formatted_value = formatted_value.gsub(/((^\D*)|(\D*$))/, '')
|
58
57
|
end
|
58
|
+
Float(formatted_value)
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
62
|
-
def process_percent(value)
|
63
|
-
Float(value.gsub(percent_chars, '')) / 100
|
64
|
-
end
|
65
|
-
|
66
|
-
def process_currency(value)
|
67
|
-
Float(value.gsub(@field[:currency], ''))
|
68
|
-
end
|
69
|
-
|
70
|
-
def percent_chars
|
71
|
-
/%|‰|‱|%|﹪|٪/
|
72
|
-
end
|
73
61
|
end
|
74
62
|
end
|
75
63
|
end
|
data/lib/tableschema/version.rb
CHANGED
data/tableschema.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "coveralls", "~> 0.8.13"
|
27
27
|
spec.add_development_dependency "rubocop", "~> 0.49.1"
|
28
28
|
|
29
|
-
spec.add_dependency "json-schema", "~> 2.
|
29
|
+
spec.add_dependency "json-schema", "~> 2.8.0"
|
30
30
|
spec.add_dependency "uuid", "~> 2.3.8"
|
31
31
|
spec.add_dependency "tod", "~> 2.1.0"
|
32
32
|
spec.add_dependency "activesupport", "~> 5.1.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tableschema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Open Knowledge Foundation
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -114,14 +114,14 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 2.
|
117
|
+
version: 2.8.0
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 2.
|
124
|
+
version: 2.8.0
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: uuid
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -201,7 +201,6 @@ files:
|
|
201
201
|
- lib/tableschema/field.rb
|
202
202
|
- lib/tableschema/helpers.rb
|
203
203
|
- lib/tableschema/infer.rb
|
204
|
-
- lib/tableschema/model.rb
|
205
204
|
- lib/tableschema/schema.rb
|
206
205
|
- lib/tableschema/table.rb
|
207
206
|
- lib/tableschema/types/any.rb
|
@@ -220,7 +219,6 @@ files:
|
|
220
219
|
- lib/tableschema/types/time.rb
|
221
220
|
- lib/tableschema/types/year.rb
|
222
221
|
- lib/tableschema/types/yearmonth.rb
|
223
|
-
- lib/tableschema/validate.rb
|
224
222
|
- lib/tableschema/version.rb
|
225
223
|
- tableschema.gemspec
|
226
224
|
homepage: https://github.com/frictionlessdata/tableschema-rb
|
data/lib/tableschema/model.rb
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
require 'tableschema/defaults'
|
2
|
-
|
3
|
-
module TableSchema
|
4
|
-
module Model
|
5
|
-
|
6
|
-
def headers
|
7
|
-
fields.map { |f| transform(f[:name]) }
|
8
|
-
rescue NoMethodError
|
9
|
-
[]
|
10
|
-
end
|
11
|
-
|
12
|
-
alias :field_names :headers
|
13
|
-
|
14
|
-
def fields
|
15
|
-
self[:fields]
|
16
|
-
end
|
17
|
-
|
18
|
-
def primary_keys
|
19
|
-
[self[:primaryKey]].flatten.reject { |k| k.nil? }
|
20
|
-
end
|
21
|
-
|
22
|
-
def foreign_keys
|
23
|
-
self[:foreignKeys] || []
|
24
|
-
end
|
25
|
-
|
26
|
-
def missing_values
|
27
|
-
self.fetch(:missingValues, TableSchema::DEFAULTS[:missing_values])
|
28
|
-
end
|
29
|
-
|
30
|
-
def get_type(field_name)
|
31
|
-
get_field(field_name)[:type]
|
32
|
-
end
|
33
|
-
|
34
|
-
def get_constraints(field_name)
|
35
|
-
get_field(field_name)[:constraints] || {}
|
36
|
-
end
|
37
|
-
|
38
|
-
def required_headers
|
39
|
-
fields.select { |f| f.fetch(:constraints, {}).fetch(:required, nil).to_s == 'true' }
|
40
|
-
.map { |f| transform(f[:name]) }
|
41
|
-
end
|
42
|
-
|
43
|
-
def unique_headers
|
44
|
-
fields.select { |f| f.fetch(:constraints, {}).fetch(:unique, nil).to_s == 'true' }
|
45
|
-
.map { |f| transform(f[:name]) }
|
46
|
-
end
|
47
|
-
|
48
|
-
def has_field?(field_name)
|
49
|
-
get_field(field_name) != nil
|
50
|
-
end
|
51
|
-
|
52
|
-
def get_field(field_name)
|
53
|
-
fields.find { |f| f[:name] == field_name }
|
54
|
-
end
|
55
|
-
|
56
|
-
def get_fields_by_type(type)
|
57
|
-
fields.select { |f| f[:type] == type }
|
58
|
-
end
|
59
|
-
|
60
|
-
def add_field(descriptor)
|
61
|
-
self[:fields].push(descriptor)
|
62
|
-
validate!
|
63
|
-
descriptor
|
64
|
-
rescue TableSchema::SchemaException => e
|
65
|
-
self[:fields].pop
|
66
|
-
raise e if @strict
|
67
|
-
nil
|
68
|
-
end
|
69
|
-
|
70
|
-
def remove_field(field_name)
|
71
|
-
field = get_field(field_name)
|
72
|
-
self[:fields].reject!{ |f| f.name == field_name }
|
73
|
-
validate
|
74
|
-
field
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def transform(name)
|
80
|
-
name.downcase! if @case_insensitive_headers == true
|
81
|
-
name
|
82
|
-
end
|
83
|
-
|
84
|
-
def expand!
|
85
|
-
(self[:fields] || []).each do |f|
|
86
|
-
f[:type] = TableSchema::DEFAULTS[:type] if f[:type] == nil
|
87
|
-
f[:format] = TableSchema::DEFAULTS[:format] if f[:format] == nil
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def load_fields!
|
92
|
-
self[:fields] = (self[:fields] || []).map { |f| TableSchema::Field.new(f, missing_values) }
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
end
|
data/lib/tableschema/validate.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
module TableSchema
|
2
|
-
module Validate
|
3
|
-
|
4
|
-
attr_reader :errors
|
5
|
-
|
6
|
-
def load_validator!
|
7
|
-
filepath = File.join(File.dirname(__FILE__), '..', 'profiles', 'table-schema.json')
|
8
|
-
@profile ||= JSON.parse(File.read(filepath), symbolize_names: true)
|
9
|
-
end
|
10
|
-
|
11
|
-
def validate
|
12
|
-
@errors = Set.new(JSON::Validator.fully_validate(@profile, self))
|
13
|
-
check_primary_keys
|
14
|
-
check_foreign_keys
|
15
|
-
@errors.empty?
|
16
|
-
end
|
17
|
-
|
18
|
-
def validate!
|
19
|
-
validate
|
20
|
-
raise SchemaException.new(@errors.first) unless @errors.empty?
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def check_primary_keys
|
27
|
-
return if self[:primaryKey].nil?
|
28
|
-
primary_keys.each { |pk| check_field_value(pk, 'primaryKey') }
|
29
|
-
end
|
30
|
-
|
31
|
-
def check_foreign_keys
|
32
|
-
return if self[:foreignKeys].nil?
|
33
|
-
self[:foreignKeys].each do |key|
|
34
|
-
if field_type_mismatch?(key)
|
35
|
-
add_error("A TableSchema `foreignKey.fields` value must be the same type as `foreignKey.reference.fields`")
|
36
|
-
end
|
37
|
-
if field_count_mismatch?(key)
|
38
|
-
add_error("A TableSchema `foreignKey.fields` must contain the same number of entries as `foreignKey.reference.fields`")
|
39
|
-
end
|
40
|
-
foreign_key_fields(key).each { |fk| check_field_value(fk, 'foreignKey.fields') }
|
41
|
-
if key.fetch(:reference).fetch(:resource).empty?
|
42
|
-
foreign_key_fields(key.fetch(:reference)).each { |fk| check_field_value(fk, 'foreignKey.reference.fields')}
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def check_field_value(key, type)
|
48
|
-
if headers.select { |f| key == f }.count == 0
|
49
|
-
add_error("The TableSchema #{type} value `#{key}` is not found in any of the schema's field names")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def foreign_key_fields(key)
|
54
|
-
[key.fetch(:fields)].flatten
|
55
|
-
end
|
56
|
-
|
57
|
-
def field_count_mismatch?(key)
|
58
|
-
foreign_key_fields(key).count != foreign_key_fields(key.fetch(:reference)).count
|
59
|
-
end
|
60
|
-
|
61
|
-
def field_type_mismatch?(key)
|
62
|
-
key.fetch(:fields).class.name != key.fetch(:reference).fetch(:fields).class.name
|
63
|
-
end
|
64
|
-
|
65
|
-
def add_error(error)
|
66
|
-
@errors << error
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|