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

#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:



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 259

def get_all_associations # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
  all_associations = []

  # Get all associations defined at document level and
  if document.respond_to?(:associations) && document.associations
    all_associations << document.associations
  end

  # Get all associations defined within classes
  indexes[:qualified_names].each_value do |entity|
    next unless entity.respond_to?(:associations) && entity.associations

    all_associations << entity.associations
  end

  all_associations.flatten.uniq
end

#regex_pattern_from_query(query, case_sensitive: false) ⇒ Object



199
200
201
202
203
204
205
206
207
208
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 199

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:



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 282

def search_associations(query, # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  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 = nil

    fields.each do |field|
      if assoc.respond_to?(field) && assoc.send(field)&.match?(pattern)
        match_field = field
      end
    end

    if 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
  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:



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 215

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

  indexes[:qualified_names].filter_map do |class_qname, entity| # rubocop:disable Metrics/BlockLength
    next unless entity.respond_to?(:attributes) && entity.attributes

    match_field = nil
    match_attr = nil
    qualified_name = nil

    entity.attributes.each do |attr|
      # Check attribute for match
      fields.each do |field|
        if attr.respond_to?(field) &&
            attr.send(field)&.match?(pattern)

          match_attr = attr
          match_field = field
          qualified_name = class_qname
        end
      end
    end

    if match_field
      SearchResult.new(
        element: match_attr,
        element_type: :attribute,
        qualified_name: "#{qualified_name}::#{match_attr.name}",
        package_path: extract_package_path(qualified_name),
        match_field: match_field,
        match_context: {
          "class_name" => entity&.name,
          "class_qname" => qualified_name,
        },
      )
    end
  end.uniq
end

#search_by_stereotype(query, case_sensitive: false) ⇒ Object

rubocop:disable Metrics/AbcSize,Metrics/MethodLength



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 152

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

  matched_entities = indexes[:stereotypes]
    .filter_map do |_stereotype, entities|
    entities.select do |entity|
      entity.respond_to?(:stereotype) &&
        entity.stereotype&.match?(pattern)
    end.uniq
  end.uniq.flatten

  matched_entities.map do |entity|
    SearchResult.new(
      element: entity,
      element_type: entity.class.name.split("::").last.downcase,
      qualified_name: "",
      package_path: "",
      match_field: :stereotype,
    )
  end
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 116

def search_classes( # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
  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|
    match_field = nil
    qualified_name = nil

    next unless entity.is_a?(Lutaml::Uml::Class)

    # Check fields for match
    fields.each do |field|
      if entity.respond_to?(field) &&
          entity.send(field)&.match?(pattern)

        match_field = field
        qualified_name = qname
      end
    end

    if match_field
      SearchResult.new(
        element: entity,
        element_type: :class,
        qualified_name: qualified_name,
        package_path: extract_package_path(qualified_name),
        match_field: match_field,
      )
    end
  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:



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/lutaml/uml_repository/queries/search_query.rb', line 181

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