Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ Metrics/ClassLength:
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def rename_collections(renames)

def rename_collection(current_name, new_name)
# Check collection exists
collection = get_collection(current_name)
get_collection(current_name)

return unless current_name != new_name

Expand All @@ -64,17 +64,6 @@ def rename_collection(current_name, new_name)
"Cannot rename a collection twice: #{@to_child_name[current_name]}->#{current_name}->#{new_name}"
end

polymorphic_relations = %w[PolymorphicOneToOne PolymorphicOneToMany]
collection.schema[:fields].each do |field_name, field_schema|
next unless polymorphic_relations.include?(field_schema.type)

reverse_relation_name = Utils::Collection.get_inverse_relation(get_collection(current_name), field_name)

raise Exceptions::ForestException,
"Cannot rename collection #{current_name} because it's a target of a polymorphic relation " \
"'#{field_schema.foreign_collection}.#{reverse_relation_name}'"
end

@from_child_name[current_name] = new_name
@to_child_name[new_name] = current_name

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,176 @@ def name
datasource.get_collection_name(super)
end

def refine_schema(sub_schema)
fields = {}
def list(caller, filter = nil, projection = nil)
refined_filter = refine_filter(caller, filter)
records = @child_collection.list(caller, refined_filter, projection)

transform_records_polymorphic_values(records)
end

def create(caller, data)
transformed_data = transform_data_polymorphic_values(data)
result = @child_collection.create(caller, transformed_data)

transform_record_polymorphic_values(result)
end

def update(caller, filter, patch)
refined_filter = refine_filter(caller, filter)
transformed_patch = transform_data_polymorphic_values(patch)

@child_collection.update(caller, refined_filter, transformed_patch)
end

def refine_filter(_caller, filter)
return filter unless filter&.condition_tree

sub_schema[:fields].each do |name, old_schema|
if old_schema.type != 'Column' && old_schema.type != 'PolymorphicManyToOne'
old_schema.foreign_collection = datasource.get_collection_name(old_schema.foreign_collection)
if old_schema.type == 'ManyToMany'
old_schema.through_collection = datasource.get_collection_name(old_schema.through_collection)
type_fields = polymorphic_type_fields

transformed_tree = filter.condition_tree.replace_leafs do |leaf|
if type_fields.include?(leaf.field)
transformed_value = transform_polymorphic_value(leaf.value)
if transformed_value == leaf.value
leaf
else
leaf.override(value: transformed_value)
end
else
leaf
end
end

filter.override(condition_tree: transformed_tree)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 8): refine_filter [qlty:function-complexity]

end

private

def polymorphic_type_fields
type_fields = []
child_schema = @child_collection.schema
return type_fields unless child_schema && child_schema[:fields]

child_schema[:fields].each_value do |field_schema|
case field_schema.type
when 'PolymorphicManyToOne'
type_fields << field_schema.foreign_key_type_field
end
end
type_fields
end

def transform_data_polymorphic_values(data)
return data unless data

type_fields = polymorphic_type_fields
transformed_data = data.dup

type_fields.each do |type_field|
next unless transformed_data.key?(type_field)

original_value = transformed_data[type_field]
transformed_value = reverse_collection_name(original_value)
transformed_data[type_field] = transformed_value if transformed_value != original_value
end

transformed_data
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 6): transform_data_polymorphic_values [qlty:function-complexity]

end

def transform_polymorphic_value(value)
return value unless value

# Handle both single values and arrays (for IN/NOT_IN operators)
if value.is_a?(Array)
value.map { |v| reverse_collection_name(v) }
else
reverse_collection_name(value)
end
end

fields[name] = old_schema
# convert new collection name back to old name for db queries
def reverse_collection_name(collection_name)
to_child_name = datasource.instance_variable_get(:@to_child_name)
to_child_name[collection_name] || collection_name
end

# convert old collection name to new name for returned data
def forward_collection_name(collection_name)
from_child_name = datasource.instance_variable_get(:@from_child_name)
from_child_name[collection_name] || collection_name
end

def transform_records_polymorphic_values(records)
return records unless records.is_a?(Array)

type_fields = polymorphic_type_fields
return records if type_fields.empty?

records.map do |record|
transform_record_polymorphic_values(record)
end
end

def transform_record_polymorphic_values(record)
return record unless record.is_a?(Hash)

type_fields = polymorphic_type_fields
return record if type_fields.empty?

transformed_record = record.dup

type_fields.each do |type_field|
next unless transformed_record.key?(type_field)

old_value = transformed_record[type_field]
new_value = forward_collection_name(old_value)
transformed_record[type_field] = new_value if new_value != old_value
end

transformed_record
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 7): transform_record_polymorphic_values [qlty:function-complexity]

end

protected

