Skip to content

Commit

Permalink
Merge branch 'core' into fix-nil-error-on-dependency-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieujobin authored Sep 3, 2024
2 parents b61e83b + f441c37 commit 7a00247
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 56 deletions.
39 changes: 14 additions & 25 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,24 @@ jobs:
strategy:
fail-fast: false
matrix:
rails: ["~> 7.0.0", "~> 6.1.0", "~> 6.0.0"]
ruby: ["3.2.2", "3.1.4", "3.0.6", "2.7.8"]
include:
- ruby: 3.2
rails: 'edge'
# single test failure with jruby
#- ruby: jruby-9.4
# rails: '~> 7.0.0'
- ruby: 2.6
rails: '~> 6.1.0'
- ruby: 2.6
rails: '~> 6.0.0'
- ruby: 2.6
rails: '~> 5.2.0'
- ruby: 2.6
rails: '~> 5.1.0'
- ruby: 2.5
rails: '~> 6.0.0'
- ruby: 2.5
rails: '~> 5.2.0'
- ruby: 2.5
rails: '~> 5.1.0'
#os: ubuntu-latest
#arch: x64
rails: ["edge", "~> 7.2.0", "~> 7.1.0", "~> 7.0.0", "~> 6.1.0"]
ruby: ["3.3","3.2", "3.1", "3.0", "2.7"]
exclude:
- rails: "~> 7.2.0"
ruby: "3.0"
- rails: "~> 7.2.0"
ruby: "2.7"
- rails: "edge"
ruby: "3.0"
- rails: "edge"
ruby: "2.7"



