From c3abb37a05ea3ab05cd7617f867116d4e3667f2e Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Mon, 18 Sep 2023 17:34:17 +0900 Subject: [PATCH 1/6] Mongoid paranoia support --- README.md | 13 +++++++++++++ lib/mongoid/slug.rb | 30 ++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 358c613..4830c27 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,19 @@ unique = Mongoid::Slug::UniqueSlug.new(Book.new).find_unique(title) # return some representation of unique ``` +### Compatibility with Mongoid::Paranoia + +The [Mongoid::Paranoia](https://github.com/simi/mongoid_paranoia) gem provides soft-delete +functionality. If you are using this gem, please enable the global `use_paranoia` option. +This will automatically ensure that slugs will be cleared when soft-deleting a document, +and re-set when restoring a document. + +```ruby +Mongoid::Slug.configure do |c| + c.use_paranoia = true +end +``` + Contributing ------------ diff --git a/lib/mongoid/slug.rb b/lib/mongoid/slug.rb index 93e853c..f98bec4 100644 --- a/lib/mongoid/slug.rb +++ b/lib/mongoid/slug.rb @@ -30,7 +30,8 @@ module Slug end class << self - attr_accessor :default_slug + attr_accessor :default_slug, + :use_paranoia def configure(&block) instance_eval(&block) @@ -103,6 +104,17 @@ def slug(*fields, &block) else set_callback :save, :before, :build_slug, if: :slug_should_be_rebuilt? end + + # If paranoid document: + # - unset slugs on soft-destroy + # - recreate the slug on restore + # - force reset the slug when saving a destroyed paranoid document, to ensure it stays unset in the database + if __slug_paranoid_doc? # rubocop:disable Style/GuardClause + set_callback :destroy, :after, :unset_slug! + set_callback :restore, :before, :set_slug! + set_callback :save, :before, :reset_slug!, if: :slug_paranoid_deleted? + set_callback :save, :after, :clear_slug!, if: :slug_paranoid_deleted? + end end def default_slug_url_builder @@ -147,6 +159,16 @@ def queryable current_scope || Criteria.new(self) # Use Mongoid::Slug::Criteria for slugged documents. end + # Returns whether to use paranoid functionality for this document. + # + # @return [ true | false ] Whether to use paranoid functionality + # for this document. + # + # @api private + def __slug_paranoid_doc? + !!(Mongoid::Slug.use_paranoia && defined?(::Mongoid::Paranoia) && self < ::Mongoid::Paranoia) + end + private if Threaded.method(:current_scope).arity == -1 @@ -237,7 +259,11 @@ def find_unique_slug # @return [Boolean] Whether the slug requires to be rebuilt def slug_should_be_rebuilt? - new_record? || _slugs_changed? || slugged_attributes_changed? + (new_record? || _slugs_changed? || slugged_attributes_changed?) && !slug_paranoid_deleted? + end + + def slug_paranoid_deleted? + !!(self.class.__slug_paranoid_doc? && !deleted_at.nil?) end def slugged_attributes_changed? From 837c8ee66362e90592f054aa72d64bb5966f949c Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:39:58 +0900 Subject: [PATCH 2/6] Rework specs --- .github/workflows/rubocop.yml | 22 ++++++++++++++++++++++ .github/workflows/test.yml | 14 +++++--------- CHANGELOG.md | 6 +++++- Gemfile | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/rubocop.yml diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..631fdd7 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,22 @@ +--- +name: Rubocop + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + env: + CI: true + TESTOPTS: '-v' + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby 3.2 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - name: bundle install + run: bundle install --jobs 4 --retry 3 + - name: Run Rubocop + run: bundle exec rubocop --parallel diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6dc685..f24a0d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,13 +19,16 @@ jobs: experimental: [false] include: - ruby: 3.2 - mongoid: HEAD + mongoid: head experimental: true - ruby: head mongoid: 7 experimental: true - ruby: head - mongoid: HEAD + mongoid: head + experimental: true + - ruby: jruby + mongoid: 7 experimental: true - ruby: jruby mongoid: 8 @@ -58,13 +61,6 @@ jobs: env: MONGOID_VERSION: ${{ matrix.mongoid }} - - name: rubocop - timeout-minutes: 5 - run: bundle exec rubocop - continue-on-error: ${{ matrix.experimental }} - env: - MONGOID_VERSION: ${{ matrix.mongoid }} - - name: test timeout-minutes: 10 run: bundle exec rake spec diff --git a/CHANGELOG.md b/CHANGELOG.md index 1edb779..0968aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ -## 7.0.1 (Next) +## 7.1.1 (Next) * Your contribution here. +## 7.1.0 (2023/09/18) + +* [#273](https://github.com/mongoid/mongoid-slug/pull/273): Add optional Mongoid::Paranoia support - [@johnnyshields](https://github.com/johnnyshields) + ## 7.0.0 (2023/09/18) * [#270](https://github.com/mongoid/mongoid-slug/pull/270): Drop support for Mongoid prior to version 7.0 - [@johnnyshields](https://github.com/johnnyshields) diff --git a/Gemfile b/Gemfile index 17ccaec..7635fb3 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ source 'https://rubygems.org' gemspec name: 'mongoid-slug' case (version = ENV['MONGOID_VERSION'] || '8') -when 'HEAD' +when /\Ahead\z/i gem 'mongoid', github: 'mongodb/mongoid' when /\A\d+\z/ gem 'mongoid', "~> #{version}.0" From 4bfd30d3cb3ddac110e0cb32a19ed8b72bb9944f Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Mon, 18 Sep 2023 20:26:30 +0900 Subject: [PATCH 3/6] Add specs --- Gemfile | 1 + lib/mongoid/slug.rb | 3 +-- spec/models/agent.rb | 10 ++++++++ spec/models/secret_agent.rb | 12 +++++++++ spec/mongoid/paranoia_spec.rb | 46 +++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 1 + 6 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 spec/models/agent.rb create mode 100644 spec/models/secret_agent.rb create mode 100644 spec/mongoid/paranoia_spec.rb diff --git a/Gemfile b/Gemfile index 7635fb3..e97d9b5 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ else gem 'mongoid', version end +gem 'mongoid_paranoia', '>= 0.6' gem 'rake' gem 'rspec' gem 'rspec-its' diff --git a/lib/mongoid/slug.rb b/lib/mongoid/slug.rb index f98bec4..e934da0 100644 --- a/lib/mongoid/slug.rb +++ b/lib/mongoid/slug.rb @@ -112,8 +112,7 @@ def slug(*fields, &block) if __slug_paranoid_doc? # rubocop:disable Style/GuardClause set_callback :destroy, :after, :unset_slug! set_callback :restore, :before, :set_slug! - set_callback :save, :before, :reset_slug!, if: :slug_paranoid_deleted? - set_callback :save, :after, :clear_slug!, if: :slug_paranoid_deleted? + set_callback :save, :before, :clear_slug!, if: :slug_paranoid_deleted? end end diff --git a/spec/models/agent.rb b/spec/models/agent.rb new file mode 100644 index 0000000..b5f7d79 --- /dev/null +++ b/spec/models/agent.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Agent + include Mongoid::Document + include Mongoid::Slug + include Mongoid::Paranoia + + field :name + slug :name, permanent: true +end diff --git a/spec/models/secret_agent.rb b/spec/models/secret_agent.rb new file mode 100644 index 0000000..f32bcd2 --- /dev/null +++ b/spec/models/secret_agent.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +Mongoid::Slug.use_paranoia = true +class SecretAgent + include Mongoid::Document + include Mongoid::Slug + include Mongoid::Paranoia + + field :name + slug :name, permanent: true +end +Mongoid::Slug.use_paranoia = false diff --git a/spec/mongoid/paranoia_spec.rb b/spec/mongoid/paranoia_spec.rb new file mode 100644 index 0000000..9bb437e --- /dev/null +++ b/spec/mongoid/paranoia_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mongoid::Slug do + context 'when paranoia support is not enabled' do + let!(:doc) { Agent.create(name: 'Garbo') } + + it 'does not delete the slug when the document is destroyed' do + expect(doc.slug).to eq 'garbo' + doc.destroy + expect(Agent.unscoped.find(doc._id).slug).to eq 'garbo' + end + end + + context 'when paranoia support is enabled' do + let!(:doc) { SecretAgent.create(name: 'Alaric') } + + around do |example| + Mongoid::Slug.use_paranoia = true + example.run + ensure + Mongoid::Slug.use_paranoia = false + end + + it 'deletes the slug when the document is destroyed' do + expect(doc.slug).to eq 'alaric' + doc.destroy + # TODO: This case should be nil. + expect(SecretAgent.unscoped.find(doc._id).slug).to eq doc._id.to_s + doc.restore + expect(SecretAgent.unscoped.find(doc._id).slug).to eq 'alaric' + end + + it 'deletes the slug when deleted document is saved' do + expect(doc.slug).to eq 'alaric' + doc.deleted_at = Time.current + doc.save! + expect(SecretAgent.unscoped.find(doc._id).slug).to eq nil + # TODO: This case should re-set the slug. + doc.deleted_at = nil + doc.save! + expect(SecretAgent.unscoped.find(doc._id).slug).to eq nil + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ce43a00..9f40170 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,7 @@ require 'active_support' require 'active_support/deprecation' require 'mongoid' +require 'mongoid/paranoia' require File.expand_path '../lib/mongoid/slug', __dir__ From 7b27669c0b829199bb6c0bce203129c6a4ede486 Mon Sep 17 00:00:00 2001 From: Johnny Shields <27655+johnnyshields@users.noreply.github.com> Date: Mon, 18 Sep 2023 20:37:16 +0900 Subject: [PATCH 4/6] Update rubocop.yml --- .github/workflows/rubocop.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 631fdd7..e86c106 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -16,7 +16,5 @@ jobs: with: ruby-version: 3.2 bundler-cache: true - - name: bundle install - run: bundle install --jobs 4 --retry 3 - name: Run Rubocop run: bundle exec rubocop --parallel From 339beda4c6fe93450ec7e91f5b40112d4e18d13f Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Tue, 19 Sep 2023 00:00:35 +0900 Subject: [PATCH 5/6] Revert to old code --- lib/mongoid/slug.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mongoid/slug.rb b/lib/mongoid/slug.rb index e934da0..d0306b8 100644 --- a/lib/mongoid/slug.rb +++ b/lib/mongoid/slug.rb @@ -112,7 +112,10 @@ def slug(*fields, &block) if __slug_paranoid_doc? # rubocop:disable Style/GuardClause set_callback :destroy, :after, :unset_slug! set_callback :restore, :before, :set_slug! - set_callback :save, :before, :clear_slug!, if: :slug_paranoid_deleted? + # TODO investigate if this should be used + # set_callback :save, :before, :clear_slug!, if: :slug_paranoid_deleted? + set_callback :save, :before, :reset_slug!, if: :paranoid_deleted? + set_callback :save, :after, :clear_slug!, if: :paranoid_deleted? end end From 930ae906f2de646a5bd041e2880dfbafe6cfd90b Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Tue, 19 Sep 2023 03:25:02 +0900 Subject: [PATCH 6/6] Revert last commit --- lib/mongoid/slug.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/mongoid/slug.rb b/lib/mongoid/slug.rb index d0306b8..e934da0 100644 --- a/lib/mongoid/slug.rb +++ b/lib/mongoid/slug.rb @@ -112,10 +112,7 @@ def slug(*fields, &block) if __slug_paranoid_doc? # rubocop:disable Style/GuardClause set_callback :destroy, :after, :unset_slug! set_callback :restore, :before, :set_slug! - # TODO investigate if this should be used - # set_callback :save, :before, :clear_slug!, if: :slug_paranoid_deleted? - set_callback :save, :before, :reset_slug!, if: :paranoid_deleted? - set_callback :save, :after, :clear_slug!, if: :paranoid_deleted? + set_callback :save, :before, :clear_slug!, if: :slug_paranoid_deleted? end end