Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ def add_to_one_association(name, options = {}, &block)
@to_one_associations[name] = format_field(name, options)
end

# A PolymorphicManyToOne can't be SQL-joined, so list projections return a phantom object with no
# primary key. Rebuild the id from the foreign key on the owner record.
def has_one_relationship(attribute_name, attr_data)
object = super
return object if object.nil? # relation not projected (e.g. update/store responses)

field = ForestAdminAgent::Facades::Container.datasource
.get_collection(@options[:class_name].gsub('::', '__'))
.schema[:fields][attribute_name.to_s]
return object unless field&.type == 'PolymorphicManyToOne'

foreign_key = @object[field.foreign_key]
foreign_type = @object[field.foreign_key_type_field]
return nil if foreign_key.nil? || foreign_type.nil?

# foreign_key_targets is keyed by formatted collection names (Admin::User => Admin__User),
# but foreign_type is the raw AR type column value, so format it before the lookup.
primary_key = (field.foreign_key_targets || {})[foreign_type.gsub('::', '__')] || 'id'
(object.is_a?(Hash) ? object : {}).merge(primary_key => foreign_key)

Copy link
Copy Markdown

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 = 5): has_one_relationship [qlty:function-complexity]

end

def has_one_relationships
return {} if @to_one_associations.nil?
data = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ module Serializer
}
)

# Namespaced model (Admin::User => Admin__User) with a custom primary key
admin_user_collection = build_collection(
name: 'Admin__User',
schema: {
fields: {
'reference' => ColumnSchema.new(column_type: 'String', is_primary_key: true, filter_operators: [Operators::EQUAL]),
'name' => ColumnSchema.new(column_type: 'String')
}
}
)

document_collection = build_collection(
name: 'Document',
schema: {
Expand All @@ -45,8 +56,8 @@ module Serializer
'documentable' => PolymorphicManyToOneSchema.new(
foreign_key: 'documentable_id',
foreign_key_type_field: 'documentable_type',
foreign_collections: %w[Car User],
foreign_key_targets: { 'Car' => 'id', 'User' => 'id' }
foreign_collections: %w[Car User Admin__User],
foreign_key_targets: { 'Car' => 'id', 'User' => 'id', 'Admin__User' => 'reference' }
)
}
}
Expand All @@ -55,6 +66,7 @@ module Serializer
allow(ForestAdminAgent::Builder::AgentFactory.instance).to receive(:send_schema).and_return(nil)
datasource.add_collection(car_collection)
datasource.add_collection(user_collection)
datasource.add_collection(admin_user_collection)
datasource.add_collection(document_collection)
ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(datasource)
ForestAdminAgent::Builder::AgentFactory.instance.build
Expand All @@ -77,6 +89,74 @@ module Serializer
expect(relationship['data']['id']).to eq('10')
end

it 'builds the linkage id from the foreign key when the related record is a phantom (issue #332)' do
# The AR datasource skips polymorphic relations while building the SELECT, so the related
# object comes back without a primary key. The id must still be built from the owner FK.
record = {
'id' => 1,
'title' => 'Registration Certificate',
'documentable_id' => 10,
'documentable_type' => 'Car',
'documentable' => { '*' => nil }
}

result = JSONAPI::Serializer.serialize(record, class_name: 'Document', serializer: described_class)

relationship = result['data']['relationships']['documentable']
expect(relationship['data']['type']).to eq('Car')
expect(relationship['data']['id']).to eq('10')
end

it 'builds the included document id and self-link from the foreign key (issue #332)' do
record = {
'id' => 1,
'title' => 'Registration Certificate',
'documentable_id' => 10,
'documentable_type' => 'Car',
'documentable' => { '*' => nil }
}

result = JSONAPI::Serializer.serialize(
record, class_name: 'Document', serializer: described_class, include: 'documentable'
)

included = result['included'].find { |r| r['type'] == 'Car' }
expect(included['id']).to eq('10')
expect(included['links']['self']).to eq('/forest/Car/10')
end

it 'builds the linkage id for a namespaced target with a custom primary key (issue #332)' do
# foreign_key_targets is keyed by the formatted name (Admin__User), but the type column
# stores the raw class name (Admin::User); the lookup must reconcile the two.
record = {
'id' => 4,
'title' => 'Audit Log',
'documentable_id' => 'ref-42',
'documentable_type' => 'Admin::User',
'documentable' => { '*' => nil }
}

result = JSONAPI::Serializer.serialize(record, class_name: 'Document', serializer: described_class)

relationship = result['data']['relationships']['documentable']
expect(relationship['data']['type']).to eq('Admin__User')
expect(relationship['data']['id']).to eq('ref-42')
end

it 'omits the data key for an unlinked polymorphic relation (issue #332)' do
record = {
'id' => 3,
'title' => 'Orphan',
'documentable_id' => nil,
'documentable_type' => nil,
'documentable' => { '*' => nil }
}

result = JSONAPI::Serializer.serialize(record, class_name: 'Document', serializer: described_class)

expect(result['data']['relationships']['documentable']).not_to have_key('data')
end

it 'serializes a polymorphic belongs_to relation pointing to User' do
record = {
'id' => 2,
Expand Down
Loading