def refine_schema(sub_schema)
current_collection_name = @child_collection.name

sub_schema[:fields].each_value do |old_schema|
case old_schema.type
when 'PolymorphicOneToOne', 'PolymorphicOneToMany'
refine_polymorphic_one_schema(old_schema, current_collection_name)
when 'PolymorphicManyToOne'
refine_polymorphic_many_schema(old_schema)
when 'ManyToOne', 'OneToMany', 'OneToOne', 'ManyToMany'
refine_standard_relation_schema(old_schema)
end
end

sub_schema
end

def refine_polymorphic_one_schema(schema, current_collection_name)
if schema.origin_type_value == current_collection_name
schema.origin_type_value = datasource.get_collection_name(current_collection_name)
end
schema.foreign_collection = datasource.get_collection_name(schema.foreign_collection)
end

def refine_polymorphic_many_schema(schema)
schema.foreign_collections = schema.foreign_collections.map { |fc| datasource.get_collection_name(fc) }
schema.foreign_key_targets = schema.foreign_key_targets.transform_keys do |key|
datasource.get_collection_name(key)
end
end

def refine_standard_relation_schema(schema)
schema.foreign_collection = datasource.get_collection_name(schema.foreign_collection)
return unless schema.type == 'ManyToMany'

schema.through_collection = datasource.get_collection_name(schema.through_collection)
end

public

# rubocop:disable Lint/UselessMethodDefinition
def mark_schema_as_dirty
super
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,48 @@ module RenameCollection
end

describe 'rename_collection' do
it 'raise an error when collection has polymorphic relation' do
expect { @datasource.rename_collection('user', 'renamed_user') }.to raise_error(
Exceptions::ForestException,
"Cannot rename collection user because it's a target of a polymorphic relation 'address.addressable'"
it 'successfully renames collection with polymorphic relations' do
@datasource.rename_collection('user', 'renamed_user')

# Check that the collection is renamed
expect(@datasource.collections.keys).to include('renamed_user')
expect(@datasource.collections.keys).not_to include('user')

# Check that the PolymorphicManyToOne relation is updated
address_collection = @datasource.get_collection('address')
addressable_field = address_collection.schema[:fields]['addressable']

expect(addressable_field.foreign_collections).to eq(['renamed_user'])
expect(addressable_field.foreign_key_targets.keys).to eq(['renamed_user'])
end

it 'updates origin_type_value when renaming a collection that is referenced' do
# First, let's create a scenario where user is the origin_type_value
@collection_user_v2 = build_collection(
name: 'user',
schema: {
fields: {
'id' => build_numeric_primary_key,
'email' => build_column,
'address' => Relations::PolymorphicOneToOneSchema.new(
origin_key: 'addressable_id',
foreign_collection: 'address',
origin_key_target: 'id',
origin_type_field: 'addressable_type',
origin_type_value: 'user' # References itself
)
}
}
)

@datasource_v2 = described_class.new(build_datasource_with_collections([@collection_user_v2, @collection_address]))
@datasource_v2.rename_collection('user', 'renamed_user')

# Check that origin_type_value is updated
renamed_user_collection = @datasource_v2.get_collection('renamed_user')
address_field = renamed_user_collection.schema[:fields]['address']

expect(address_field.origin_type_value).to eq('renamed_user')
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module ForestAdminDatasourceToolkit
module Schema
module Relations
class PolymorphicManyToOneSchema
attr_reader :foreign_key_target, :foreign_key, :foreign_key_targets, :foreign_key_type_field,
:foreign_collections, :type
attr_reader :foreign_key_target, :foreign_key, :foreign_key_type_field, :type
attr_accessor :foreign_key_targets, :foreign_collections

def initialize(
foreign_key_type_field:,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module ForestAdminDatasourceToolkit
module Schema
module Relations
class PolymorphicOneToManySchema < RelationSchema
attr_accessor :origin_key
attr_reader :origin_key_target, :origin_type_field, :origin_type_value
attr_accessor :origin_key, :origin_type_value
attr_reader :origin_key_target, :origin_type_field

def initialize(origin_key:, origin_key_target:, foreign_collection:, origin_type_field:, origin_type_value:)
super(foreign_collection, 'PolymorphicOneToMany')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ module ForestAdminDatasourceToolkit
module Schema
module Relations
class PolymorphicOneToOneSchema < RelationSchema
attr_accessor :origin_key
attr_reader :origin_key_target, :origin_type_field, :origin_type_value
attr_accessor :origin_key, :origin_type_value
attr_reader :origin_key_target, :origin_type_field

def initialize(origin_key:, origin_key_target:, foreign_collection:, origin_type_field:, origin_type_value:)
super(foreign_collection, 'PolymorphicOneToOne')
Expand Down