expressir 2.1.30 → 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 +244 -39
- data/Gemfile +2 -1
- data/README.adoc +621 -54
- 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/cli.rb +12 -4
- 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 +15 -2
- metadata +114 -4
- data/docs/benchmarking.adoc +0 -107
- data/docs/liquid_drops.adoc +0 -1547
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Data Model
|
|
3
|
+
nav_order: 5
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
== Expressir Data Model
|
|
7
|
+
|
|
8
|
+
=== Purpose
|
|
9
|
+
|
|
10
|
+
This page explains Expressir's Ruby data model that represents EXPRESS schemas. Understanding this model is essential for programmatic manipulation of EXPRESS data, writing custom tools, and generating documentation.
|
|
11
|
+
|
|
12
|
+
=== References
|
|
13
|
+
|
|
14
|
+
* link:express-language.html[EXPRESS Language] - Understanding EXPRESS concepts
|
|
15
|
+
* link:parsers.html[Parsers] - How EXPRESS becomes the data model
|
|
16
|
+
* link:../guides/ruby-api/model-traversal.html[Guide: Model Traversal] - Navigating the model
|
|
17
|
+
* link:../references/data-model/[Data Model Reference] - Complete API documentation
|
|
18
|
+
|
|
19
|
+
=== Concepts
|
|
20
|
+
|
|
21
|
+
Model Element:: Base class for all EXPRESS constructs in Expressir's object model
|
|
22
|
+
Repository:: Top-level container holding multiple schemas
|
|
23
|
+
Declaration:: Named definition (entity, type, function, etc.) within a schema
|
|
24
|
+
Data Type:: Type specification (primitives, aggregates, constructed types)
|
|
25
|
+
Expression:: Algorithmic calculation or value reference
|
|
26
|
+
Statement:: Procedural operation (assignment, if/then, loop, etc.)
|
|
27
|
+
Reference:: Link to another model element by name
|
|
28
|
+
|
|
29
|
+
=== Model Hierarchy
|
|
30
|
+
|
|
31
|
+
Expressir's data model follows an object-oriented design with clear class hierarchies:
|
|
32
|
+
|
|
33
|
+
[source]
|
|
34
|
+
----
|
|
35
|
+
ModelElement (base class)
|
|
36
|
+
├── Repository
|
|
37
|
+
│ └── Schema
|
|
38
|
+
│ ├── Entity
|
|
39
|
+
│ ├── Type
|
|
40
|
+
│ ├── Function
|
|
41
|
+
│ ├── Procedure
|
|
42
|
+
│ ├── Rule
|
|
43
|
+
│ ├── Constant
|
|
44
|
+
│ ├── SubtypeConstraint
|
|
45
|
+
│ └── Interface
|
|
46
|
+
├── DataTypes
|
|
47
|
+
│ ├── Primitives (String, Integer, Real, Boolean, Logical, Binary, Number)
|
|
48
|
+
│ ├── Aggregates (Array, List, Set, Bag)
|
|
49
|
+
│ ├── Enumeration
|
|
50
|
+
│ ├── Select
|
|
51
|
+
│ ├── Generic
|
|
52
|
+
│ └── GenericEntity
|
|
53
|
+
├── Declarations
|
|
54
|
+
│ ├── Attribute
|
|
55
|
+
│ ├── DerivedAttribute
|
|
56
|
+
│ ├── InverseAttribute
|
|
57
|
+
│ ├── Parameter
|
|
58
|
+
│ ├── Variable
|
|
59
|
+
│ ├── UniqueRule
|
|
60
|
+
│ ├── WhereRule
|
|
61
|
+
│ └── ...
|
|
62
|
+
├── Expressions
|
|
63
|
+
│ ├── BinaryExpression
|
|
64
|
+
│ ├── UnaryExpression
|
|
65
|
+
│ ├── FunctionCall
|
|
66
|
+
│ ├── QueryExpression
|
|
67
|
+
│ ├── AggregateInitializer
|
|
68
|
+
│ └── ...
|
|
69
|
+
├── Statements
|
|
70
|
+
│ ├── Assignment
|
|
71
|
+
│ ├── If
|
|
72
|
+
│ ├── Repeat
|
|
73
|
+
│ ├── Case
|
|
74
|
+
│ ├── Return
|
|
75
|
+
│ └── ...
|
|
76
|
+
├── References
|
|
77
|
+
│ ├── SimpleReference
|
|
78
|
+
│ ├── AttributeReference
|
|
79
|
+
│ ├── GroupReference
|
|
80
|
+
│ └── IndexReference
|
|
81
|
+
└── Literals
|
|
82
|
+
├── String
|
|
83
|
+
├── Integer
|
|
84
|
+
├── Real
|
|
85
|
+
├── Logical
|
|
86
|
+
└── Binary
|
|
87
|
+
----
|
|
88
|
+
|
|
89
|
+
=== Key Classes
|
|
90
|
+
|
|
91
|
+
==== ModelElement
|
|
92
|
+
|
|
93
|
+
The base class for all EXPRESS constructs:
|
|
94
|
+
|
|
95
|
+
[source,ruby]
|
|
96
|
+
----
|
|
97
|
+
module Expressir::Model
|
|
98
|
+
class ModelElement
|
|
99
|
+
attr_accessor :parent # Parent element in tree
|
|
100
|
+
attribute :source, :string # Original EXPRESS text
|
|
101
|
+
attribute :untagged_remarks, :string, collection: true
|
|
102
|
+
|
|
103
|
+
# Navigate to parent and ancestors
|
|
104
|
+
def path # Full qualified path (e.g., "schema.entity.attribute")
|
|
105
|
+
def find(path) # Find child by path
|
|
106
|
+
def children # Direct children
|
|
107
|
+
def children_by_id # Children indexed by ID
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
----
|
|
111
|
+
|
|
112
|
+
**Key features**:
|
|
113
|
+
|
|
114
|
+
* **Parent linkage**: Every element knows its parent
|
|
115
|
+
* **Path navigation**: Full qualified paths for reference resolution
|
|
116
|
+
* **Child access**: Easy traversal of the tree structure
|
|
117
|
+
* **Source tracking**: Original EXPRESS text available
|
|
118
|
+
|
|
119
|
+
==== Repository
|
|
120
|
+
|
|
121
|
+
Top-level container for schemas:
|
|
122
|
+
|
|
123
|
+
[source,ruby]
|
|
124
|
+
----
|
|
125
|
+
repository = Expressir::Model::Repository.new
|
|
126
|
+
repository.schemas << schema1
|
|
127
|
+
repository.schemas << schema2
|
|
128
|
+
|
|
129
|
+
# Access schemas
|
|
130
|
+
repository.schemas.each { |schema| puts schema.id }
|
|
131
|
+
|
|
132
|
+
# Find elements across schemas
|
|
133
|
+
entity = repository.find_entity(qualified_name: "action_schema.action")
|
|
134
|
+
type = repository.find_type(qualified_name: "geometry_schema.point")
|
|
135
|
+
|
|
136
|
+
# Statistics
|
|
137
|
+
stats = repository.statistics
|
|
138
|
+
# => { total_schemas: 10, total_entities: 150, ... }
|
|
139
|
+
|
|
140
|
+
# Build indexes for fast lookup
|
|
141
|
+
repository.build_indexes
|
|
142
|
+
----
|
|
143
|
+
|
|
144
|
+
**Capabilities**:
|
|
145
|
+
|
|
146
|
+
* **Multi-schema management**: Holds multiple schemas
|
|
147
|
+
* **Global search**: Find entities/types across all schemas
|
|
148
|
+
* **Statistics**: Aggregate counts and metrics
|
|
149
|
+
* **Indexing**: Fast lookup with automatic index building
|
|
150
|
+
* **Validation**: Schema consistency checking
|
|
151
|
+
|
|
152
|
+
==== Schema
|
|
153
|
+
|
|
154
|
+
A named collection of EXPRESS declarations:
|
|
155
|
+
|
|
156
|
+
[source,ruby]
|
|
157
|
+
----
|
|
158
|
+
schema = Expressir::Model::Declarations::Schema.new(id: "example_schema")
|
|
159
|
+
|
|
160
|
+
# Access declarations
|
|
161
|
+
schema.entities # Array of Entity objects
|
|
162
|
+
schema.types # Array of Type objects
|
|
163
|
+
schema.functions # Array of Function objects
|
|
164
|
+
schema.procedures # Array of Procedure objects
|
|
165
|
+
schema.rules # Array of Rule objects
|
|
166
|
+
schema.constants # Array of Constant objects
|
|
167
|
+
schema.interfaces # Array of Interface objects
|
|
168
|
+
|
|
169
|
+
# Metadata
|
|
170
|
+
schema.file # Source file path
|
|
171
|
+
schema.version # Schema version info
|
|
172
|
+
schema.remarks # Documentation comments
|
|
173
|
+
|
|
174
|
+
# Find child element
|
|
175
|
+
entity = schema.find("person")
|
|
176
|
+
----
|
|
177
|
+
|
|
178
|
+
**Key attributes**:
|
|
179
|
+
|
|
180
|
+
* **id**: Schema name
|
|
181
|
+
* **file**: Source file path
|
|
182
|
+
* **version**: SchemaVersion object
|
|
183
|
+
* **interfaces**: USE FROM / REFERENCE FROM declarations
|
|
184
|
+
* **entities, types, functions, etc.**: Collections of declarations
|
|
185
|
+
|
|
186
|
+
==== Entity
|
|
187
|
+
|
|
188
|
+
Represents an EXPRESS ENTITY:
|
|
189
|
+
|
|
190
|
+
[source,ruby]
|
|
191
|
+
----
|
|
192
|
+
entity = Expressir::Model::Declarations::Entity.new(
|
|
193
|
+
id: "person",
|
|
194
|
+
abstract: false
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Attributes
|
|
198
|
+
entity.attributes # Explicit attributes
|
|
199
|
+
entity.derived_attributes # DERIVE attributes
|
|
200
|
+
entity.inverse_attributes # INVERSE attributes
|
|
201
|
+
|
|
202
|
+
# Inheritance
|
|
203
|
+
entity.subtype_of # Array of supertypes
|
|
204
|
+
entity.supertype_expression # Supertype expression
|
|
205
|
+
|
|
206
|
+
# Constraints
|
|
207
|
+
entity.unique_rules # UNIQUE rules
|
|
208
|
+
entity.where_rules # WHERE rules
|
|
209
|
+
entity.informal_propositions # Informal propositions
|
|
210
|
+
|
|
211
|
+
# Navigation
|
|
212
|
+
entity.children # All child elements
|
|
213
|
+
entity.parent # Parent schema
|
|
214
|
+
----
|
|
215
|
+
|
|
216
|
+
**Key features**:
|
|
217
|
+
|
|
218
|
+
* **Inheritance support**: Subtypes and supertypes
|
|
219
|
+
* **Three attribute types**: Explicit, derived, inverse
|
|
220
|
+
* **Constraint modeling**: WHERE and UNIQUE rules
|
|
221
|
+
* **Documentation**: Remarks and informal propositions
|
|
222
|
+
|
|
223
|
+
==== Type
|
|
224
|
+
|
|
225
|
+
Represents an EXPRESS TYPE:
|
|
226
|
+
|
|
227
|
+
[source,ruby]
|
|
228
|
+
----
|
|
229
|
+
type = Expressir::Model::Declarations::Type.new(id: "length_measure")
|
|
230
|
+
|
|
231
|
+
# Underlying type definition
|
|
232
|
+
type.underlying_type # DataType object (Select, Enumeration, etc.)
|
|
233
|
+
|
|
234
|
+
# Constraints
|
|
235
|
+
type.where_rules # WHERE constraints
|
|
236
|
+
|
|
237
|
+
# For enumeration types
|
|
238
|
+
if type.underlying_type.is_a?(Expressir::Model::DataTypes::Enumeration)
|
|
239
|
+
type.enumeration_items # Array of EnumerationItem objects
|
|
240
|
+
end
|
|
241
|
+
----
|
|
242
|
+
|
|
243
|
+
**Type categories**:
|
|
244
|
+
|
|
245
|
+
* **Simple types**: Constrained primitives
|
|
246
|
+
* **Enumeration**: Named values
|
|
247
|
+
* **Select**: Union types
|
|
248
|
+
* **Aggregate**: Arrays, lists, sets, bags
|
|
249
|
+
* **Generic**: Generic and GenericEntity
|
|
250
|
+
|
|
251
|
+
==== Attribute
|
|
252
|
+
|
|
253
|
+
Entity attributes come in three varieties:
|
|
254
|
+
|
|
255
|
+
**Explicit attributes** (stored data):
|
|
256
|
+
|
|
257
|
+
[source,ruby]
|
|
258
|
+
----
|
|
259
|
+
attr = Expressir::Model::Declarations::Attribute.new(
|
|
260
|
+
id: "name",
|
|
261
|
+
optional: false
|
|
262
|
+
)
|
|
263
|
+
attr.type = String_type # Type reference
|
|
264
|
+
----
|
|
265
|
+
|
|
266
|
+
**Derived attributes** (computed):
|
|
267
|
+
|
|
268
|
+
[source,ruby]
|
|
269
|
+
----
|
|
270
|
+
derived = Expressir::Model::Declarations::DerivedAttribute.new(
|
|
271
|
+
id: "full_name"
|
|
272
|
+
)
|
|
273
|
+
derived.type = String_type
|
|
274
|
+
derived.expression = ... # Calculation expression
|
|
275
|
+
----
|
|
276
|
+
|
|
277
|
+
**Inverse attributes** (relationships):
|
|
278
|
+
|
|
279
|
+
[source,ruby]
|
|
280
|
+
----
|
|
281
|
+
inverse = Expressir::Model::Declarations::InverseAttribute.new(
|
|
282
|
+
id: "employees"
|
|
283
|
+
)
|
|
284
|
+
inverse.type = Set_type
|
|
285
|
+
inverse.attribute = "works_for" # Target attribute
|
|
286
|
+
----
|
|
287
|
+
|
|
288
|
+
=== Relationships
|
|
289
|
+
|
|
290
|
+
==== Parent-Child Relationships
|
|
291
|
+
|
|
292
|
+
Every element maintains a link to its parent:
|
|
293
|
+
|
|
294
|
+
[source,ruby]
|
|
295
|
+
----
|
|
296
|
+
# Navigate up
|
|
297
|
+
attribute.parent # => Entity
|
|
298
|
+
entity.parent # => Schema
|
|
299
|
+
schema.parent # => Repository
|
|
300
|
+
|
|
301
|
+
# Navigate down
|
|
302
|
+
schema.entities.first # => Entity
|
|
303
|
+
entity.attributes.first # => Attribute
|
|
304
|
+
----
|
|
305
|
+
|
|
306
|
+
==== Reference Resolution
|
|
307
|
+
|
|
308
|
+
References are resolved to their target elements:
|
|
309
|
+
|
|
310
|
+
[source,ruby]
|
|
311
|
+
----
|
|
312
|
+
# Before resolution: SimpleReference with id
|
|
313
|
+
attribute.type # => SimpleReference(id: "length_measure")
|
|
314
|
+
|
|
315
|
+
# After resolution: Points to actual Type
|
|
316
|
+
repository.resolve_all_references
|
|
317
|
+
attribute.type.ref # => Type(id: "length_measure")
|
|
318
|
+
----
|
|
319
|
+
|
|
320
|
+
==== Interface Relationships
|
|
321
|
+
|
|
322
|
+
Schemas can import from other schemas:
|
|
323
|
+
|
|
324
|
+
[source,ruby]
|
|
325
|
+
----
|
|
326
|
+
interface = Expressir::Model::Declarations::Interface.new(
|
|
327
|
+
kind: Interface::USE, # or Interface::REFERENCE
|
|
328
|
+
schema: schema_ref
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Items can be selectively imported
|
|
332
|
+
interface.items = [item1, item2] # or empty for all
|
|
333
|
+
----
|
|
334
|
+
|
|
335
|
+
=== Working Programmatically
|
|
336
|
+
|
|
337
|
+
==== Creating Models from Code
|
|
338
|
+
|
|
339
|
+
You can construct models programmatically:
|
|
340
|
+
|
|
341
|
+
[source,ruby]
|
|
342
|
+
----
|
|
343
|
+
# Create repository
|
|
344
|
+
repo = Expressir::Model::Repository.new
|
|
345
|
+
|
|
346
|
+
# Create schema
|
|
347
|
+
schema = Expressir::Model::Declarations::Schema.new(id: "my_schema")
|
|
348
|
+
repo.schemas << schema
|
|
349
|
+
|
|
350
|
+
# Create entity
|
|
351
|
+
entity = Expressir::Model::Declarations::Entity.new(id: "my_entity")
|
|
352
|
+
schema.entities << entity
|
|
353
|
+
|
|
354
|
+
# Create attribute
|
|
355
|
+
attr = Expressir::Model::Declarations::Attribute.new(
|
|
356
|
+
id: "my_attribute",
|
|
357
|
+
optional: false
|
|
358
|
+
)
|
|
359
|
+
entity.attributes << attr
|
|
360
|
+
|
|
361
|
+
# Set attribute type
|
|
362
|
+
string_ref = Expressir::Model::References::SimpleReference.new(id: "STRING")
|
|
363
|
+
attr.type = string_ref
|
|
364
|
+
----
|
|
365
|
+
|
|
366
|
+
==== Parsing EXPRESS to Model
|
|
367
|
+
|
|
368
|
+
Most commonly, you parse EXPRESS files:
|
|
369
|
+
|
|
370
|
+
[source,ruby]
|
|
371
|
+
----
|
|
372
|
+
# Parse file to repository
|
|
373
|
+
repo = Expressir::Express::Parser.from_file("schema.exp")
|
|
374
|
+
|
|
375
|
+
# Access elements
|
|
376
|
+
schema = repo.schemas.first
|
|
377
|
+
entity = schema.entities.find { |e| e.id == "person" }
|
|
378
|
+
attribute = entity.attributes.find { |a| a.id == "name" }
|
|
379
|
+
|
|
380
|
+
# Check type
|
|
381
|
+
if attribute.type.is_a?(Expressir::Model::References::SimpleReference)
|
|
382
|
+
puts "Type name: #{attribute.type.id}"
|
|
383
|
+
end
|
|
384
|
+
----
|
|
385
|
+
|
|
386
|
+
==== Model Traversal
|
|
387
|
+
|
|
388
|
+
Navigate the model tree systematically:
|
|
389
|
+
|
|
390
|
+
[source,ruby]
|
|
391
|
+
----
|
|
392
|
+
# Visit all entities in repository
|
|
393
|
+
repo.schemas.each do |schema|
|
|
394
|
+
schema.entities.each do |entity|
|
|
395
|
+
puts "Entity: #{entity.path}"
|
|
396
|
+
|
|
397
|
+
entity.attributes.each do |attr|
|
|
398
|
+
puts " Attribute: #{attr.id}"
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Find specific element by path
|
|
404
|
+
element = repo.find("action_schema.action.id")
|
|
405
|
+
|
|
406
|
+
# Search with pattern
|
|
407
|
+
entities = repo.schemas.flat_map(&:entities)
|
|
408
|
+
person_entities = entities.select { |e| e.id.include?("person") }
|
|
409
|
+
----
|
|
410
|
+
|
|
411
|
+
==== Visitor Pattern
|
|
412
|
+
|
|
413
|
+
For complex traversals, use the visitor pattern:
|
|
414
|
+
|
|
415
|
+
[source,ruby]
|
|
416
|
+
----
|
|
417
|
+
class MyVisitor < Expressir::Express::Visitor
|
|
418
|
+
def visit_entity(entity)
|
|
419
|
+
puts "Visiting entity: #{entity.id}"
|
|
420
|
+
super # Visit children
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def visit_attribute(attribute)
|
|
424
|
+
puts " Attribute: #{attribute.id}"
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
visitor = MyVisitor.new
|
|
429
|
+
visitor.visit(repository)
|
|
430
|
+
----
|
|
431
|
+
|
|
432
|
+
=== Accessing Elements
|
|
433
|
+
|
|
434
|
+
==== By Direct Navigation
|
|
435
|
+
|
|
436
|
+
[source,ruby]
|
|
437
|
+
----
|
|
438
|
+
# Access through collections
|
|
439
|
+
schema = repo.schemas.first
|
|
440
|
+
entity = schema.entities.first
|
|
441
|
+
attribute = entity.attributes.first
|
|
442
|
+
----
|
|
443
|
+
|
|
444
|
+
==== By ID Lookup
|
|
445
|
+
|
|
446
|
+
[source,ruby]
|
|
447
|
+
----
|
|
448
|
+
# Find by ID in collection
|
|
449
|
+
entity = schema.entities.find { |e| e.id == "person" }
|
|
450
|
+
|
|
451
|
+
# Use children_by_id for faster lookup
|
|
452
|
+
entity = schema.children_by_id["person"]
|
|
453
|
+
----
|
|
454
|
+
|
|
455
|
+
==== By Path
|
|
456
|
+
|
|
457
|
+
[source,ruby]
|
|
458
|
+
----
|
|
459
|
+
# Full qualified path
|
|
460
|
+
element = repo.find("action_schema.action.id")
|
|
461
|
+
|
|
462
|
+
# Relative path from schema
|
|
463
|
+
attribute = schema.find("person.name")
|
|
464
|
+
----
|
|
465
|
+
|
|
466
|
+
==== By Type
|
|
467
|
+
|
|
468
|
+
[source,ruby]
|
|
469
|
+
----
|
|
470
|
+
# Filter by class
|
|
471
|
+
entities = schema.children.select { |c| c.is_a?(Expressir::Model::Declarations::Entity) }
|
|
472
|
+
|
|
473
|
+
# Type-specific collections
|
|
474
|
+
schema.entities # Only entities
|
|
475
|
+
schema.types # Only types
|
|
476
|
+
----
|
|
477
|
+
|
|
478
|
+
=== Model Modification
|
|
479
|
+
|
|
480
|
+
==== Adding Elements
|
|
481
|
+
|
|
482
|
+
[source,ruby]
|
|
483
|
+
----
|
|
484
|
+
# Add to collections
|
|
485
|
+
schema.entities << new_entity
|
|
486
|
+
entity.attributes << new_attribute
|
|
487
|
+
|
|
488
|
+
# Parent is automatically set
|
|
489
|
+
new_entity.parent # => schema
|
|
490
|
+
----
|
|
491
|
+
|
|
492
|
+
==== Removing Elements
|
|
493
|
+
|
|
494
|
+
[source,ruby]
|
|
495
|
+
----
|
|
496
|
+
# Remove from collection
|
|
497
|
+
schema.entities.delete(entity)
|
|
498
|
+
|
|
499
|
+
# Clear parent link
|
|
500
|
+
entity.parent = nil
|
|
501
|
+
----
|
|
502
|
+
|
|
503
|
+
==== Modifying Elements
|
|
504
|
+
|
|
505
|
+
[source,ruby]
|
|
506
|
+
----
|
|
507
|
+
# Change properties
|
|
508
|
+
entity.id = "new_name"
|
|
509
|
+
entity.abstract = true
|
|
510
|
+
|
|
511
|
+
# Modify collections
|
|
512
|
+
entity.attributes.clear
|
|
513
|
+
entity.attributes = new_attributes
|
|
514
|
+
----
|
|
515
|
+
|
|
516
|
+
=== Converting to EXPRESS
|
|
517
|
+
|
|
518
|
+
Convert model back to EXPRESS text:
|
|
519
|
+
|
|
520
|
+
[source,ruby]
|
|
521
|
+
----
|
|
522
|
+
# Format entire repository
|
|
523
|
+
express = Expressir::Express::Formatter.format(repo)
|
|
524
|
+
|
|
525
|
+
# Format single schema
|
|
526
|
+
express = Expressir::Express::Formatter.format(schema)
|
|
527
|
+
|
|
528
|
+
# Format with options
|
|
529
|
+
formatter = Expressir::Express::Formatter.new(no_remarks: true)
|
|
530
|
+
express = formatter.format(schema)
|
|
531
|
+
----
|
|
532
|
+
|
|
533
|
+
=== Converting to Liquid
|
|
534
|
+
|
|
535
|
+
For template-based generation:
|
|
536
|
+
|
|
537
|
+
[source,ruby]
|
|
538
|
+
----
|
|
539
|
+
# Convert to Liquid drops
|
|
540
|
+
repo_drop = repo.to_liquid
|
|
541
|
+
schema_drop = schema.to_liquid
|
|
542
|
+
|
|
543
|
+
# Use in Liquid templates
|
|
544
|
+
template = Liquid::Template.parse("{{ schema.id }}")
|
|
545
|
+
output = template.render('schema' => schema_drop)
|
|
546
|
+
----
|
|
547
|
+
|
|
548
|
+
See link:../guides/liquid/[Liquid guides] for details.
|
|
549
|
+
|
|
550
|
+
=== Performance Considerations
|
|
551
|
+
|
|
552
|
+
==== Indexing
|
|
553
|
+
|
|
554
|
+
Build indexes for large repositories:
|
|
555
|
+
|
|
556
|
+
[source,ruby]
|
|
557
|
+
----
|
|
558
|
+
# Build all indexes
|
|
559
|
+
repo.build_indexes
|
|
560
|
+
|
|
561
|
+
# Fast lookups now available
|
|
562
|
+
entity = repo.find_entity(qualified_name: "schema.entity")
|
|
563
|
+
type = repo.find_type(qualified_name: "schema.type")
|
|
564
|
+
----
|
|
565
|
+
|
|
566
|
+
==== Lazy Loading
|
|
567
|
+
|
|
568
|
+
Collections are loaded on demand:
|
|
569
|
+
|
|
570
|
+
[source,ruby]
|
|
571
|
+
----
|
|
572
|
+
# No parsing until accessed
|
|
573
|
+
schema.entities # Triggers collection loading
|
|
574
|
+
----
|
|
575
|
+
|
|
576
|
+
==== Memory Management
|
|
577
|
+
|
|
578
|
+
For very large schemas:
|
|
579
|
+
|
|
580
|
+
[source,ruby]
|
|
581
|
+
----
|
|
582
|
+
# Process one schema at a time
|
|
583
|
+
schemas.each do |schema_file|
|
|
584
|
+
repo = Expressir::Express::Parser.from_file(schema_file)
|
|
585
|
+
process(repo)
|
|
586
|
+
repo = nil # Allow garbage collection
|
|
587
|
+
end
|
|
588
|
+
----
|
|
589
|
+
|
|
590
|
+
=== Best Practices
|
|
591
|
+
|
|
592
|
+
**Navigate using relationships**::
|
|
593
|
+
Use parent/child links rather than searching
|
|
594
|
+
|
|
595
|
+
**Use typed collections**::
|
|
596
|
+
Access `schema.entities` rather than filtering `schema.children`
|
|
597
|
+
|
|
598
|
+
**Build indexes once**::
|
|
599
|
+
For repositories with many schemas, build indexes early
|
|
600
|
+
|
|
601
|
+
**Check types carefully**::
|
|
602
|
+
Types can be references, resolved references, or inline definitions
|
|
603
|
+
|
|
604
|
+
**Preserve parent links**::
|
|
605
|
+
When moving elements, update parent correctly
|
|
606
|
+
|
|
607
|
+
**Use visitor pattern**:
|
|
608
|
+
For complex tree traversals, implement a visitor
|
|
609
|
+
|
|
610
|
+
=== Common Patterns
|
|
611
|
+
|
|
612
|
+
==== Find All Entities Inheriting From Base
|
|
613
|
+
|
|
614
|
+
[source,ruby]
|
|
615
|
+
----
|
|
616
|
+
def find_subtypes(repo, base_entity_name)
|
|
617
|
+
repo.schemas.flat_map(&:entities).select do |entity|
|
|
618
|
+
entity.subtype_of&.any? { |st| st.ref&.id == base_entity_name }
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
----
|
|
622
|
+
|
|
623
|
+
==== List All SELECT Types
|
|
624
|
+
|
|
625
|
+
[source,ruby]
|
|
626
|
+
----
|
|
627
|
+
select_types = repo.schemas.flat_map(&:types).select do |type|
|
|
628
|
+
type.underlying_type.is_a?(Expressir::Model::DataTypes::Select)
|
|
629
|
+
end
|
|
630
|
+
----
|
|
631
|
+
|
|
632
|
+
==== Find Undefined References
|
|
633
|
+
|
|
634
|
+
[source,ruby]
|
|
635
|
+
----
|
|
636
|
+
undefined = []
|
|
637
|
+
visitor = Expressir::Express::Visitor.new
|
|
638
|
+
visitor.define_singleton_method(:visit_simple_reference) do |ref|
|
|
639
|
+
undefined << ref if ref.ref.nil?
|
|
640
|
+
end
|
|
641
|
+
visitor.visit(repo)
|
|
642
|
+
----
|
|
643
|
+
|
|
644
|
+
=== Next Steps
|
|
645
|
+
|
|
646
|
+
Now that you understand the data model:
|
|
647
|
+
|
|
648
|
+
**Try it out**::
|
|
649
|
+
link:../tutorials/parsing-your-first-schema.html[Parse a schema] and explore the model
|
|
650
|
+
|
|
651
|
+
**Learn traversal**::
|
|
652
|
+
Read the link:../guides/ruby-api/model-traversal.html[model traversal guide]
|
|
653
|
+
|
|
654
|
+
**Study references**::
|
|
655
|
+
Review the complete link:../references/data-model/[data model reference]
|
|
656
|
+
|
|
657
|
+
**Build tools**::
|
|
658
|
+
Create custom analyzers or converters using the model
|
|
659
|
+
|
|
660
|
+
=== Bibliography
|
|
661
|
+
|
|
662
|
+
* link:../references/data-model/[Data Model Reference] - Complete API documentation
|
|
663
|
+
* link:parsers.html[Parsers] - How the model is created
|
|
664
|
+
* https://github.com/lutaml/expressir/tree/main/lib/expressir/model[Model Source Code] - Implementation details
|
|
665
|
+
* link:../guides/ruby-api/[Ruby API Guides] - Working with the model programmatically
|