diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..e86c106 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,20 @@ +--- +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: 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..e97d9b5 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" @@ -13,6 +13,7 @@ else gem 'mongoid', version end +gem 'mongoid_paranoia', '>= 0.6' gem 'rake' gem 'rspec' gem 'rspec-its' 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..e934da0 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,16 @@ 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, :clear_slug!, if: :slug_paranoid_deleted? + end end def default_slug_url_builder @@ -147,6 +158,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 +258,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? 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__