Class: Lutaml::Store::DatabaseStore

Inherits:
Object
  • Object
show all
Defined in:
lib/lutaml/store/database_store.rb

Overview

Store-centric API with model registry and database-style operations

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(adapter:, models: [], **options) ⇒ DatabaseStore

Returns a new instance of DatabaseStore.



9
10
11
12
13
14
15
16
17
# File 'lib/lutaml/store/database_store.rb', line 9

def initialize(adapter:, models: [], **options)
  @store = BasicStore.new(adapter_type: adapter, **options)
  @registry = ModelRegistry.new(models)
  @serializer = ModelSerializer.new
  @composite_handler = CompositeModelHandler.new(@registry, @store, self, serializer: @serializer)
  @attribute_updater = AttributeUpdater.new(@registry, @composite_handler)

  validate_configuration!
end

Instance Attribute Details

#attribute_updaterObject (readonly)

Returns the value of attribute attribute_updater.



7
8
9
# File 'lib/lutaml/store/database_store.rb', line 7

def attribute_updater
  @attribute_updater
end

#composite_handlerObject (readonly)

Returns the value of attribute composite_handler.



7
8
9
# File 'lib/lutaml/store/database_store.rb', line 7

def composite_handler
  @composite_handler
end

#registryObject (readonly)

Returns the value of attribute registry.



7
8
9
# File 'lib/lutaml/store/database_store.rb', line 7

def registry
  @registry
end

#storeObject (readonly)

Returns the value of attribute store.



7
8
9
# File 'lib/lutaml/store/database_store.rb', line 7

def store
  @store
end

Instance Method Details

#all(model:) ⇒ Object

Get all models of a specific type



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/lutaml/store/database_store.rb', line 105

def all(model:)
  registration = @registry.registration_for(model)

  models = []
  @store.keys.each do |storage_key|
    parsed = StorageKey.parse(storage_key.to_s)
    next unless parsed.class_name == model.name

    stored_data = @store.get(storage_key)
    next unless stored_data

    begin
      model_instance = @serializer.deserialize(stored_data, model, registration)

      composite_references = stored_data["_composite_models"]
      if composite_references
        model_instance = @composite_handler.restore_composite_models(
          model_instance, composite_references
        )
      end

      models << model_instance
    rescue StandardError => e
      @store.emit_event(:deserialization_error, key: storage_key, error: e)
    end
  end

  models
end

#closeObject



232
233
234
# File 'lib/lutaml/store/database_store.rb', line 232

def close
  @store.close
end

#count(model:) ⇒ Object



139
140
141
# File 'lib/lutaml/store/database_store.rb', line 139

def count(model:)
  all(model: model).size
end

#destroy(model:, **key_params) ⇒ Object

Destroy model by class and key field

Raises:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/lutaml/store/database_store.rb', line 79

def destroy(model:, **key_params)
  registration = @registry.registration_for(model)
  key_field = registration.key_field
  key_value = key_params[key_field]
  raise InvalidKeyError, "Key field '#{key_field}' not provided" if key_value.nil?

  model_instance = fetch(model: model, **key_params)
  return false unless model_instance

  @composite_handler.delete_composite_models(model_instance)

  storage_key = registration.generate_storage_key_from_value(key_value)
  deleted = @store.delete(storage_key)

  @store.emit_event(:model_destroy, model: model, key: key_value, deleted: deleted)
  deleted
end

#exists?(model:, **key_params) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
# File 'lib/lutaml/store/database_store.rb', line 135

def exists?(model:, **key_params)
  !fetch(model: model, **key_params).nil?
end

#export(models, path:, format: :yaml) ⇒ Object

Export models to a single file or directory.



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/lutaml/store/database_store.rb', line 202

def export(models, path:, format: :yaml)
  fmt = Format.resolve(format)
  models_array = Array(models)

  FileUtils.mkdir_p(File.dirname(path))

  content = fmt.serialize_many(models_array)

  File.write(path, content, encoding: "utf-8")
  @store.emit_event(:model_export, count: models_array.size, path: path)
  path
end

#fetch(model:, **key_params) ⇒ Object

Fetch model by class and key field

Raises:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/lutaml/store/database_store.rb', line 29

def fetch(model:, **key_params)
  registration = @registry.registration_for(model)
  key_field = registration.key_field
  key_value = key_params[key_field]
  raise InvalidKeyError, "Key field '#{key_field}' not provided" if key_value.nil?

  stored_data = find_stored_data(registration, model, key_value)
  return nil unless stored_data

  model_instance = @serializer.deserialize(stored_data, model, registration)

  composite_references = stored_data["_composite_models"]
  if composite_references
    model_instance = @composite_handler.restore_composite_models(
      model_instance, composite_references
    )
  end

  @store.emit_event(:model_fetch, model: model_instance, key: key_value, source: :backend)
  model_instance
