Class: Lutaml::UmlRepository::Queries::SearchQuery

Inherits:
BaseQuery
  • Object
show all
Defined in:
lib/lutaml/uml_repository/queries/search_query.rb

Overview

Query service for search operations.

Provides methods for fuzzy text search and regex pattern matching across classes, attributes, and associations in the UML model.

Examples:

Searching for text

query = SearchQuery.new(document, indexes)
results = query.search("Building")
# => { classes: [...], attributes: [...], associations: [...],
total: 15 }

Pattern matching

results = query.search("^Urban", type: :class)

Instance Method Summary collapse

Methods inherited from BaseQuery

#initialize

Constructor Details

This class inherits a constructor from Lutaml::UmlRepository::Queries::BaseQuery

Instance Method Details

#build_attribute_result(attr, entity, class_qname, match_field) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 255

def build_attribute_result(attr, entity, class_qname, match_field)
  SearchResult.new(
    element: attr,
    element_type: :attribute,
    qualified_name: "#{class_qname}::#{attr.name}",
    package_path: extract_package_path(class_qname),
    match_field: match_field,
    match_context: {
      "class_name" => entity.name,
      "class_qname" => class_qname,
    },
  )
end

#build_search_result(entity, type, qname, match_field, context = {}) ⇒ Object



145
146
147
148
149
150
151
152
153
154
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 145

def build_search_result(entity, type, qname, match_field, context = {})
  SearchResult.new(
    element: entity,
    element_type: type,
    qualified_name: qname,
    package_path: extract_package_path(qname),
    match_field: match_field,
    match_context: context,
  )
end

#build_stereotype_result(entity) ⇒ Object



175
176
177
178
179
180
181
182
183
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 175

def build_stereotype_result(entity)
  SearchResult.new(
    element: entity,
    element_type: entity.class.name.split("::").last.downcase,
    qualified_name: "",
    package_path: "",
    match_field: :stereotype,
  )
end

#classifiable_with_associations?(entity) ⇒ Boolean

Returns:

  • (Boolean)


288
289
290
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 288

def classifiable_with_associations?(entity)
  (entity.is_a?(Lutaml::Uml::Class) || entity.is_a?(Lutaml::Uml::DataType)) && entity.associations
end

#find_entities_by_stereotype_pattern(pattern) ⇒ Object



165
166
167
168
169
170
171
172
173
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 165

def find_entities_by_stereotype_pattern(pattern)
  indexes[:stereotypes]
    .filter_map do |_stereotype, entities|
    entities.select do |entity|
      entity.is_a?(Lutaml::Uml::Classifier) &&
        Array(entity.stereotype).any? { |s| s&.match?(pattern) }
    end.uniq
  end.uniq.flatten
end

#find_matching_attribute(entity, fields, pattern) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 240

def find_matching_attribute(entity, fields, pattern)
  match_attr = nil
  match_field = nil
  entity.attributes.each do |attr|
    fields.each do |field|
      if attr.class.attributes.key?(field) &&
          attr.public_send(field)&.match?(pattern)
        match_attr = attr
        match_field = field
      end
    end
  end
  match_field ? [match_attr, match_field] : nil
end

#find_matching_field(entity, fields, pattern) ⇒ Object



134
135
136
137
138
139
140
141
142
143
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 134

def find_matching_field(entity, fields, pattern)
  last_match = nil
  fields.each do |field|
    if entity.class.attributes.key?(field) &&
        entity.public_send(field)&.match?(pattern)
      last_match = field
    end
  end
  last_match
end

#full_text_search(query_string, fields: [:name], case_sensitive: false) ⇒ Object

Perform a full-text search across all text fields. Searches for the query string in all relevant text fields of classes and packages.

Parameters:

  • query_string (String)

    The text to search for

  • fields (Array<Symbol>) (defaults to: [:name])

    Fields to search in - :name, :



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 87

def full_text_search( # rubocop:disable Metrics/MethodLength
  query_string,
  fields: [:name], case_sensitive: false
)
  results = empty_full_text_search_result
  if query_string.nil? || query_string.empty?
    return results
  end

  # Search classes
  results[:classes] = search_classes(
    query_string, fields: fields, case_sensitive: case_sensitive
  )

  # Search packages
  results[:packages] = search_packages(
    query_string, case_sensitive: case_sensitive
  )

  results[:total] = results[:classes].size + results[:packages].size

  results
end

#get_all_associationsArray<Lutaml::Uml::Association>

Get all associations in the model

Returns:



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 272

def get_all_associations
  all_associations = []

  if document.is_a?(Lutaml::Uml::Document) && document.associations
    all_associations << document.associations
  end

  indexes[:qualified_names].each_value do |entity|
    next unless classifiable_with_associations?(entity)

    all_associations << entity.associations
  end

  all_associations.flatten.uniq
end

#regex_pattern_from_query(query, case_sensitive: false) ⇒ Object



208
209
210
211
212
213
214
215
216
217
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 208

def regex_pattern_from_query(query, case_sensitive: false)
  # handle wildcard '*' and glob patterns
  query = query.gsub("*", ".*") unless query.include?(".*")

  if case_sensitive
    Regexp.new(query)
  else
    Regexp.new(query, Regexp::IGNORECASE)
  end
end

#search(query_string, types: %i[class attribute association],, fields: [:name], case_sensitive: false) ⇒ Hash

Perform a fuzzy text search across the model.

