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
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ Metrics/ParameterLists:
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/dsl/builders/form_builder.rb'
- 'packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/datasource.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/many_to_many_schema.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/column_schema.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/caller.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def delete_records(args, selection_ids, context)

context.collection.schema[:fields].each_value do |field_schema|
next unless ['PolymorphicOneToOne', 'PolymorphicOneToMany'].include?(field_schema.type)
next if field_schema.respond_to?(:cascade_on_delete) && field_schema.cascade_on_delete

origin_values = selection_ids[:ids].map do |pk_hash|
if pk_hash.is_a?(Hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,105 @@ def respond_to?(arg)
end
end
end

context 'with polymorphic relation that cascades on delete' do
before do
cache = FileCache.new('app', 'tmp/cache/forest_admin')
cache.clear

agent_factory = ForestAdminAgent::Builder::AgentFactory.instance
agent_factory.setup(
{
auth_secret: 'cba803d01a4d43b55010cab41fa1ea1f1f51a95e',
env_secret: '89719c6d8e2e2de2694c2f220fe2dbf02d5289487364daf1e4c6b13733ed0cdb',
is_production: false,
cache_dir: 'tmp/cache/forest_admin',
schema_path: File.join('tmp', '.forestadmin-schema.json'),
forest_server_url: 'https://api.development.forestadmin.com',
debug: true,
prefix: 'forest',
customize_error_message: nil,
append_schema_path: nil
}
)

feedback_class = Struct.new(:id, :body)
evidence_class = Struct.new(:id, :evidence_id, :evidence_type)
stub_const('Feedback', feedback_class)
stub_const('AchievementEvidence', evidence_class)

datasource = Datasource.new

feedback_collection = build_collection(
name: 'feedback',
schema: {
fields: {
'id' => ColumnSchema.new(
column_type: 'Number',
is_primary_key: true,
filter_operators: [Operators::IN, Operators::EQUAL]
),
'body' => ColumnSchema.new(column_type: 'String'),
'achievement_evidences' => Relations::PolymorphicOneToManySchema.new(
foreign_collection: 'achievement_evidence',
origin_key: 'evidence_id',
origin_key_target: 'id',
origin_type_field: 'evidence_type',
origin_type_value: 'Feedback',
cascade_on_delete: true
)
}
},
delete: true
)

evidence_collection = build_collection(
name: 'achievement_evidence',
schema: {
fields: {
'id' => ColumnSchema.new(
column_type: 'Number',
is_primary_key: true,
filter_operators: [Operators::IN, Operators::EQUAL]
),
'evidence_id' => ColumnSchema.new(column_type: 'Number'),
'evidence_type' => ColumnSchema.new(column_type: 'String')
}
},
update: true
)

allow(agent_factory).to receive(:send_schema).and_return(nil)
datasource.add_collection(feedback_collection)
datasource.add_collection(evidence_collection)
agent_factory.add_datasource(datasource)
agent_factory.build

@datasource = ForestAdminAgent::Facades::Container.datasource
allow(@datasource.get_collection('feedback')).to receive(:delete)
allow(@datasource.get_collection('achievement_evidence')).to receive(:update)
end

let(:params) do
{
'collection_name' => 'feedback',
'timezone' => 'Europe/Paris',
'id' => 1
}
end

it 'does not nullify the polymorphic FK on the foreign collection' do
delete.handle_request(args)

expect(@datasource.get_collection('achievement_evidence')).not_to have_received(:update)
end

it 'still calls delete on the parent collection' do
delete.handle_request(args)

expect(@datasource.get_collection('feedback')).to have_received(:delete)
end
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ def association_primary_key?(association)
association.polymorphic?
end

def cascade_on_delete?(association)
%i[destroy destroy_async delete_all].include?(association.options[:dependent])
end

# rubocop:disable Metrics/BlockNesting
def fetch_associations
associations(@model, support_polymorphic_relations: @support_polymorphic_relations).each do |association|
Expand Down Expand Up @@ -118,7 +122,8 @@ def fetch_associations
origin_key: association.foreign_key,
origin_key_target: association.association_primary_key,
origin_type_field: association.inverse_of.foreign_type,
origin_type_value: @model.name
origin_type_value: @model.name,
cascade_on_delete: cascade_on_delete?(association)
)
)
else
Expand Down Expand Up @@ -192,7 +197,8 @@ def fetch_associations
origin_key: association.foreign_key,
origin_key_target: association.association_primary_key,
origin_type_field: association.inverse_of.foreign_type,
origin_type_value: @model.name
origin_type_value: @model.name,
cascade_on_delete: cascade_on_delete?(association)
)
)
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ module ForestAdminDatasourceActiveRecord
expect(datasource.get_collection('Address').schema[:fields].keys).to include('addressable')
end

