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 @@ -329,6 +329,7 @@ Metrics/ClassLength:
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query_aggregate.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb'
- '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/publication/publication_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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def self.build_fields(collection)
ForestAdminDatasourceToolkit::Utils::Schema.primary_key?(collection, name)
end

fields.map { |name, _field| GeneratorField.build_schema(collection, name) }
fields.filter_map { |name, _field| GeneratorField.build_schema(collection, name) }
.sort_by { |v| v[:field] }
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def self.build_schema(collection, name)
build_relation_schema(collection, name)
end

return nil if schema.nil?

schema.sort_by { |k, _v| k }.to_h
end

Expand Down Expand Up @@ -81,6 +83,22 @@ def build_many_to_many_schema(relation, collection, foreign_collection, base_sch
foreign_schema = through_schema.schema[:fields][relation.foreign_key]
origin_key = through_schema.schema[:fields][relation.origin_key]

unless target_field
log_missing_relation_field(foreign_collection.name, target_name, 'foreign_key_target',
base_schema[:field])
return nil
end

unless foreign_schema
log_missing_relation_field(through_schema.name, relation.foreign_key, 'foreign_key', base_schema[:field])
return nil
end

unless origin_key
log_missing_relation_field(through_schema.name, relation.origin_key, 'origin_key', base_schema[:field])
return nil
end

base_schema.merge(
{
type: [target_field.column_type],
Expand All @@ -101,6 +119,17 @@ def build_one_to_many_schema(relation, collection, foreign_collection, base_sche
target_field = collection.schema[:fields][target_name]
origin_key = foreign_collection.schema[:fields][relation.origin_key]

unless target_field
log_missing_relation_field(collection.name, target_name, 'origin_key_target', base_schema[:field])
return nil
end

unless origin_key
log_missing_relation_field(foreign_collection.name, relation.origin_key, 'origin_key',
base_schema[:field])
return nil
end

base_schema.merge(
{
type: [target_field.column_type],
Expand All @@ -120,6 +149,18 @@ def build_one_to_one_schema(relation, collection, foreign_collection, base_schem
target_field = collection.schema[:fields][relation.origin_key_target]
key_field = foreign_collection.schema[:fields][relation.origin_key]

unless target_field
log_missing_relation_field(collection.name, relation.origin_key_target, 'origin_key_target',
base_schema[:field])
return nil
end

unless key_field
log_missing_relation_field(foreign_collection.name, relation.origin_key, 'origin_key',
base_schema[:field])
return nil
end

base_schema.merge(
{
type: key_field.column_type,
Expand All @@ -138,6 +179,11 @@ def build_one_to_one_schema(relation, collection, foreign_collection, base_schem
def build_many_to_one_schema(relation, collection, foreign_collection, base_schema)
key_field = collection.schema[:fields][relation.foreign_key]

unless key_field
log_missing_relation_field(collection.name, relation.foreign_key, 'foreign_key', base_schema[:field])
return nil
end

base_schema.merge(
{
type: key_field.column_type,
Expand Down Expand Up @@ -202,6 +248,13 @@ def build_relation_schema(collection, name)
end
end
end

def log_missing_relation_field(collection_name, field_name, field_type, relation_name)
Copy link

Choose a reason for hiding this comment

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

Function with many parameters (count = 4): log_missing_relation_field [qlty:function-parameters]

message = "Field '#{field_name}' (#{field_type}) not found in collection '#{collection_name}' " \
"for relation '#{relation_name}'. This relation will be skipped. " \
'Check if the field exists in your database.'
Facades::Container.logger.log('Warn', message)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,49 @@ module Schema
relationship: 'HasOne'
)
end

context 'when relations have missing fields' do
before do
@datasource_with_bad_relations = Datasource.new

collection_order = Collection.new(@datasource_with_bad_relations, 'Order')
collection_order.add_fields(
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'total' => ColumnSchema.new(column_type: 'Number'),
# Relation with missing foreign_key
'customer' => Relations::ManyToOneSchema.new(
foreign_key: 'missing_customer_id',
foreign_key_target: 'id',
foreign_collection: 'Customer'
)
}
)

collection_customer = Collection.new(@datasource_with_bad_relations, 'Customer')
collection_customer.add_fields(
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'name' => ColumnSchema.new(column_type: 'String')
}
)

@datasource_with_bad_relations.add_collection(collection_order)
@datasource_with_bad_relations.add_collection(collection_customer)
end

it 'filters out relations with missing fields from schema' do
logger = instance_spy(Logger)
allow(Facades::Container).to receive(:logger).and_return(logger)

schema = described_class.build_schema(@datasource_with_bad_relations.get_collection('Order'))

# Should only have 'id' and 'total', not 'customer' (which has missing foreign_key)
field_names = schema[:fields].map { |f| f[:field] }
expect(field_names).to contain_exactly('id', 'total')
expect(field_names).not_to include('customer')
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,100 @@ module Schema
]
)
end

context 'when relation has missing fields' do
before do
@datasource_with_bad_relations = Datasource.new

collection_order = Collection.new(@datasource_with_bad_relations, 'Order')
collection_order.add_fields(
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
# user_id field is missing but relation references it
'user' => Relations::ManyToOneSchema.new(
foreign_key: 'missing_user_id',
foreign_key_target: 'id',
foreign_collection: 'User'
)
}
)

collection_user = Collection.new(@datasource_with_bad_relations, 'User')
collection_user.add_fields(
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'name' => ColumnSchema.new(column_type: 'String')
}
)

@datasource_with_bad_relations.add_collection(collection_order)
@datasource_with_bad_relations.add_collection(collection_user)
end

it 'returns nil and logs a warning when foreign_key field is missing in ManyToOne relation' do
logger = instance_spy(Logger)
allow(Facades::Container).to receive(:logger).and_return(logger)

schema = described_class.build_schema(
@datasource_with_bad_relations.get_collection('Order'),
'user'
)

expect(schema).to be_nil
expect(logger).to have_received(:log).with(
'Warn',
"Field 'missing_user_id' (foreign_key) not found in collection 'Order' for relation 'user'. " \
'This relation will be skipped. Check if the field exists in your database.'
)
end
end

context 'when OneToMany relation has missing origin_key' do
before do
@datasource_with_bad_one_to_many = Datasource.new

collection_author = Collection.new(@datasource_with_bad_one_to_many, 'Author')
collection_author.add_fields(
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'books' => Relations::OneToManySchema.new(
origin_key: 'missing_author_id',
origin_key_target: 'id',
foreign_collection: 'Book'
)
}
)

collection_book = Collection.new(@datasource_with_bad_one_to_many, 'Book')
collection_book.add_fields(
{
'id' => ColumnSchema.new(column_type: 'Number', is_primary_key: true),
'title' => ColumnSchema.new(column_type: 'String')
# author_id is missing
}
)

@datasource_with_bad_one_to_many.add_collection(collection_author)
@datasource_with_bad_one_to_many.add_collection(collection_book)
end

it 'returns nil and logs a warning when origin_key field is missing' do
logger = instance_spy(Logger)
allow(Facades::Container).to receive(:logger).and_return(logger)

schema = described_class.build_schema(
@datasource_with_bad_one_to_many.get_collection('Author'),
'books'
)

expect(schema).to be_nil
expect(logger).to have_received(:log).with(
'Warn',
"Field 'missing_author_id' (origin_key) not found in collection 'Book' for relation 'books'. " \
'This relation will be skipped. Check if the field exists in your database.'
)
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative 'forest_admin_datasource_customizer/version'
require 'set'
require 'zeitwerk'

loader = Zeitwerk::Loader.for_gem
Expand Down
Loading
Loading