Searches for the query string in class names, attribute names, and association names. Can optionally search in documentation fields. The search is case-insensitive.

  • :name, :documentation (default: [:name])

Examples:

results = query.search("building")
results = query.search("urban", fields: [:name, :documentation])

Parameters:

  • query_string (String)

    The text to search for

  • types (Array<Symbol>) (defaults to: %i[class attribute association],)

    Types to search in - :class, :attribute, :association (default: [:class, :attribute, :association])

  • fields (Array<Symbol>) (defaults to: [:name])

    Fields to search in

Returns:

  • (Hash)

    Search results with keys :classes, :attributes, :associations, :total



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 40

def search( # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  query_string, types: %i[class attribute association],
                fields: [:name], case_sensitive: false
)
  return empty_result if query_string.nil? || query_string.empty?

  results = {
    classes: [],
    attributes: [],
    associations: [],
    total: 0,
  }

  # Search classes
  if types.include?(:class)
    results[:classes] = search_classes(
      query_string, fields: fields, case_sensitive: case_sensitive
    )
  end

  # Search attributes
  if types.include?(:attribute)
    results[:attributes] = search_attributes(
      query_string, fields: fields, case_sensitive: case_sensitive
    )
  end

  # Search associations
  if types.include?(:association)
    results[:associations] = search_associations(
      query_string, fields: fields, case_sensitive: case_sensitive
    )
  end

  results[:total] = results[:classes].size +
    results[:attributes].size +
    results[:associations].size

  results
end

#search_associations(query, fields: %i[ name owner_end member_end owner_end_attribute_name member_end_attribute_name documentation ],, case_sensitive: false) ⇒ Array<SearchResult>

Search for associations matching the query

Parameters:

  • query (String)

    Query string

  • fields (Array<Symbol>) (defaults to: %i[ name owner_end member_end owner_end_attribute_name member_end_attribute_name documentation ],)

    Fields to search in

Returns:



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 297

def search_associations(query, # rubocop:disable Metrics/MethodLength
  fields: %i[
    name owner_end member_end owner_end_attribute_name
    member_end_attribute_name documentation
  ],
  case_sensitive: false)
  all_associations = get_all_associations
  pattern = regex_pattern_from_query(
    query, case_sensitive: case_sensitive
  )

  all_associations.filter_map do |assoc|
    match_field = find_matching_field(assoc, fields, pattern)
    next unless match_field

    SearchResult.new(
      element: assoc,
      element_type: :association,
      qualified_name: assoc.name || "(unnamed)",
      package_path: "",
      match_field: match_field,
      match_context: {
        "source" => assoc.owner_end,
        "target" => assoc.member_end,
      },
    )
  end.uniq
end

#search_attributes(query, fields: [:name], case_sensitive: false) ⇒ Array<Lutaml::Uml::Class>

Search for attributes matching the query

Parameters:

  • query (String)

    Query string

  • fields (Array<Symbol>) (defaults to: [:name])

    Fields to search in

Returns:



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 224

def search_attributes(query, fields: [:name], case_sensitive: false) # rubocop:disable Metrics/MethodLength
  pattern = regex_pattern_from_query(
    query, case_sensitive: case_sensitive
  )

  indexes[:qualified_names].filter_map do |class_qname, entity|
    next unless entity.is_a?(Lutaml::Uml::Classifier) && entity.attributes

    match_attr, match_field = find_matching_attribute(entity, fields,
                                                      pattern)
    next unless match_field

    build_attribute_result(match_attr, entity, class_qname, match_field)
  end.uniq
end

#search_by_stereotype(query, case_sensitive: false) ⇒ Object



156
157
158
159
160
161
162
163
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 156

def search_by_stereotype(query, case_sensitive: false)
  pattern = regex_pattern_from_query(
    query, case_sensitive: case_sensitive
  )

  matched_entities = find_entities_by_stereotype_pattern(pattern)
  matched_entities.map { |entity| build_stereotype_result(entity) }
end

#search_classes(query, fields: %i[name documentation],, case_sensitive: false) ⇒ Array<SearchResult>

Search for classes matching the query

Parameters:

  • query (String)

    Query string

  • fields (Array<Symbol>) (defaults to: %i[name documentation],)

    Fields to search in

Returns:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 116

def search_classes( # rubocop:disable Metrics/MethodLength
  query, fields: %i[name documentation],
  case_sensitive: false
)
  pattern = regex_pattern_from_query(
    query, case_sensitive: case_sensitive
  )

  indexes[:qualified_names].filter_map do |qname, entity|
    next unless entity.is_a?(Lutaml::Uml::Class)

    match_field = find_matching_field(entity, fields, pattern)
    next unless match_field

    build_search_result(entity, :class, qname, match_field)
  end.uniq
end

#search_packages(query, case_sensitive: false) ⇒ Array<Lutaml::Uml::Package>

Search for packages matching the query

Parameters:

  • query (String)

    Query string

  • case_sensitive (Boolean) (defaults to: false)

    Whether the search is case-sensitive

Returns:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 190

def search_packages(query, case_sensitive: false) # rubocop:disable Metrics/MethodLength
  pattern = regex_pattern_from_query(
    query, case_sensitive: case_sensitive
  )

  indexes[:package_paths].filter_map do |path_string, package|
    if path_string.to_s.match?(pattern)
      SearchResult.new(
        element: package,
        element_type: :package,
        qualified_name: path_string,
        package_path: path_string,
        match_field: :package_path,
      )
    end
  end
end