ea 0.1.0 → 0.1.4

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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Models
6
+ # Represents a cross-reference from the t_xref table in EA database
7
+ # Stores cross-references for stereotypes, properties, and relationships
8
+ # between UML elements
9
+ class EaXref < BaseModel
10
+ attribute :xref_id, Lutaml::Model::Type::String
11
+ attribute :name, Lutaml::Model::Type::String
12
+ attribute :xref_type, Lutaml::Model::Type::String
13
+ attribute :client, Lutaml::Model::Type::String
14
+ attribute :supplier, Lutaml::Model::Type::String
15
+ attribute :description, Lutaml::Model::Type::String
16
+
17
+ def self.primary_key_column
18
+ :xref_id
19
+ end
20
+
21
+ def self.table_name
22
+ "t_xref"
23
+ end
24
+
25
+
26
+ COLUMN_MAP = {
27
+ "XrefID" => :xref_id,
28
+ "Type" => :xref_type,
29
+ }.freeze
30
+
31
+ def self.column_map
32
+ COLUMN_MAP
33
+ end
34
+
35
+ # Convenience aliases
36
+ alias_method :id, :xref_id
37
+ alias_method :type, :xref_type
38
+
39
+ # Parse the Description field into structured data
40
+ #
41
+ # @return [Hash] Parsed description data
42
+ def parsed_description
43
+ return @parsed_description if defined?(@parsed_description)
44
+
45
+ @parsed_description = parse_description_field(description)
46
+ end
47
+
48
+ # Check if this xref is for stereotypes
49
+ # @return [Boolean]
50
+ def stereotype?
51
+ name == "Stereotypes" || description&.include?("@STEREO")
52
+ end
53
+
54
+ # Check if this xref is for custom properties
55
+ # @return [Boolean]
56
+ def custom_property?
57
+ !description&.include?("@STEREO") && !description&.include?("@TAG")
58
+ end
59
+
60
+ # Check if this xref is for element properties
61
+ # @return [Boolean]
62
+ def element_property?
63
+ xref_type == "element property"
64
+ end
65
+
66
+ # Check if this xref is for connector properties
67
+ # @return [Boolean]
68
+ def connector_property?
69
+ xref_type&.include?("connector") && xref_type.include?("property")
70
+ end
71
+
72
+ # Check if this xref is for diagram properties
73
+ # @return [Boolean]
74
+ def diagram_property?
75
+ xref_type == "diagram properties"
76
+ end
77
+
78
+ # Check if this xref is for attribute properties
79
+ # @return [Boolean]
80
+ def attribute_property?
81
+ xref_type == "attribute property"
82
+ end
83
+
84
+ private
85
+
86
+ # Parse Description field with various formats
87
+ #
88
+ # Formats:
89
+ # - @STEREO;Name=X;GUID={...};
90
+ # - @TAG;Name=X;Value=Y;GUID={...};
91
+ # - key=value;key=value;
92
+ #
93
+ # @param desc [String] Description field content
94
+ # @return [Hash] Parsed data
95
+ def parse_description_field(desc) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
96
+ return {} if desc.nil? || desc.empty?
97
+
98
+ result = { raw: desc }
99
+
100
+ # Detect format
101
+ if desc.start_with?("@STEREO")
102
+ result[:format] = :stereotype
103
+ result[:data] = parse_stereo_format(desc)
104
+ elsif desc.start_with?("@TAG")
105
+ result[:format] = :tag
106
+ result[:data] = parse_tag_format(desc)
107
+ else
108
+ result[:format] = :key_value
109
+ result[:data] = parse_key_value_format(desc)
110
+ end
111
+
112
+ result
113
+ end
114
+
115
+ # Parse @STEREO format
116
+ # Example: @STEREO;Name=FeatureType;GUID={ABC...};
117
+ def parse_stereo_format(desc)
118
+ data = {}
119
+ parts = desc.sub(/^@STEREO;/, "").split(";")
120
+
121
+ parts.each do |part|
122
+ next if part.empty?
123
+
124
+ key, value = part.split("=", 2)
125
+ data[key.downcase.to_sym] = value if key && value
126
+ end
127
+
128
+ data
129
+ end
130
+
131
+ # Parse @TAG format
132
+ # Example: @TAG;Name=author;Value=John;GUID={...};
133
+ def parse_tag_format(desc)
134
+ data = {}
135
+ parts = desc.sub(/^@TAG;/, "").split(";")
136
+
137
+ parts.each do |part|
138
+ next if part.empty?
139
+
140
+ key, value = part.split("=", 2)
141
+ data[key.downcase.to_sym] = value if key && value
142
+ end
143
+
144
+ data
145
+ end
146
+
147
+ # Parse key=value format
148
+ # Example: aggregation=composite;direction=source;
149
+ def parse_key_value_format(desc)
150
+ data = {}
151
+ parts = desc.split(";")
152
+
153
+ parts.each do |part|
154
+ next if part.empty?
155
+
156
+ key, value = part.split("=", 2)
157
+ data[key.downcase.to_sym] = value if key && value
158
+ end
159
+
160
+ data
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Ea
6
+ module Qea
7
+ module Models
8
+ autoload :BaseModel, "ea/qea/models/base_model"
9
+ autoload :EaAttribute, "ea/qea/models/ea_attribute"
10
+ autoload :EaAttributeTag, "ea/qea/models/ea_attribute_tag"
11
+ autoload :EaComplexityType, "ea/qea/models/ea_complexity_type"
12
+ autoload :EaConnector, "ea/qea/models/ea_connector"
13
+ autoload :EaConnectorType, "ea/qea/models/ea_connector_type"
14
+ autoload :EaConstraintType, "ea/qea/models/ea_constraint_type"
15
+ autoload :EaDatatype, "ea/qea/models/ea_datatype"
16
+ autoload :EaDiagram, "ea/qea/models/ea_diagram"
17
+ autoload :EaDiagramLink, "ea/qea/models/ea_diagram_link"
18
+ autoload :EaDiagramObject, "ea/qea/models/ea_diagram_object"
19
+ autoload :EaDiagramType, "ea/qea/models/ea_diagram_type"
20
+ autoload :EaDocument, "ea/qea/models/ea_document"
21
+ autoload :EaObject, "ea/qea/models/ea_object"
22
+ autoload :EaObjectConstraint, "ea/qea/models/ea_object_constraint"
23
+ autoload :EaObjectProperty, "ea/qea/models/ea_object_property"
24
+ autoload :EaObjectType, "ea/qea/models/ea_object_type"
25
+ autoload :EaOperation, "ea/qea/models/ea_operation"
26
+ autoload :EaOperationParam, "ea/qea/models/ea_operation_param"
27
+ autoload :EaPackage, "ea/qea/models/ea_package"
28
+ autoload :EaScript, "ea/qea/models/ea_script"
29
+ autoload :EaStatusType, "ea/qea/models/ea_status_type"
30
+ autoload :EaStereotype, "ea/qea/models/ea_stereotype"
31
+ autoload :EaTaggedValue, "ea/qea/models/ea_tagged_value"
32
+ autoload :EaXref, "ea/qea/models/ea_xref"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Repositories
6
+ # Base repository for querying model collections
7
+ #
8
+ # This class provides common query methods for all model repositories.
9
+ # Subclasses can extend with model-specific query methods.
10
+ #
11
+ # @example Using base repository
12
+ # repository = BaseRepository.new(records)
13
+ # all_records = repository.all
14
+ # record = repository.find(123)
15
+ # filtered = repository.where { |r| r.name == "Test" }
16
+ class BaseRepository
17
+ include Enumerable
18
+
19
+ # @return [Array] The collection of records
20
+ attr_reader :records
21
+
22
+ # Initialize repository with a collection
23
+ #
24
+ # @param records [Array] Array of model instances
25
+ def initialize(records)
26
+ @records = records.freeze
27
+ @pk_index = nil
28
+ @secondary_indexes = {}
29
+ end
30
+
31
+ # Iterates over all records
32
+ #
33
+ # @yield [record] Each record in the collection
34
+ # @return [Enumerator] if no block given
35
+ def each(&block)
36
+ return to_enum(:each) unless block
37
+
38
+ @records.each(&block)
39
+ end
40
+
41
+ # Get all records
42
+ #
43
+ # @return [Array] All records in the collection
44
+ def all
45
+ @records
46
+ end
47
+
48
+ # Find a record by primary key
49
+ #
50
+ # @param id [Object] Primary key value
51
+ # @return [Object, nil] The record or nil if not found
52
+ def find(id)
53
+ build_pk_index unless @pk_index
54
+ @pk_index[id]
55
+ end
56
+
57
+ # Find a record by key and id
58
+ #
59
+ # @param key [Symbol] key attribute name
60
+ # @param id [Object] key value
61
+ # @return [Object, nil] The record or nil if not found
62
+ def find_by_key(key, id)
63
+ @records.find { |record| record.public_send(key) == id }
64
+ end
65
+
66
+ # Filter records by conditions
67
+ #
68
+ # @param conditions [Hash] Hash of attribute/value pairs
69
+ # @yield [record] Optional block for custom filtering
70
+ # @yieldparam record [Object] Each record
71
+ # @yieldreturn [Boolean] true to include record
72
+ # @return [Array] Filtered records
73
+ #
74
+ # @example Hash conditions
75
+ # repository.where(name: "Test", type: "Class")
76
+ #
77
+ # @example Block condition
78
+ # repository.where { |r| r.name.start_with?("Test") }
79
+ def where(conditions = nil, &block) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
80
+ if block
81
+ @records.select(&block)
82
+ elsif conditions.is_a?(Hash)
83
+ @records.select do |record|
84
+ conditions.all? do |attr, value|
85
+ record.public_send(attr) == value
86
+ end
87
+ end
88
+ else
89
+ @records
90
+ end
91
+ end
92
+
93
+ # Count records
94
+ #
95
+ # @param conditions [Hash, nil] Optional conditions to filter
96
+ # @yield [record] Optional block for custom filtering
97
+ # @return [Integer] Number of records
98
+ #
99
+ # @example Count all
100
+ # repository.count
101
+ #
102
+ # @example Count with conditions
103
+ # repository.count(type: "Class")
104
+ #
105
+ # @example Count with block
106
+ # repository.count { |r| r.name.include?("Test") }
107
+ def count(conditions = nil, &block)
108
+ if block || conditions
109
+ where(conditions, &block).size
110
+ else
111
+ @records.size
112
+ end
113
+ end
114
+
115
+ # Find first record matching conditions
116
+ #
117
+ # @param conditions [Hash, nil] Optional conditions
118
+ # @yield [record] Optional block for custom filtering
119
+ # @return [Object, nil] First matching record or nil
120
+ #
121
+ # @example
122
+ # repository.find_first(name: "Test")
123
+ # repository.find_first { |r| r.name.start_with?("Test") }
124
+ def find_first(conditions = nil, &)
125
+ where(conditions, &).first
126
+ end
127
+
128
+ # Check if any records match conditions
129
+ #
130
+ # @param conditions [Hash, nil] Optional conditions
131
+ # @yield [record] Optional block for custom filtering
132
+ # @return [Boolean] true if any records match
133
+ def any?(conditions = nil, &)
134
+ !where(conditions, &).empty?
135
+ end
136
+
137
+ # Check if no records match conditions
138
+ #
139
+ # @param conditions [Hash, nil] Optional conditions
140
+ # @yield [record] Optional block for custom filtering
141
+ # @return [Boolean] true if no records match
142
+ def none?(conditions = nil, &)
143
+ where(conditions, &).empty?
144
+ end
145
+
146
+ # Select specific attributes from records
147
+ #
148
+ # @param attributes [Array<Symbol>] Attribute names to select
149
+ # @return [Array<Hash>] Array of hashes with selected attributes
150
+ #
151
+ # @example
152
+ # repository.pluck(:id, :name)
153
+ # # => [{id: 1, name: "Test"}, {id: 2, name: "Other"}]
154
+ def pluck(*attributes)
155
+ @records.map do |record|
156
+ attributes.to_h do |attr|
157
+ [attr, record.public_send(attr)]
158
+ end
159
+ end
160
+ end
161
+
162
+ # Group records by an attribute
163
+ #
164
+ # @param attribute [Symbol] Attribute name to group by
165
+ # @return [Hash] Hash with attribute values as keys, arrays as values
166
+ #
167
+ # @example
168
+ # repository.group_by(:type)
169
+ # # => {"Class" => [obj1, obj2], "Interface" => [obj3]}
170
+ def group_by(attribute)
171
+ @records.group_by { |record| record.public_send(attribute) }
172
+ end
173
+
174
+ # Sort records by an attribute
175
+ #
176
+ # @param attribute [Symbol] Attribute name to sort by
177
+ # @param order [Symbol] :asc or :desc (default: :asc)
178
+ # @return [Array] Sorted records
179
+ #
180
+ # @example
181
+ # repository.order_by(:name)
182
+ # repository.order_by(:created_at, :desc)
183
+ def order_by(attribute, order = :asc)
184
+ sorted = @records.sort_by { |record| record.public_send(attribute) }
185
+ order == :desc ? sorted.reverse : sorted
186
+ end
187
+
188
+ # Get unique values for an attribute
189
+ #
190
+ # @param attribute [Symbol] Attribute name
191
+ # @return [Array] Unique values
192
+ #
193
+ # @example
194
+ # repository.distinct(:type)
195
+ # # => ["Class", "Interface", "Component"]
196
+ def distinct(attribute)
197
+ @records.map { |record| record.public_send(attribute) }.uniq
198
+ end
199
+
200
+ # Check if repository is empty
201
+ #
202
+ # @return [Boolean] true if no records
203
+ def empty?
204
+ @records.empty?
205
+ end
206
+
207
+ # Get the size of the collection
208
+ #
209
+ # @return [Integer] Number of records
210
+ def size
211
+ @records.size
212
+ end
213
+
214
+ alias length size
215
+
216
+ private
217
+
218
+ def build_pk_index
219
+ @pk_index = {}
220
+ @records.each { |r| @pk_index[r.primary_key] = r }
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Repositories
6
+ # Repository for EaObject collection with type-specific queries
7
+ #
8
+ # This repository provides convenient methods for querying objects
9
+ # by type, package, and other common criteria.
10
+ #
11
+ # @example Query by type
12
+ # classes = repository.find_by_type("Class")
13
+ # interfaces = repository.interfaces
14
+ #
15
+ # @example Query by package
16
+ # pkg_objects = repository.find_by_package(5)
17
+ class ObjectRepository < BaseRepository
18
+ # Find objects by type
19
+ #
20
+ # @param object_type [String] The object type
21
+ # (e.g., "Class", "Interface")
22
+ # @return [Array<Models::EaObject>] Matching objects
23
+ #
24
+ # @example
25
+ # repository.find_by_type("Class")
26
+ # repository.find_by_type("Interface")
27
+ def find_by_type(object_type)
28
+ where(object_type: object_type)
29
+ end
30
+
31
+ # Find objects by package ID
32
+ #
33
+ # @param package_id [Integer] The package ID
34
+ # @return [Array<Models::EaObject>] Objects in the package
35
+ def find_by_package(package_id)
36
+ where(package_id: package_id)
37
+ end
38
+
39
+ # Find objects by stereotype
40
+ #
41
+ # @param stereotype [String] The stereotype name
42
+ # @return [Array<Models::EaObject>] Objects with the stereotype
43
+ def find_by_stereotype(stereotype)
44
+ where(stereotype: stereotype)
45
+ end
46
+
47
+ # Get all UML classes
48
+ #
49
+ # @return [Array<Models::EaObject>] All Class objects
50
+ def classes
51
+ find_by_type("Class")
52
+ end
53
+
54
+ # Get all interfaces
55
+ #
56
+ # @return [Array<Models::EaObject>] All Interface objects
57
+ def interfaces
58
+ find_by_type("Interface")
59
+ end
60
+
61
+ # Get all enumerations
62
+ #
63
+ # @return [Array<Models::EaObject>] All Enumeration objects
64
+ def enumerations
65
+ find_by_type("Enumeration")
66
+ end
67
+
68
+ # Get all components
69
+ #
70
+ # @return [Array<Models::EaObject>] All Component objects
71
+ def components
72
+ find_by_type("Component")
73
+ end
74
+
75
+ # Get all data types
76
+ #
77
+ # @return [Array<Models::EaObject>] All DataType objects
78
+ def data_types
79
+ find_by_type("DataType")
80
+ end
81
+
82
+ # Get all packages
83
+ #
84
+ # @return [Array<Models::EaObject>] All Package objects
85
+ def packages
86
+ find_by_type("Package")
87
+ end
88
+
89
+ # Get all abstract objects
90
+ #
91
+ # @return [Array<Models::EaObject>] All abstract objects
92
+ def abstract_objects
93
+ where(&:abstract?)
94
+ end
95
+
96
+ # Get all root objects
97
+ #
98
+ # @return [Array<Models::EaObject>] All root objects
99
+ def root_objects
100
+ where(&:root?)
101
+ end
102
+
103
+ # Get all leaf objects
104
+ #
105
+ # @return [Array<Models::EaObject>] All leaf objects
106
+ def leaf_objects
107
+ where(&:leaf?)
108
+ end
109
+
110
+ # Find objects by name pattern
111
+ #
112
+ # @param pattern [String, Regexp] Name pattern to match
113
+ # @return [Array<Models::EaObject>] Matching objects
114
+ #
115
+ # @example String match
116
+ # repository.find_by_name("MyClass")
117
+ #
118
+ # @example Regex match
119
+ # repository.find_by_name(/^Test/)
120
+ def find_by_name(pattern)
121
+ if pattern.is_a?(Regexp)
122
+ where { |obj| obj.name =~ pattern }
123
+ else
124
+ where(name: pattern)
125
+ end
126
+ end
127
+
128
+ # Get objects created after a date
129
+ #
130
+ # @param date [String] Date in ISO format
131
+ # @return [Array<Models::EaObject>] Objects created after date
132
+ def created_after(date)
133
+ where { |obj| obj.createddate && obj.createddate > date }
134
+ end
135
+
136
+ # Get objects modified after a date
137
+ #
138
+ # @param date [String] Date in ISO format
139
+ # @return [Array<Models::EaObject>] Objects modified after date
140
+ def modified_after(date)
141
+ where { |obj| obj.modifieddate && obj.modifieddate > date }
142
+ end
143
+
144
+ # Get statistics by object type
145
+ #
146
+ # @return [Hash<String, Integer>] Count by object type
147
+ #
148
+ # @example
149
+ # repository.type_statistics
150
+ # # => {"Class" => 42, "Interface" => 15, ...}
151
+ def type_statistics
152
+ group_by(:object_type).transform_values(&:size)
153
+ end
154
+
155
+ # Get statistics by package
156
+ #
157
+ # @return [Hash<Integer, Integer>] Count by package ID
158
+ def package_statistics
159
+ group_by(:package_id).transform_values(&:size)
160
+ end
161
+
162
+ # Get all object types in the collection
163
+ #
164
+ # @return [Array<String>] Unique object types
165
+ def object_types
166
+ distinct(:object_type).compact
167
+ end
168
+
169
+ # Get all stereotypes in the collection
170
+ #
171
+ # @return [Array<String>] Unique stereotypes
172
+ def stereotypes
173
+ distinct(:stereotype).compact
174
+ end
175
+
176
+ # Find objects by visibility
177
+ #
178
+ # @param visibility [String] Visibility (e.g., "Public", "Private")
179
+ # @return [Array<Models::EaObject>] Objects with the visibility
180
+ def find_by_visibility(visibility)
181
+ where(visibility: visibility)
182
+ end
183
+
184
+ # Get public objects
185
+ #
186
+ # @return [Array<Models::EaObject>] All public objects
187
+ def public_objects
188
+ find_by_visibility("Public")
189
+ end
190
+
191
+ # Get private objects
192
+ #
193
+ # @return [Array<Models::EaObject>] All private objects
194
+ def private_objects
195
+ find_by_visibility("Private")
196
+ end
197
+
198
+ # Get protected objects
199
+ #
200
+ # @return [Array<Models::EaObject>] All protected objects
201
+ def protected_objects
202
+ find_by_visibility("Protected")
203
+ end
204
+
205
+ # Search objects by name or alias
206
+ #
207
+ # @param query [String] Search query
208
+ # @return [Array<Models::EaObject>] Matching objects
209
+ def search(query)
210
+ query_downcase = query.downcase
211
+ where do |obj|
212
+ obj.name&.downcase&.include?(query_downcase) ||
213
+ obj.alias&.downcase&.include?(query_downcase)
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Repositories
6
+ autoload :BaseRepository, "ea/qea/repositories/base_repository"
7
+ autoload :ObjectRepository, "ea/qea/repositories/object_repository"
8
+ end
9
+ end
10
+ end