diff --git a/backend/Gemfile b/backend/Gemfile index a25650c..3d823f0 100644 --- a/backend/Gemfile +++ b/backend/Gemfile @@ -103,6 +103,9 @@ gem "paper_trail" # A Rails engine that helps you put together a super-flexible admin dashboard. gem "administrate" +# ActiveRecord plugin allowing you to hide and restore records without actually deleting them. +gem "acts_as_paranoid" + group :development, :test do gem "bullet" diff --git a/backend/Gemfile.lock b/backend/Gemfile.lock index 0d112ff..3417b9a 100644 --- a/backend/Gemfile.lock +++ b/backend/Gemfile.lock @@ -93,6 +93,9 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) + acts_as_paranoid (0.10.0) + activerecord (>= 6.1, < 7.2) + activesupport (>= 6.1, < 7.2) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) administrate (0.20.1) @@ -568,6 +571,7 @@ PLATFORMS DEPENDENCIES active_model_serializers + acts_as_paranoid administrate bootsnap brakeman diff --git a/backend/app/models/application_record.rb b/backend/app/models/application_record.rb index 02f987e..6a572e0 100644 --- a/backend/app/models/application_record.rb +++ b/backend/app/models/application_record.rb @@ -3,4 +3,5 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class has_paper_trail + acts_as_paranoid end diff --git a/backend/db/migrate/20240526124316_add_deleted_at_to_users.rb b/backend/db/migrate/20240526124316_add_deleted_at_to_users.rb new file mode 100644 index 0000000..1f753c9 --- /dev/null +++ b/backend/db/migrate/20240526124316_add_deleted_at_to_users.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddDeletedAtToUsers < ActiveRecord::Migration[7.1] + def change + add_column :users, :deleted_at, :datetime + add_index :users, :deleted_at + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index 418e571..99253a3 100644 --- a/backend/db/schema.rb +++ b/backend/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_25_174254) do +ActiveRecord::Schema[7.1].define(version: 2024_05_26_124316) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "plpgsql" @@ -117,6 +117,8 @@ t.string "oauth_provider" t.string "oauth_uid" t.boolean "admin", default: false, null: false + t.datetime "deleted_at" + t.index ["deleted_at"], name: "index_users_on_deleted_at" t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end diff --git a/backend/spec/models/acts_as_paranoid_factories_spec.rb b/backend/spec/models/acts_as_paranoid_factories_spec.rb new file mode 100644 index 0000000..1304e94 --- /dev/null +++ b/backend/spec/models/acts_as_paranoid_factories_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# spec/models/acts_as_paranoid_factories_spec.rb +require "rails_helper" + +RSpec.describe "ApplicationRecord" do + FactoryBot.factories.each do |factory| + model_class = factory.build_class + + it_behaves_like "acts_as_paranoid", model_class + end +end diff --git a/backend/spec/support/acts_as_paranoid_examples.rb b/backend/spec/support/acts_as_paranoid_examples.rb new file mode 100644 index 0000000..839295c --- /dev/null +++ b/backend/spec/support/acts_as_paranoid_examples.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# spec/support/acts_as_paranoid_examples.rb +RSpec.shared_examples "acts_as_paranoid" do |model_class| + let!(:record) { create(model_class.name.underscore.to_sym) } + + describe "acts_as_paranoid" do + it "soft deletes the record" do + record.destroy + expect(record.reload).to be_deleted + expect(model_class.with_deleted.find(record.id)).to eq(record) + end + + it "does not include soft deleted records in default scope" do + record.destroy + expect(model_class.all).not_to include(record) + end + + it "includes soft deleted records with with_deleted scope" do + record.destroy + expect(model_class.with_deleted).to include(record) + end + + it "restores a soft deleted record" do + record.destroy + record.recover + expect(model_class.all).to include(record) + expect(record.reload.deleted_at).to be_nil + end + end +end