expressir 2.1.29 → 2.1.31
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/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +209 -55
- data/Gemfile +2 -1
- data/README.adoc +650 -83
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/changes/schema_change.rb +32 -22
- data/lib/expressir/changes/{edition_change.rb → version_change.rb} +3 -3
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/changes_import_eengine.rb +2 -2
- data/lib/expressir/commands/changes_validate.rb +1 -1
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +16 -3
- metadata +115 -5
- data/docs/benchmarking.adoc +0 -107
- data/docs/liquid_drops.adoc +0 -1547
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Working with Multiple Schemas
|
|
3
|
+
nav_order: 3
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
== Working with Multiple Schemas
|
|
7
|
+
|
|
8
|
+
=== Prerequisites
|
|
9
|
+
|
|
10
|
+
Before starting this tutorial, ensure you have:
|
|
11
|
+
|
|
12
|
+
* Completed link:parsing-your-first-schema.html[Parsing Your First Schema]
|
|
13
|
+
* Understanding of EXPRESS interfaces (USE FROM, REFERENCE FROM)
|
|
14
|
+
* Basic knowledge of schema dependencies
|
|
15
|
+
* Expressir installed and working
|
|
16
|
+
|
|
17
|
+
=== Learning Objectives
|
|
18
|
+
|
|
19
|
+
By the end of this tutorial, you will be able to:
|
|
20
|
+
|
|
21
|
+
* Parse multiple EXPRESS schema files together
|
|
22
|
+
* Understand and work with schema dependencies
|
|
23
|
+
* Use interfaces to share entities and types
|
|
24
|
+
* Resolve cross-schema references
|
|
25
|
+
* Manage schema collections effectively
|
|
26
|
+
* Handle circular dependencies
|
|
27
|
+
|
|
28
|
+
=== What You'll Build
|
|
29
|
+
|
|
30
|
+
You'll create a multi-schema EXPRESS application modeling a product catalog system with base definitions, product schemas, and an application schema that uses them all.
|
|
31
|
+
|
|
32
|
+
=== Step 1: Understanding Schema Dependencies
|
|
33
|
+
|
|
34
|
+
EXPRESS schemas often depend on each other through interfaces.
|
|
35
|
+
|
|
36
|
+
==== Interface Types
|
|
37
|
+
|
|
38
|
+
**USE FROM**::
|
|
39
|
+
Imports all declarations from another schema
|
|
40
|
+
+
|
|
41
|
+
[source,express]
|
|
42
|
+
----
|
|
43
|
+
USE FROM base_schema;
|
|
44
|
+
----
|
|
45
|
+
|
|
46
|
+
**REFERENCE FROM**::
|
|
47
|
+
Imports specific declarations from another schema
|
|
48
|
+
+
|
|
49
|
+
[source,express]
|
|
50
|
+
----
|
|
51
|
+
REFERENCE FROM base_schema (person, organization);
|
|
52
|
+
----
|
|
53
|
+
|
|
54
|
+
==== Why Multiple Schemas?
|
|
55
|
+
|
|
56
|
+
* **Modularity**: Separate concerns into logical units
|
|
57
|
+
* **Reusability**: Share common definitions across projects
|
|
58
|
+
* **Maintainability**: Easier to update and test smaller schemas
|
|
59
|
+
* **Standards compliance**: ISO standards use modular schemas
|
|
60
|
+
|
|
61
|
+
=== Step 2: Create Base Schemas
|
|
62
|
+
|
|
63
|
+
Let's create a foundation with reusable types.
|
|
64
|
+
|
|
65
|
+
==== Create `base_types.exp`
|
|
66
|
+
|
|
67
|
+
[source,express]
|
|
68
|
+
----
|
|
69
|
+
SCHEMA base_types;
|
|
70
|
+
|
|
71
|
+
TYPE identifier = STRING;
|
|
72
|
+
END_TYPE;
|
|
73
|
+
|
|
74
|
+
TYPE label = STRING;
|
|
75
|
+
END_TYPE;
|
|
76
|
+
|
|
77
|
+
TYPE text = STRING;
|
|
78
|
+
END_TYPE;
|
|
79
|
+
|
|
80
|
+
TYPE positive_integer = INTEGER;
|
|
81
|
+
WHERE
|
|
82
|
+
WR1: SELF > 0;
|
|
83
|
+
END_TYPE;
|
|
84
|
+
|
|
85
|
+
TYPE date_string = STRING;
|
|
86
|
+
END_TYPE;
|
|
87
|
+
|
|
88
|
+
END_SCHEMA;
|
|
89
|
+
----
|
|
90
|
+
|
|
91
|
+
==== Create `base_entities.exp`
|
|
92
|
+
|
|
93
|
+
[source,express]
|
|
94
|
+
----
|
|
95
|
+
SCHEMA base_entities;
|
|
96
|
+
|
|
97
|
+
USE FROM base_types;
|
|
98
|
+
|
|
99
|
+
ENTITY person;
|
|
100
|
+
name : label;
|
|
101
|
+
email : OPTIONAL text;
|
|
102
|
+
END_ENTITY;
|
|
103
|
+
|
|
104
|
+
ENTITY organization;
|
|
105
|
+
org_name : label;
|
|
106
|
+
employees : SET [0:?] OF person;
|
|
107
|
+
END_ENTITY;
|
|
108
|
+
|
|
109
|
+
ENTITY address;
|
|
110
|
+
street : text;
|
|
111
|
+
city : label;
|
|
112
|
+
country : label;
|
|
113
|
+
END_ENTITY;
|
|
114
|
+
|
|
115
|
+
END_SCHEMA;
|
|
116
|
+
----
|
|
117
|
+
|
|
118
|
+
=== Step 3: Create Product Schema
|
|
119
|
+
|
|
120
|
+
Now create a schema that uses the base schemas.
|
|
121
|
+
|
|
122
|
+
==== Create `product_schema.exp`
|
|
123
|
+
|
|
124
|
+
[source,express]
|
|
125
|
+
----
|
|
126
|
+
SCHEMA product_schema;
|
|
127
|
+
|
|
128
|
+
REFERENCE FROM base_types (identifier, label, text, positive_integer);
|
|
129
|
+
REFERENCE FROM base_entities (person, organization);
|
|
130
|
+
|
|
131
|
+
ENTITY product;
|
|
132
|
+
id : identifier;
|
|
133
|
+
name : label;
|
|
134
|
+
description : OPTIONAL text;
|
|
135
|
+
price : REAL;
|
|
136
|
+
quantity : positive_integer;
|
|
137
|
+
manufacturer : organization;
|
|
138
|
+
END_ENTITY;
|
|
139
|
+
|
|
140
|
+
ENTITY product_category;
|
|
141
|
+
category_name : label;
|
|
142
|
+
products : SET [0:?] OF product;
|
|
143
|
+
END_ENTITY;
|
|
144
|
+
|
|
145
|
+
TYPE product_list = LIST [1:?] OF product;
|
|
146
|
+
END_TYPE;
|
|
147
|
+
|
|
148
|
+
END_SCHEMA;
|
|
149
|
+
----
|
|
150
|
+
|
|
151
|
+
=== Step 4: Create Application Schema
|
|
152
|
+
|
|
153
|
+
Finally, create an application schema that ties everything together.
|
|
154
|
+
|
|
155
|
+
==== Create `catalog_application.exp`
|
|
156
|
+
|
|
157
|
+
[source,express]
|
|
158
|
+
----
|
|
159
|
+
SCHEMA catalog_application;
|
|
160
|
+
|
|
161
|
+
USE FROM product_schema;
|
|
162
|
+
REFERENCE FROM base_entities (address);
|
|
163
|
+
|
|
164
|
+
ENTITY catalog;
|
|
165
|
+
catalog_name : label;
|
|
166
|
+
categories : LIST [1:?] OF product_category;
|
|
167
|
+
contact : person;
|
|
168
|
+
location : address;
|
|
169
|
+
END_ENTITY;
|
|
170
|
+
|
|
171
|
+
ENTITY order_item;
|
|
172
|
+
product_ref : product;
|
|
173
|
+
quantity : positive_integer;
|
|
174
|
+
END_ENTITY;
|
|
175
|
+
|
|
176
|
+
ENTITY customer_order;
|
|
177
|
+
order_id : identifier;
|
|
178
|
+
customer : person;
|
|
179
|
+
items : LIST [1:?] OF order_item;
|
|
180
|
+
total : REAL;
|
|
181
|
+
END_ENTITY;
|
|
182
|
+
|
|
183
|
+
END_SCHEMA;
|
|
184
|
+
----
|
|
185
|
+
|
|
186
|
+
=== Step 5: Parse Multiple Files with CLI
|
|
187
|
+
|
|
188
|
+
Use the CLI to parse all schemas together.
|
|
189
|
+
|
|
190
|
+
[source,bash]
|
|
191
|
+
----
|
|
192
|
+
# Format all schemas
|
|
193
|
+
expressir format base_types.exp base_entities.exp product_schema.exp catalog_application.exp
|
|
194
|
+
|
|
195
|
+
# Validate all schemas
|
|
196
|
+
expressir validate base_types.exp base_entities.exp product_schema.exp catalog_application.exp
|
|
197
|
+
----
|
|
198
|
+
|
|
199
|
+
**Expected output**:
|
|
200
|
+
[source]
|
|
201
|
+
----
|
|
202
|
+
Validation passed for all EXPRESS schemas.
|
|
203
|
+
----
|
|
204
|
+
|
|
205
|
+
=== Step 6: Parse Multiple Files with Ruby API
|
|
206
|
+
|
|
207
|
+
Now let's parse programmatically.
|
|
208
|
+
|
|
209
|
+
==== Basic Multi-File Parsing
|
|
210
|
+
|
|
211
|
+
Create `parse_multiple.rb`:
|
|
212
|
+
|
|
213
|
+
[source,ruby]
|
|
214
|
+
----
|
|
215
|
+
require 'expressir'
|
|
216
|
+
|
|
217
|
+
# List all schema files in dependency order
|
|
218
|
+
files = [
|
|
219
|
+
'base_types.exp',
|
|
220
|
+
'base_entities.exp',
|
|
221
|
+
'product_schema.exp',
|
|
222
|
+
'catalog_application.exp'
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
# Parse all files
|
|
226
|
+
repository = Expressir::Express::Parser.from_files(files)
|
|
227
|
+
|
|
228
|
+
# Display results
|
|
229
|
+
puts "Loaded #{repository.schemas.size} schemas:"
|
|
230
|
+
repository.schemas.each do |schema|
|
|
231
|
+
puts " - #{schema.id}"
|
|
232
|
+
puts " File: #{schema.file}"
|
|
233
|
+
puts " Entities: #{schema.entities.size}"
|
|
234
|
+
puts " Types: #{schema.types.size}"
|
|
235
|
+
end
|
|
236
|
+
----
|
|
237
|
+
|
|
238
|
+
Run it:
|
|
239
|
+
|
|
240
|
+
[source,bash]
|
|
241
|
+
----
|
|
242
|
+
ruby parse_multiple.rb
|
|
243
|
+
----
|
|
244
|
+
|
|
245
|
+
**Expected output**:
|
|
246
|
+
[source]
|
|
247
|
+
----
|
|
248
|
+
Loaded 4 schemas:
|
|
249
|
+
- base_types
|
|
250
|
+
File: base_types.exp
|
|
251
|
+
Entities: 0
|
|
252
|
+
Types: 5
|
|
253
|
+
- base_entities
|
|
254
|
+
File: base_entities.exp
|
|
255
|
+
Entities: 3
|
|
256
|
+
Types: 0
|
|
257
|
+
- product_schema
|
|
258
|
+
File: product_schema.exp
|
|
259
|
+
Entities: 2
|
|
260
|
+
Types: 1
|
|
261
|
+
- catalog_application
|
|
262
|
+
File: catalog_application.exp
|
|
263
|
+
Entities: 3
|
|
264
|
+
Types: 0
|
|
265
|
+
----
|
|
266
|
+
|
|
267
|
+
==== Progress Tracking
|
|
268
|
+
|
|
269
|
+
Add progress tracking:
|
|
270
|
+
|
|
271
|
+
[source,ruby]
|
|
272
|
+
----
|
|
273
|
+
repository = Expressir::Express::Parser.from_files(files) do |filename, schemas, error|
|
|
274
|
+
if error
|
|
275
|
+
puts "❌ Error loading #{filename}:"
|
|
276
|
+
puts " #{error.message}"
|
|
277
|
+
else
|
|
278
|
+
puts "✓ Loaded #{schemas.length} schema(s) from #{filename}"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
puts "\n📊 Total: #{repository.schemas.size} schemas loaded successfully"
|
|
283
|
+
----
|
|
284
|
+
|
|
285
|
+
**Output**:
|
|
286
|
+
[source]
|
|
287
|
+
----
|
|
288
|
+
✓ Loaded 1 schema(s) from base_types.exp
|
|
289
|
+
✓ Loaded 1 schema(s) from base_entities.exp
|
|
290
|
+
✓ Loaded 1 schema(s) from product_schema.exp
|
|
291
|
+
✓ Loaded 1 schema(s) from catalog_application.exp
|
|
292
|
+
|
|
293
|
+
📊 Total: 4 schemas loaded successfully
|
|
294
|
+
----
|
|
295
|
+
|
|
296
|
+
=== Step 7: Explore Cross-Schema References
|
|
297
|
+
|
|
298
|
+
Now let's explore how references work across schemas.
|
|
299
|
+
|
|
300
|
+
==== Inspect Interfaces
|
|
301
|
+
|
|
302
|
+
Create `inspect_interfaces.rb`:
|
|
303
|
+
|
|
304
|
+
[source,ruby]
|
|
305
|
+
----
|
|
306
|
+
require 'expressir'
|
|
307
|
+
|
|
308
|
+
files = ['base_types.exp', 'base_entities.exp', 'product_schema.exp', 'catalog_application.exp']
|
|
309
|
+
repo = Expressir::Express::Parser.from_files(files)
|
|
310
|
+
|
|
311
|
+
repo.schemas.each do |schema|
|
|
312
|
+
next if schema.interfaces.empty?
|
|
313
|
+
|
|
314
|
+
puts "\n#{schema.id} interfaces:"
|
|
315
|
+
schema.interfaces.each do |interface|
|
|
316
|
+
puts " #{interface.kind.upcase}: #{interface.schema.ref&.id || interface.schema.id}"
|
|
317
|
+
|
|
318
|
+
if interface.items && !interface.items.empty?
|
|
319
|
+
interface.items.each do |item|
|
|
320
|
+
puts " - #{item.id}"
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
puts " (all declarations)"
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
----
|
|
328
|
+
|
|
329
|
+
**Output**:
|
|
330
|
+
[source]
|
|
331
|
+
----
|
|
332
|
+
base_entities interfaces:
|
|
333
|
+
USE: base_types
|
|
334
|
+
(all declarations)
|
|
335
|
+
|
|
336
|
+
product_schema interfaces:
|
|
337
|
+
REFERENCE: base_types
|
|
338
|
+
- identifier
|
|
339
|
+
- label
|
|
340
|
+
- text
|
|
341
|
+
- positive_integer
|
|
342
|
+
REFERENCE: base_entities
|
|
343
|
+
- person
|
|
344
|
+
- organization
|
|
345
|
+
|
|
346
|
+
catalog_application interfaces:
|
|
347
|
+
USE: product_schema
|
|
348
|
+
(all declarations)
|
|
349
|
+
REFERENCE: base_entities
|
|
350
|
+
- address
|
|
351
|
+
----
|
|
352
|
+
|
|
353
|
+
==== Trace Reference Resolution
|
|
354
|
+
|
|
355
|
+
Create `trace_references.rb`:
|
|
356
|
+
|
|
357
|
+
[source,ruby]
|
|
358
|
+
----
|
|
359
|
+
require 'expressir'
|
|
360
|
+
|
|
361
|
+
files = ['base_types.exp', 'base_entities.exp', 'product_schema.exp', 'catalog_application.exp']
|
|
362
|
+
repo = Expressir::Express::Parser.from_files(files)
|
|
363
|
+
|
|
364
|
+
# Find product entity
|
|
365
|
+
product_schema = repo.schemas.find { |s| s.id == 'product_schema' }
|
|
366
|
+
product_entity = product_schema.entities.find { |e| e.id == 'product' }
|
|
367
|
+
|
|
368
|
+
puts "Product entity attributes:"
|
|
369
|
+
product_entity.attributes.each do |attr|
|
|
370
|
+
puts "\n #{attr.id}: #{attr.type}"
|
|
371
|
+
|
|
372
|
+
# Check if type is a reference
|
|
373
|
+
if attr.type.respond_to?(:ref) && attr.type.ref
|
|
374
|
+
ref = attr.type.ref
|
|
375
|
+
puts " Resolved to: #{ref.class.name}"
|
|
376
|
+
puts " Defined in: #{ref.parent.id}" if ref.respond_to?(:parent)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
----
|
|
380
|
+
|
|
381
|
+
**Output**:
|
|
382
|
+
[source]
|
|
383
|
+
----
|
|
384
|
+
Product entity attributes:
|
|
385
|
+
|
|
386
|
+
id: identifier
|
|
387
|
+
Resolved to: Expressir::Model::Declarations::Type
|
|
388
|
+
Defined in: base_types
|
|
389
|
+
|
|
390
|
+
name: label
|
|
391
|
+
Resolved to: Expressir::Model::Declarations::Type
|
|
392
|
+
Defined in: base_types
|
|
393
|
+
|
|
394
|
+
description: text
|
|
395
|
+
Resolved to: Expressir::Model::Declarations::Type
|
|
396
|
+
Defined in: base_types
|
|
397
|
+
|
|
398
|
+
price: REAL
|
|
399
|
+
|
|
400
|
+
quantity: positive_integer
|
|
401
|
+
Resolved to: Expressir::Model::Declarations::Type
|
|
402
|
+
Defined in: base_types
|
|
403
|
+
|
|
404
|
+
manufacturer: organization
|
|
405
|
+
Resolved to: Expressir::Model::Declarations::Entity
|
|
406
|
+
Defined in: base_entities
|
|
407
|
+
----
|
|
408
|
+
|
|
409
|
+
=== Step 8: Handle Dependencies Automatically
|
|
410
|
+
|
|
411
|
+
Expressir can discover dependencies automatically.
|
|
412
|
+
|
|
413
|
+
==== Using Schema Manifests
|
|
414
|
+
|
|
415
|
+
Create `schemas.yml`:
|
|
416
|
+
|
|
417
|
+
[source,yaml]
|
|
418
|
+
----
|
|
419
|
+
schemas:
|
|
420
|
+
- path: base_types.exp
|
|
421
|
+
id: base_types
|
|
422
|
+
- path: base_entities.exp
|
|
423
|
+
id: base_entities
|
|
424
|
+
- path: product_schema.exp
|
|
425
|
+
id: product_schema
|
|
426
|
+
- path: catalog_application.exp
|
|
427
|
+
id: catalog_application
|
|
428
|
+
----
|
|
429
|
+
|
|
430
|
+
==== Load from Manifest
|
|
431
|
+
|
|
432
|
+
Create `load_manifest.rb`:
|
|
433
|
+
|
|
434
|
+
[source,ruby]
|
|
435
|
+
----
|
|
436
|
+
require 'expressir'
|
|
437
|
+
|
|
438
|
+
# Load manifest
|
|
439
|
+
manifest = Expressir::SchemaManifest.from_file('schemas.yml')
|
|
440
|
+
|
|
441
|
+
# Get file paths
|
|
442
|
+
files = manifest.schemas.map(&:path)
|
|
443
|
+
puts "Loading #{files.size} schemas from manifest..."
|
|
444
|
+
|
|
445
|
+
# Parse all
|
|
446
|
+
repo = Expressir::Express::Parser.from_files(files)
|
|
447
|
+
|
|
448
|
+
puts "\nLoaded successfully:"
|
|
449
|
+
repo.schemas.each { |s| puts " - #{s.id}" }
|
|
450
|
+
----
|
|
451
|
+
|
|
452
|
+
=== Step 9: Validate Cross-Schema Consistency
|
|
453
|
+
|
|
454
|
+
Check that all references resolve correctly.
|
|
455
|
+
|
|
456
|
+
==== Create Validation Script
|
|
457
|
+
|
|
458
|
+
Create `validate_references.rb`:
|
|
459
|
+
|
|
460
|
+
[source,ruby]
|
|
461
|
+
----
|
|
462
|
+
require 'expressir'
|
|
463
|
+
|
|
464
|
+
files = ['base_types.exp', 'base_entities.exp', 'product_schema.exp', 'catalog_application.exp']
|
|
465
|
+
repo = Expressir::Express::Parser.from_files(files)
|
|
466
|
+
|
|
467
|
+
unresolved = []
|
|
468
|
+
|
|
469
|
+
repo.schemas.each do |schema|
|
|
470
|
+
schema.entities.each do |entity|
|
|
471
|
+
entity.attributes.each do |attr|
|
|
472
|
+
if attr.type.respond_to?(:ref) && attr.type.ref.nil?
|
|
473
|
+
unresolved << {
|
|
474
|
+
schema: schema.id,
|
|
475
|
+
entity: entity.id,
|
|
476
|
+
attribute: attr.id,
|
|
477
|
+
type: attr.type.id
|
|
478
|
+
}
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
if unresolved.empty?
|
|
485
|
+
puts "✓ All references resolved successfully!"
|
|
486
|
+
else
|
|
487
|
+
puts "❌ Found #{unresolved.size} unresolved references:"
|
|
488
|
+
unresolved.each do |item|
|
|
489
|
+
puts " #{item[:schema]}.#{item[:entity]}.#{item[:attribute]}: #{item[:type]}"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
----
|
|
493
|
+
|
|
494
|
+
=== Step 10: Generate Dependency Graph
|
|
495
|
+
|
|
496
|
+
Visualize schema dependencies.
|
|
497
|
+
|
|
498
|
+
==== Create Dependency Report
|
|
499
|
+
|
|
500
|
+
Create `dependency_graph.rb`:
|
|
501
|
+
|
|
502
|
+
[source,ruby]
|
|
503
|
+
----
|
|
504
|
+
require 'expressir'
|
|
505
|
+
|
|
506
|
+
files = ['base_types.exp', 'base_entities.exp', 'product_schema.exp', 'catalog_application.exp']
|
|
507
|
+
repo = Expressir::Express::Parser.from_files(files)
|
|
508
|
+
|
|
509
|
+
puts "Schema Dependency Graph:"
|
|
510
|
+
puts "=" * 60
|
|
511
|
+
|
|
512
|
+
repo.schemas.each do |schema|
|
|
513
|
+
puts "\n#{schema.id}:"
|
|
514
|
+
|
|
515
|
+
if schema.interfaces.empty?
|
|
516
|
+
puts " (no dependencies)"
|
|
517
|
+
else
|
|
518
|
+
schema.interfaces.each do |interface|
|
|
519
|
+
target = interface.schema.ref&.id || interface.schema.id
|
|
520
|
+
kind = interface.kind == 'use' ? 'USES' : 'REFERENCES'
|
|
521
|
+
puts " #{kind} #{target}"
|
|
522
|
+
|
|
523
|
+
if interface.items && !interface.items.empty?
|
|
524
|
+
puts " Imports: #{interface.items.map(&:id).join(', ')}"
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
----
|
|
530
|
+
|
|
531
|
+
**Output**:
|
|
532
|
+
[source]
|
|
533
|
+
----
|
|
534
|
+
Schema Dependency Graph:
|
|
535
|
+
============================================================
|
|
536
|
+
|
|
537
|
+
base_types:
|
|
538
|
+
(no dependencies)
|
|
539
|
+
|
|
540
|
+
base_entities:
|
|
541
|
+
USES base_types
|
|
542
|
+
|
|
543
|
+
product_schema:
|
|
544
|
+
REFERENCES base_types
|
|
545
|
+
Imports: identifier, label, text, positive_integer
|
|
546
|
+
REFERENCES base_entities
|
|
547
|
+
Imports: person, organization
|
|
548
|
+
|
|
549
|
+
catalog_application:
|
|
550
|
+
USES product_schema
|
|
551
|
+
REFERENCES base_entities
|
|
552
|
+
Imports: address
|
|
553
|
+
----
|
|
554
|
+
|
|
555
|
+
=== Step 11: Practice Exercises
|
|
556
|
+
|
|
557
|
+
==== Exercise 1: Add New Schema
|
|
558
|
+
|
|
559
|
+
Create a `shipping_schema.exp` that:
|
|
560
|
+
|
|
561
|
+
* References `base_entities` for address
|
|
562
|
+
* References `product_schema` for product
|
|
563
|
+
* Defines shipment and delivery entities
|
|
564
|
+
|
|
565
|
+
Parse all schemas together and verify references resolve.
|
|
566
|
+
|
|
567
|
+
==== Exercise 2: Circular Dependencies
|
|
568
|
+
|
|
569
|
+
Create two schemas that reference each other:
|
|
570
|
+
|
|
571
|
+
[source,express]
|
|
572
|
+
----
|
|
573
|
+
SCHEMA schema_a;
|
|
574
|
+
REFERENCE FROM schema_b (entity_b);
|
|
575
|
+
ENTITY entity_a;
|
|
576
|
+
ref_b : entity_b;
|
|
577
|
+
END_ENTITY;
|
|
578
|
+
END_SCHEMA;
|
|
579
|
+
|
|
580
|
+
SCHEMA schema_b;
|
|
581
|
+
REFERENCE FROM schema_a (entity_a);
|
|
582
|
+
ENTITY entity_b;
|
|
583
|
+
ref_a : entity_a;
|
|
584
|
+
END_ENTITY;
|
|
585
|
+
END_SCHEMA;
|
|
586
|
+
----
|
|
587
|
+
|
|
588
|
+
Parse them and observe how Expressir handles circular references.
|
|
589
|
+
|
|
590
|
+
==== Exercise 3: Dependency Ordering
|
|
591
|
+
|
|
592
|
+
Given these schemas:
|
|
593
|
+
* `z_schema.exp` - References `m_schema`
|
|
594
|
+
* `m_schema.exp` - References `a_schema`
|
|
595
|
+
* `a_schema.exp` - No dependencies
|
|
596
|
+
|
|
597
|
+
Find the correct parsing order and explain why it matters.
|
|
598
|
+
|
|
599
|
+
=== Common Pitfalls
|
|
600
|
+
|
|
601
|
+
==== Wrong File Order
|
|
602
|
+
|
|
603
|
+
[source,ruby]
|
|
604
|
+
----
|
|
605
|
+
# ❌ Wrong: dependent schema before dependency
|
|
606
|
+
files = ['catalog_application.exp', 'base_types.exp']
|
|
607
|
+
|
|
608
|
+
# ✅ Correct: dependencies first
|
|
609
|
+
files = ['base_types.exp', 'catalog_application.exp']
|
|
610
|
+
----
|
|
611
|
+
|
|
612
|
+
**Note**: Expressir handles this automatically, but explicit ordering is clearer.
|
|
613
|
+
|
|
614
|
+
==== Missing Interface Declarations
|
|
615
|
+
|
|
616
|
+
[source,express]
|
|
617
|
+
----
|
|
618
|
+
# ❌ Wrong: using type without interface
|
|
619
|
+
SCHEMA my_schema;
|
|
620
|
+
ENTITY my_entity;
|
|
621
|
+
name : label; -- label not declared or imported!
|
|
622
|
+
END_ENTITY;
|
|
623
|
+
END_SCHEMA;
|
|
624
|
+
|
|
625
|
+
# ✅ Correct: import the type
|
|
626
|
+
SCHEMA my_schema;
|
|
627
|
+
REFERENCE FROM base_types (label);
|
|
628
|
+
ENTITY my_entity;
|
|
629
|
+
name : label;
|
|
630
|
+
END_ENTITY;
|
|
631
|
+
END_SCHEMA;
|
|
632
|
+
----
|
|
633
|
+
|
|
634
|
+
==== Assuming Reference Resolution
|
|
635
|
+
|
|
636
|
+
[source,ruby]
|
|
637
|
+
----
|
|
638
|
+
# ❌ Wrong: assuming ref is resolved
|
|
639
|
+
attr.type.ref.id # May crash if ref is nil!
|
|
640
|
+
|
|
641
|
+
# ✅ Correct: check first
|
|
642
|
+
if attr.type.respond_to?(:ref) && attr.type.ref
|
|
643
|
+
puts attr.type.ref.id
|
|
644
|
+
else
|
|
645
|
+
puts "Unresolved reference: #{attr.type.id}"
|
|
646
|
+
end
|
|
647
|
+
----
|
|
648
|
+
|
|
649
|
+
=== Next Steps
|
|
650
|
+
|
|
651
|
+
Congratulations! You now understand multi-schema EXPRESS applications.
|
|
652
|
+
|
|
653
|
+
**Continue learning**:
|
|
654
|
+
|
|
655
|
+
* link:creating-ler-package.html[Creating LER Packages] - Package schemas for distribution
|
|
656
|
+
* link:querying-schemas.html[Querying Schemas] - Search across multiple schemas
|
|
657
|
+
* link:../pages/ler-packages.html[LER Packages] - Understanding LER format
|
|
658
|
+
|
|
659
|
+
**Read more**:
|
|
660
|
+
|
|
661
|
+
* link:../pages/parsers.html[Parsers] - Deep dive into parsing
|
|
662
|
+
* link:../pages/data-model.html[Data Model] - Understanding the model
|
|
663
|
+
* link:../guides/ruby-api/[Ruby API Guides] - Advanced techniques
|
|
664
|
+
|
|
665
|
+
=== Summary
|
|
666
|
+
|
|
667
|
+
In this tutorial, you learned to:
|
|
668
|
+
|
|
669
|
+
* ✅ Parse multiple EXPRESS schema files
|
|
670
|
+
* ✅ Work with USE FROM and REFERENCE FROM
|
|
671
|
+
* ✅ Resolve and validate cross-schema references
|
|
672
|
+
* ✅ Manage schema dependencies
|
|
673
|
+
* ✅ Create dependency graphs
|
|
674
|
+
* ✅ Use schema manifests
|
|
675
|
+
|
|
676
|
+
You're now ready to work with complex, multi-schema EXPRESS applications!
|