env:
RAILS: ${{ matrix.rails }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
Expand Down
29 changes: 25 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
# paranoia Changelog

## 2.6.2
## 3.0.0 - August 13, 2024

* [#441](https://github.com/rubysherpas/paranoia/pull/441) Recursive restore with has_many/one through assocs (#441)
_Tagged as 3.0 as Ruby + Rails version constraints have been modernised._

- [#564](https://github.com/rubysherpas/paranoia/pull/564) Support Rails edge
- [#563](https://github.com/rubysherpas/paranoia/pull/563) Support Rails 7.2

## 2.6.4 - July 20, 2024

* [#554](https://github.com/rubysherpas/paranoia/pull/554) Support prebuilt counter cache association list (#554)
[Joé Dupuis](https://github.com/JoeDupuis)
* [#551](https://github.com/rubysherpas/paranoia/pull/551) Fix: restore has_one with scope (#551)
[Paweł Charyło](https://github.com/zygzagZ)
* [#555](https://github.com/rubysherpas/paranoia/pull/555) 📝 Add Yard documentation for Paranoia::Query (#555)
[Clément Prod'homme](https://github.com/cprodhomme)

## 2.6.3 - Oct 12, 2023

* [#548](https://github.com/rubysherpas/paranoia/pull/548) Add support for [Rails 7.1](https://github.com/rails/rails/releases/tag/v7.1.0) (#548)
[Indyarocks](https://github.com/indyarocks)

## 2.6.2 - Jun 6, 2023

* [#441](https://github.com/rubysherpas/paranoia/pull/441) Recursive restore with has_many/one through assocs (#441)
[Emil Ong](https://github.com/emilong)

## 2.6.1
## 2.6.1 - Nov 16, 2022

* [#535](https://github.com/rubysherpas/paranoia/pull/535) Allow to skip updating paranoia_destroy_attributes for records while really_destroy!
[Anton Bogdanov](https://github.com/kortirso)

## 2.6.0
## 2.6.0 - Mar 23, 2022

* [#512](https://github.com/rubysherpas/paranoia/pull/512) Quote table names; Mysql 8 has keywords that might match table names which cause an exception.
* [#476](https://github.com/rubysherpas/paranoia/pull/476) Fix syntax error in documentation.
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sqlite = ENV['SQLITE_VERSION']
if sqlite
gem 'sqlite3', sqlite, platforms: [:ruby]
else
gem 'sqlite3', platforms: [:ruby]
gem 'sqlite3', '~> 1.4', platforms: [:ruby]
end

platforms :jruby do
Expand Down
56 changes: 35 additions & 21 deletions lib/paranoia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ def self.included(klazz)
module Query
def paranoid? ; true ; end

# If you want to find all records, even those which are deleted
def with_deleted
if ActiveRecord::VERSION::STRING >= "4.1"
return unscope where: paranoia_column
end
all.tap { |x| x.default_scoped = false }
end

# If you want to find only the deleted records
def only_deleted
if paranoia_sentinel_value.nil?
return with_deleted.where.not(paranoia_column => paranoia_sentinel_value)
Expand All @@ -45,6 +47,7 @@ def only_deleted
end
alias_method :deleted, :only_deleted

# If you want to restore a record
def restore(id_or_ids, opts = {})
ids = Array(id_or_ids).flatten
any_object_instead_of_id = ids.any? { |id| ActiveRecord::Base === id }
Expand All @@ -60,7 +63,7 @@ def restore(id_or_ids, opts = {})
def paranoia_destroy
with_transaction_returning_status do
result = run_callbacks(:destroy) do
@_disable_counter_cache = deleted?
@_disable_counter_cache = paranoia_destroyed?
result = paranoia_delete
next result unless result && ActiveRecord::VERSION::STRING >= '4.2'
each_counter_cached_associations do |association|
Expand All @@ -73,7 +76,7 @@ def paranoia_destroy
@_disable_counter_cache = false
result
end
raise ActiveRecord::Rollback, "Not destroyed" unless self.deleted?
raise ActiveRecord::Rollback, "Not destroyed" unless paranoia_destroyed?
result
end || false
end
Expand Down Expand Up @@ -173,8 +176,25 @@ def really_destroy!(update_destroy_attributes: true)

private

def counter_cache_disabled?
defined?(@_disable_counter_cache) && @_disable_counter_cache
end

def counter_cached_association_names
return [] if counter_cache_disabled?
super
end

def each_counter_cached_associations
!(defined?(@_disable_counter_cache) && @_disable_counter_cache) ? super : []
return [] if counter_cache_disabled?

if defined?(super)
super
else
counter_cached_association_names.each do |name|
yield association(name)
end
end
end

def paranoia_restore_attributes
Expand All @@ -193,6 +213,16 @@ def timestamp_attributes_with_current_time
timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
end

def paranoia_find_has_one_target(association)
association_foreign_key = association.options[:through].present? ? association.klass.primary_key : association.foreign_key
association_find_conditions = { association_foreign_key => self.id }
association_find_conditions[association.type] = self.class.name if association.type

scope = association.klass.only_deleted.where(association_find_conditions)
scope = scope.merge(association.scope) if association.scope
scope.first
end

# restore associated records that have been soft deleted when
# we called #destroy
def restore_associated_records(recovery_window_range = nil)
Expand All @@ -216,24 +246,8 @@ def restore_associated_records(recovery_window_range = nil)
end

if association_data.nil? && association.macro.to_s == "has_one"
association_class_name = association.klass.name

association_foreign_key = if association.options[:through].present?
association.klass.primary_key
else
association.foreign_key
end

if association.type
association_polymorphic_type = association.type
association_find_conditions = { association_polymorphic_type => self.class.name.to_s, association_foreign_key => self.id }
else
association_find_conditions = { association_foreign_key => self.id }
end

association_class = association.klass
if association_class.paranoid?
association_class.only_deleted.where(association_find_conditions).first
if association.klass.paranoid?
paranoia_find_has_one_target(association)
.try!(:restore, recursive: true, :recovery_window_range => recovery_window_range)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/paranoia/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Paranoia
VERSION = '2.6.2'.freeze
VERSION = '3.0.0'.freeze
end
6 changes: 3 additions & 3 deletions paranoia.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Gem::Specification.new do |s|
s.license = 'MIT'
s.summary = "Paranoia is a re-implementation of acts_as_paranoid for Rails 3, 4, and 5, using much, much, much less code."
s.description = <<-DSC
Paranoia is a re-implementation of acts_as_paranoid for Rails 4, 5, 6, and 7,
Paranoia is a re-implementation of acts_as_paranoid for Rails 5, 6, and 7,
using much, much, much less code. You would use either plugin / gem if you
wished that when you called destroy on an Active Record object that it
didn't actually destroy it, but just "hid" the record. Paranoia does this
Expand All @@ -22,9 +22,9 @@ Gem::Specification.new do |s|

s.required_rubygems_version = ">= 1.3.6"

s.required_ruby_version = '>= 2.5'
s.required_ruby_version = '>= 2.7'

s.add_dependency 'activerecord', '>= 5.1', '< 7.1'
s.add_dependency 'activerecord', '>= 6', '< 8.1'

s.add_development_dependency "bundler", ">= 1.0.0"
s.add_development_dependency "rake"
Expand Down
42 changes: 41 additions & 1 deletion test/paranoia_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'minitest/autorun'
require 'paranoia'

test_framework = defined?(MiniTest::Test) ? MiniTest::Test : MiniTest::Unit::TestCase
test_framework = defined?(Minitest::Test) ? Minitest::Test : Minitest::Unit::TestCase

if ActiveRecord::Base.respond_to?(:raise_in_transactional_callbacks=)
ActiveRecord::Base.raise_in_transactional_callbacks = true
Expand Down Expand Up @@ -54,6 +54,7 @@ def setup!
'empty_paranoid_models' => 'deleted_at DATETIME',
'paranoid_has_one_throughs' => 'paranoid_has_through_restore_parent_id INTEGER NOT NULL, empty_paranoid_model_id INTEGER NOT NULL, deleted_at DATETIME',
'paranoid_has_many_throughs' => 'paranoid_has_through_restore_parent_id INTEGER NOT NULL, empty_paranoid_model_id INTEGER NOT NULL, deleted_at DATETIME',
'paranoid_has_one_with_scopes' => 'deleted_at DATETIME, kind STRING, paranoid_has_one_with_scope_id INTEGER',
}.each do |table_name, columns_as_sql_string|
ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
end
Expand Down Expand Up @@ -1223,6 +1224,37 @@ def test_counter_cache_column_on_restore
end
end

def test_has_one_with_scope_missed
parent = ParanoidHasOneWithScope.create
gamma = ParanoidHasOneWithScope.create(kind: :gamma, paranoid_has_one_with_scope: parent) # this has to be first
alpha = ParanoidHasOneWithScope.create(kind: :alpha, paranoid_has_one_with_scope: parent)
beta = ParanoidHasOneWithScope.create(kind: :beta, paranoid_has_one_with_scope: parent)

parent.destroy
assert !gamma.reload.destroyed?
gamma.destroy
assert_equal 0, ParanoidHasOneWithScope.count # all destroyed
parent.reload # we unload associations
parent.restore(recursive: true)

assert_equal "alpha", parent.alpha&.kind, "record was not restored"
assert_equal "beta", parent.beta&.kind, "record was not restored"
assert_nil parent.gamma, "record was incorrectly restored"
end

def test_has_one_with_scope_not_restored
parent = ParanoidHasOneWithScope.create
gamma = ParanoidHasOneWithScope.create(kind: :gamma, paranoid_has_one_with_scope: parent)
parent.destroy
assert_equal 1, ParanoidHasOneWithScope.count # gamma not deleted
gamma.destroy
parent.reload # we unload associations
parent.restore(recursive: true)

assert gamma.reload.deleted?, "the record was incorrectly restored"
assert_equal 1, ParanoidHasOneWithScope.count # gamma deleted
end

private
def get_featureful_model
FeaturefulModel.new(:name => "not empty")
Expand Down Expand Up @@ -1627,3 +1659,11 @@ class ParanoidHasManyThrough < ActiveRecord::Base
belongs_to :paranoid_has_through_restore_parent
belongs_to :empty_paranoid_model, dependent: :destroy
end

class ParanoidHasOneWithScope < ActiveRecord::Base
acts_as_paranoid
has_one :alpha, -> () { where(kind: :alpha) }, class_name: "ParanoidHasOneWithScope", dependent: :destroy
has_one :beta, -> () { where(kind: :beta) }, class_name: "ParanoidHasOneWithScope", dependent: :destroy
has_one :gamma, -> () { where(kind: :gamma) }, class_name: "ParanoidHasOneWithScope"
belongs_to :paranoid_has_one_with_scope
end

0 comments on commit 7a00247

Please sign in to comment.