it 'sets cascade_on_delete=true on polymorphic has_many when dependent: :destroy is declared' do
members = datasource.get_collection('Project').schema[:fields]['members']
expect(members).to be_a(Relations::PolymorphicOneToManySchema)
expect(members.cascade_on_delete).to be true
end

it 'leaves cascade_on_delete=false on polymorphic has_one without dependent option' do
address = datasource.get_collection('User').schema[:fields]['address']
expect(address).to be_a(Relations::PolymorphicOneToOneSchema)
expect(address.cascade_on_delete).to be false
end

it 'sets foreign_type_field/value for has_many :through with polymorphic source_type' do
user_collection = datasource.get_collection('User')
projects_relation = user_collection.schema[:fields]['projects']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ module Schema
module Relations
class PolymorphicOneToManySchema < RelationSchema
attr_accessor :origin_key, :origin_type_value
attr_reader :origin_key_target, :origin_type_field
attr_reader :origin_key_target, :origin_type_field, :cascade_on_delete

def initialize(origin_key:, origin_key_target:, foreign_collection:, origin_type_field:, origin_type_value:)
def initialize(origin_key:, origin_key_target:, foreign_collection:, origin_type_field:, origin_type_value:,
cascade_on_delete: false)
super(foreign_collection, 'PolymorphicOneToMany')
@origin_key = origin_key
@origin_key_target = origin_key_target
@origin_type_field = origin_type_field
@origin_type_value = origin_type_value
@cascade_on_delete = cascade_on_delete
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ module Schema
module Relations
class PolymorphicOneToOneSchema < RelationSchema
attr_accessor :origin_key, :origin_type_value
attr_reader :origin_key_target, :origin_type_field
attr_reader :origin_key_target, :origin_type_field, :cascade_on_delete

def initialize(origin_key:, origin_key_target:, foreign_collection:, origin_type_field:, origin_type_value:)
def initialize(origin_key:, origin_key_target:, foreign_collection:, origin_type_field:, origin_type_value:,
cascade_on_delete: false)
super(foreign_collection, 'PolymorphicOneToOne')
@origin_key = origin_key
@origin_key_target = origin_key_target
@origin_type_field = origin_type_field
@origin_type_value = origin_type_value
@cascade_on_delete = cascade_on_delete
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ module Relations
it { expect(relation.origin_type_field).to eq 'origin_type_field' }
it { expect(relation.origin_type_value).to eq 'origin_type_value' }
it { expect(relation.foreign_collection).to eq 'foreign_collection' }
it { expect(relation.cascade_on_delete).to be false }
end

describe 'cascade_on_delete' do
it 'accepts an explicit cascade_on_delete value' do
relation = described_class.new(
origin_key: 'origin_key',
origin_key_target: 'origin_key_target',
origin_type_field: 'origin_type_field',
origin_type_value: 'origin_type_value',
foreign_collection: 'foreign_collection',
cascade_on_delete: true
)
expect(relation.cascade_on_delete).to be true
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ module Relations
it { expect(relation.origin_type_field).to eq 'origin_type_field' }
it { expect(relation.origin_type_value).to eq 'origin_type_value' }
it { expect(relation.foreign_collection).to eq 'foreign_collection' }
it { expect(relation.cascade_on_delete).to be false }
end

describe 'cascade_on_delete' do
it 'accepts an explicit cascade_on_delete value' do
relation = described_class.new(
origin_key: 'origin_key',
origin_key_target: 'origin_key_target',
origin_type_field: 'origin_type_field',
origin_type_value: 'origin_type_value',
foreign_collection: 'foreign_collection',
cascade_on_delete: true
)
expect(relation.cascade_on_delete).to be true
end
end
end
end
Expand Down
Loading