From 38faee8a8af39ade73ddce97899b1f8d6ddbb7bb Mon Sep 17 00:00:00 2001 From: Niklas van Schrick Date: Sun, 1 Feb 2026 01:15:17 +0100 Subject: [PATCH] Link generic types to their owning types --- app/models/data_type.rb | 2 + app/models/data_type_rule.rb | 2 + app/models/generic_mapper.rb | 2 + app/models/generic_type.rb | 1 + app/models/runtime_function_definition.rb | 2 + app/models/runtime_parameter_definition.rb | 2 + .../runtimes/grpc/data_type_helper.rb | 41 ++++++++++--------- .../grpc/data_types/update_service.rb | 19 ++++++--- .../update_service.rb | 8 +++- ...0260131170109_add_owner_to_generic_type.rb | 7 ++++ db/schema_migrations/20260131170109 | 1 + db/structure.sql | 6 ++- spec/factories/generic_types.rb | 1 + ...untime_function_definition_service_spec.rb | 11 ++--- 14 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 db/migrate/20260131170109_add_owner_to_generic_type.rb create mode 100644 db/schema_migrations/20260131170109 diff --git a/app/models/data_type.rb b/app/models/data_type.rb index 5db62631..08262887 100644 --- a/app/models/data_type.rb +++ b/app/models/data_type.rb @@ -21,6 +21,8 @@ class DataType < ApplicationRecord has_many :data_type_identifiers, class_name: 'DataTypeIdentifier', inverse_of: :data_type has_many :generic_types, class_name: 'GenericType', inverse_of: :data_type + has_many :owned_generic_types, class_name: 'GenericType', inverse_of: :owner + has_many :display_messages, -> { by_purpose(:display_message) }, class_name: 'Translation', as: :owner, inverse_of: :owner has_many :aliases, -> { by_purpose(:alias) }, class_name: 'Translation', as: :owner, inverse_of: :owner diff --git a/app/models/data_type_rule.rb b/app/models/data_type_rule.rb index dbfd8d48..c0a0bcd0 100644 --- a/app/models/data_type_rule.rb +++ b/app/models/data_type_rule.rb @@ -16,6 +16,8 @@ class DataTypeRule < ApplicationRecord belongs_to :data_type, inverse_of: :rules + has_many :owned_generic_types, class_name: 'GenericType', inverse_of: :owner + validates :variant, presence: true, inclusion: { in: VARIANTS.keys.map(&:to_s), diff --git a/app/models/generic_mapper.rb b/app/models/generic_mapper.rb index cb7fbf5c..b6d77252 100644 --- a/app/models/generic_mapper.rb +++ b/app/models/generic_mapper.rb @@ -8,6 +8,8 @@ class GenericMapper < ApplicationRecord has_many :sources, class_name: 'DataTypeIdentifier', inverse_of: :generic_mapper has_many :generic_combination_strategies, class_name: 'GenericCombinationStrategy', inverse_of: :generic_mapper + has_many :owned_generic_types, class_name: 'GenericType', inverse_of: :owner + validates :target, presence: true def to_grpc diff --git a/app/models/generic_type.rb b/app/models/generic_type.rb index 7c1291f3..5d1a98c9 100644 --- a/app/models/generic_type.rb +++ b/app/models/generic_type.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class GenericType < ApplicationRecord + belongs_to :owner, polymorphic: true belongs_to :data_type, class_name: 'DataType', inverse_of: :generic_types has_many :generic_mappers, inverse_of: :generic_type diff --git a/app/models/runtime_function_definition.rb b/app/models/runtime_function_definition.rb index 4fcd74e5..2a830504 100644 --- a/app/models/runtime_function_definition.rb +++ b/app/models/runtime_function_definition.rb @@ -18,6 +18,8 @@ class RuntimeFunctionDefinition < ApplicationRecord class_name: 'Translation', as: :owner, inverse_of: :owner has_many :aliases, -> { by_purpose(:alias) }, class_name: 'Translation', as: :owner, inverse_of: :owner + has_many :owned_generic_types, class_name: 'GenericType', inverse_of: :owner + validates :runtime_name, presence: true, length: { minimum: 3, maximum: 50 }, uniqueness: { case_sensitive: false, scope: :runtime_id } diff --git a/app/models/runtime_parameter_definition.rb b/app/models/runtime_parameter_definition.rb index ad47dbb7..d3638264 100644 --- a/app/models/runtime_parameter_definition.rb +++ b/app/models/runtime_parameter_definition.rb @@ -10,6 +10,8 @@ class RuntimeParameterDefinition < ApplicationRecord has_many :parameter_definitions, inverse_of: :runtime_parameter_definition + has_many :owned_generic_types, class_name: 'GenericType', inverse_of: :owner + validates :runtime_name, length: { minimum: 3, maximum: 50 }, presence: true, uniqueness: { case_sensitive: false, scope: :runtime_function_definition_id } end diff --git a/app/services/runtimes/grpc/data_type_helper.rb b/app/services/runtimes/grpc/data_type_helper.rb index 9dd1a58f..5e6733a6 100644 --- a/app/services/runtimes/grpc/data_type_helper.rb +++ b/app/services/runtimes/grpc/data_type_helper.rb @@ -6,15 +6,18 @@ module DataTypeHelper # This method updates or creates GenericMappers based on the provided gRPC GenericMapper objects # within the current runtime. # @param generic_mappers [Array] An array of gRPC GenericMapper objects. - def update_mappers(generic_mappers, t) + def update_mappers(generic_mappers, generic_type, t) raise 'Including class must define current_runtime' unless respond_to?(:current_runtime) generic_mappers.to_a.map do |generic_mapper| - mapper = GenericMapper.create_or_find_by(runtime: current_runtime, - target: generic_mapper.target, - sources: generic_mapper.source.map do |source| - find_data_type_identifier(source, t) - end) + mapper = GenericMapper.find_by(runtime: current_runtime, generic_type: generic_type) + + mapper = GenericMapper.new(runtime: current_runtime, generic_type: generic_type) if mapper.nil? + + mapper.target = generic_mapper.target + mapper.sources = generic_mapper.source.map do |source| + find_data_type_identifier(source, mapper, t, additional_dti_kwargs: { generic_mapper: mapper }) + end if mapper.nil? || !mapper.save t.rollback_and_return! ServiceResponse.error( @@ -28,22 +31,19 @@ def update_mappers(generic_mappers, t) # This method finds or creates a DataTypeIdentifier based on the provided identifier within the current runtime. # @param identifier [Tucana::Sagittarius::DataTypeIdentifier] The gRPC DataTypeIdentifier object. - def find_data_type_identifier(identifier, t) + def find_data_type_identifier(identifier, owner, t, additional_dti_kwargs: {}) if identifier.data_type_identifier.present? - return create_data_type_identifier(t, data_type_id: find_data_type(identifier.data_type_identifier, t).id) + return create_data_type_identifier( + t, + **additional_dti_kwargs, + data_type_id: find_data_type(identifier.data_type_identifier, t).id + ) end if identifier.generic_type.present? data_type = find_data_type(identifier.generic_type.data_type_identifier, t) - generic_type = GenericType.find_by( - data_type: data_type - ) - if generic_type.nil? - generic_type = GenericType.create( - data_type: data_type - ) - end + generic_type = owner.owned_generic_types.find_or_initialize_by(data_type: data_type) if generic_type.nil? t.rollback_and_return! ServiceResponse.error( @@ -52,13 +52,14 @@ def find_data_type_identifier(identifier, t) ) end - generic_type.assign_attributes(generic_mappers: update_mappers(identifier.generic_type.generic_mappers, - t)) + generic_type.generic_mappers = update_mappers(identifier.generic_type.generic_mappers, generic_type, t) - return create_data_type_identifier(t, generic_type_id: generic_type.id) + return create_data_type_identifier(t, **additional_dti_kwargs, generic_type_id: generic_type.id) end - return create_data_type_identifier(t, generic_key: identifier.generic_key) if identifier.generic_key.present? + if identifier.generic_key.present? + return create_data_type_identifier(t, **additional_dti_kwargs, generic_key: identifier.generic_key) + end raise ArgumentError, "Invalid identifier: #{identifier.inspect}" end diff --git a/app/services/runtimes/grpc/data_types/update_service.rb b/app/services/runtimes/grpc/data_types/update_service.rb index 9377aa37..bd697e77 100644 --- a/app/services/runtimes/grpc/data_types/update_service.rb +++ b/app/services/runtimes/grpc/data_types/update_service.rb @@ -122,7 +122,11 @@ def update_datatype(data_type, t) db_object.removed_at = nil db_object.variant = data_type.variant.to_s.downcase if parent?(data_type) - db_object.parent_type = find_data_type_identifier(find_parent_rule(data_type).rule_config.parent_type, t) + db_object.parent_type = find_data_type_identifier( + find_parent_rule(data_type).rule_config.parent_type, + db_object, + t + ) end db_object.rules = update_rules(data_type.rules, db_object, t) db_object.names = update_translations(data_type.name, db_object.names) @@ -142,13 +146,16 @@ def update_rules(rules, data_type, t) db_rules = data_type.rules.first(rules.length) rules.each_with_index do |rule, index| db_rules[index] ||= DataTypeRule.new - db_rules[index].assign_attributes(variant: rule.variant.to_s.downcase, config: extend_rule_config(rule, t)) + db_rules[index].assign_attributes( + variant: rule.variant.to_s.downcase, + config: extend_rule_config(rule, db_rules[index], t) + ) end db_rules end - def extend_rule_config(rule, t) + def extend_rule_config(rule, db_rule, t) case rule.variant when :parent_type {} @@ -156,12 +163,12 @@ def extend_rule_config(rule, t) { key: rule.rule_config.key, data_type_identifier: rule.rule_config.data_type_identifier, - data_type_identifier_id: find_data_type_identifier(rule.rule_config.data_type_identifier, t).id, + data_type_identifier_id: find_data_type_identifier(rule.rule_config.data_type_identifier, db_rule, t).id, } when :contains_type, :return_type { data_type_identifier: rule.rule_config.data_type_identifier, - data_type_identifier_id: find_data_type_identifier(rule.rule_config.data_type_identifier, t).id, + data_type_identifier_id: find_data_type_identifier(rule.rule_config.data_type_identifier, db_rule, t).id, } when :input_types { @@ -169,7 +176,7 @@ def extend_rule_config(rule, t) { input_identifier: input_type.input_identifier, data_type_identifier: input_type.data_type_identifier, - data_type_identifier_id: find_data_type_identifier(input_type.data_type_identifier, t).id, + data_type_identifier_id: find_data_type_identifier(input_type.data_type_identifier, db_rule, t).id, } end, } diff --git a/app/services/runtimes/grpc/runtime_function_definitions/update_service.rb b/app/services/runtimes/grpc/runtime_function_definitions/update_service.rb index 532c5b02..2a630504 100644 --- a/app/services/runtimes/grpc/runtime_function_definitions/update_service.rb +++ b/app/services/runtimes/grpc/runtime_function_definitions/update_service.rb @@ -53,7 +53,11 @@ def update_runtime_function_definition(runtime_function_definition, t) ) db_object.removed_at = nil db_object.return_type = if runtime_function_definition.return_type_identifier.present? - find_data_type_identifier(runtime_function_definition.return_type_identifier, t) + find_data_type_identifier( + runtime_function_definition.return_type_identifier, + db_object, + t + ) end db_object.names = update_translations(runtime_function_definition.name, db_object.names) db_object.descriptions = update_translations(runtime_function_definition.description, db_object.descriptions) @@ -106,7 +110,7 @@ def update_parameters(runtime_function_definition, parameters, db_parameters, t) db_param.runtime_function_definition = runtime_function_definition db_param.runtime_name = real_param.runtime_name db_param.removed_at = nil - db_param.data_type = find_data_type_identifier(real_param.data_type_identifier, t) + db_param.data_type = find_data_type_identifier(real_param.data_type_identifier, db_param, t) db_param.names = update_translations(real_param.name, db_param.names) db_param.descriptions = update_translations(real_param.description, db_param.descriptions) diff --git a/db/migrate/20260131170109_add_owner_to_generic_type.rb b/db/migrate/20260131170109_add_owner_to_generic_type.rb new file mode 100644 index 00000000..656b15d1 --- /dev/null +++ b/db/migrate/20260131170109_add_owner_to_generic_type.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddOwnerToGenericType < Code0::ZeroTrack::Database::Migration[1.0] + def change + add_reference :generic_types, :owner, polymorphic: true + end +end diff --git a/db/schema_migrations/20260131170109 b/db/schema_migrations/20260131170109 new file mode 100644 index 00000000..484b5d62 --- /dev/null +++ b/db/schema_migrations/20260131170109 @@ -0,0 +1 @@ +c6ed271c0a902a25fb5a7ca5e7137a0fefa65ca1f652368cce2e2599af9f2b96 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index ec628740..0838d67b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -320,7 +320,9 @@ CREATE TABLE generic_types ( id bigint NOT NULL, data_type_id bigint NOT NULL, created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL + updated_at timestamp with time zone NOT NULL, + owner_type character varying, + owner_id bigint ); CREATE SEQUENCE generic_types_id_seq @@ -1125,6 +1127,8 @@ CREATE INDEX index_generic_mappers_on_runtime_id ON generic_mappers USING btree CREATE INDEX index_generic_types_on_data_type_id ON generic_types USING btree (data_type_id); +CREATE INDEX index_generic_types_on_owner ON generic_types USING btree (owner_type, owner_id); + CREATE INDEX index_good_job_executions_on_active_job_id_and_created_at ON good_job_executions USING btree (active_job_id, created_at); CREATE INDEX index_good_job_executions_on_process_id_and_created_at ON good_job_executions USING btree (process_id, created_at); diff --git a/spec/factories/generic_types.rb b/spec/factories/generic_types.rb index 32458025..6e7a9fdf 100644 --- a/spec/factories/generic_types.rb +++ b/spec/factories/generic_types.rb @@ -4,5 +4,6 @@ factory :generic_type do generic_mappers { [] } data_type + owner { data_type } end end diff --git a/spec/requests/grpc/sagittarius/runtime_function_definition_service_spec.rb b/spec/requests/grpc/sagittarius/runtime_function_definition_service_spec.rb index 75b253dd..7bc68161 100644 --- a/spec/requests/grpc/sagittarius/runtime_function_definition_service_spec.rb +++ b/spec/requests/grpc/sagittarius/runtime_function_definition_service_spec.rb @@ -14,10 +14,7 @@ create(:data_type_identifier, runtime: runtime, data_type: create(:data_type, runtime: runtime)) end - let!(:generic_type) do - create(:generic_type, data_type: create(:data_type, runtime: runtime)) - end - let!(:return_type) { create(:data_type_identifier, runtime: runtime, generic_type: generic_type.reload).reload } + let!(:generic_base_type) { create(:data_type, runtime: runtime) } let(:runtime_functions) do [ @@ -43,7 +40,7 @@ ], return_type_identifier: { generic_type: { - data_type_identifier: return_type.generic_type.data_type.identifier, + data_type_identifier: generic_base_type.identifier, generic_mappers: [{ source: [{ generic_key: 'T' }], target: 'V', generic_combinations: [] }], }, }, @@ -87,7 +84,7 @@ function = RuntimeFunctionDefinition.last expect(function.runtime_name).to eq('runtime_function_id') expect(function.return_type.generic_type.reload.data_type.identifier) - .to eq(return_type.generic_type.data_type.identifier) + .to eq(generic_base_type.identifier) expect(function.names.first.content).to eq('Eine Funktion') expect(function.descriptions.first.content).to eq('Eine Funktionsbeschreibung') expect(function.documentations.first.content).to eq('Eine Funktionsdokumentation') @@ -111,7 +108,7 @@ expect(function_definition.aliases.first.content).to eq('Ein Funktionsalias') expect(function_definition.display_messages.first.content).to eq('Eine Funktionsanzeige') expect(function_definition.return_type.generic_type.reload.data_type.identifier) - .to eq(return_type.generic_type.data_type.identifier) + .to eq(generic_base_type.identifier) parameter_definition = ParameterDefinition.first expect(parameter_definition.data_type.data_type.identifier).to eq(parameter_type.data_type.identifier) expect(parameter_definition.names.first.content).to eq('Ein Parameter')