diff --git a/.rubocop.yml b/.rubocop.yml index efe3deaa3..c4733f1b3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -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' diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/delete.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/delete.rb index 2a4f6c355..5d1c1f3cf 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/delete.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/delete.rb @@ -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) diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb index 1edb28a5d..b7ee1bf10 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/delete_spec.rb @@ -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 diff --git a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb index cd0f89070..78ce83add 100644 --- a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb +++ b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb @@ -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| @@ -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 @@ -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 diff --git a/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/collection_spec.rb b/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/collection_spec.rb index ff11e8167..107e1e149 100644 --- a/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/collection_spec.rb +++ b/packages/forest_admin_datasource_active_record/spec/lib/forest_admin_datasource_active_record/collection_spec.rb @@ -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'] diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema.rb index 1b8f5e355..6681c5208 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema.rb @@ -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 diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema.rb index 1b7da2ba1..8d939402e 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema.rb @@ -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 diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema_spec.rb index 333043f97..1467ae46c 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_many_schema_spec.rb @@ -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 diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema_spec.rb index df9e11488..4e32dff6d 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/schema/relations/polymorphic_one_to_one_schema_spec.rb @@ -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