end

#import_all(model_class, path: nil, format: :yaml, layout: :separate) ⇒ Object

Load from directory and store into the key-value backend. Returns the loaded models and makes them available via fetch/where/all.



166
167
168
169
170
171
# File 'lib/lutaml/store/database_store.rb', line 166

def import_all(model_class, path: nil, format: :yaml, layout: :separate)
  models = load_all(model_class, path: path, format: format, layout: layout)
  models.each { |model| save(model) }
  @store.emit_event(:model_import, count: models.size, path: path)
  models
end

#load_all(model_class, path: nil, format: :yaml, layout: :separate) ⇒ Object

Load all models of a type from a directory using format-specific serialization. Bypasses the key-value layer and reads files directly using the format handler.

Raises:



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/lutaml/store/database_store.rb', line 145

def load_all(model_class, path: nil, format: :yaml, layout: :separate)
  fmt = Format.resolve(format)
  dir = resolve_model_dir(model_class, path)

  raise BackendError, "No directory specified for load_all" unless dir
  raise BackendError, "Directory not found: #{dir}" unless Dir.exist?(dir)

  case layout
  when :separate
    load_separate(dir, model_class, fmt)
  when :grouped
    load_grouped(dir, model_class, fmt)
  when :flat
    load_flat(dir, model_class, fmt)
  else
    raise ConfigurationError, "Unknown layout: #{layout}"
  end
end

#off(event, listener) ⇒ Object



219
220
221
# File 'lib/lutaml/store/database_store.rb', line 219

def off(event, listener)
  @store.off(event, listener)
end

#on(event, &block) ⇒ Object



215
216
217
# File 'lib/lutaml/store/database_store.rb', line 215

def on(event, &block)
  @store.on(event, &block)
end

#save(models) ⇒ Object

Save single model or array of models



20
21
22
23
24
25
26
# File 'lib/lutaml/store/database_store.rb', line 20

def save(models)
  models_array = Array(models)
  saved_models = models_array.map { |model| save_single_model(model) }

  @store.emit_event(:model_save, models: saved_models, count: saved_models.size)
  models.is_a?(Array) ? saved_models : saved_models.first
end

#save_all(models, path: nil, format: :yaml, layout: :separate) ⇒ Object

Save all models to a directory using format-specific serialization.

Raises:



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/lutaml/store/database_store.rb', line 174

def save_all(models, path: nil, format: :yaml, layout: :separate)
  fmt = Format.resolve(format)
  models_array = Array(models)
  return [] if models_array.empty?

  model_class = models_array.first.class
  dir = resolve_model_dir(model_class, path)

  raise BackendError, "No directory specified for save_all" unless dir

  FileUtils.mkdir_p(dir)

  saved = case layout
          when :separate
            save_separate(models_array, dir, fmt)
          when :grouped
            save_grouped(models_array, dir, fmt, model_class)
          when :flat
            save_flat(models_array, dir, fmt)
          else
            raise ConfigurationError, "Unknown layout: #{layout}"
          end

  @store.emit_event(:model_save_all, models: saved, count: saved.size, path: dir)
  saved
end

#statsObject



223
224
225
226
227
228
229
230
# File 'lib/lutaml/store/database_store.rb', line 223

def stats
  base_stats = @store.stats
  base_stats.merge(
    models_registered: @registry.count,
    registered_models: @registry.registered_models.map(&:name),
    total_models: total_model_count
  )
end

#update(model:, attributes: nil, **key_params, &block) ⇒ Object

Update model with attributes array or block



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
# File 'lib/lutaml/store/database_store.rb', line 52

def update(model:, attributes: nil, **key_params, &block)
  current_model = fetch(model: model, **key_params)
  raise ModelNotRegisteredError, "Model not found" unless current_model

  updated_model = if block_given?
                    @attribute_updater.update_with_block(current_model, &block)
                  elsif attributes
                    if attributes.is_a?(Hash)
                      @attribute_updater.update_with_hash(current_model, attributes)
                    else
                      @attribute_updater.update_with_attributes(current_model, attributes)
                    end
                  else
                    raise ArgumentError, "Either attributes or block must be provided"
                  end

  save(updated_model)

  @store.emit_event(:model_update,
                    model: updated_model,
                    key: key_params,
                    changes: extract_changes(current_model, updated_model))

  updated_model
end

#where(model:, **conditions) ⇒ Object

Query operations



98
99
100
101
102
# File 'lib/lutaml/store/database_store.rb', line 98

def where(model:, **conditions)
  all(model: model).select do |model_instance|
    conditions.all? { |field, value| model_instance.public_send(field) == value }
  end
end