Commit f04b13ac by Emanuel Evans

Merge branch 'rails4' into fix_null_transaction

parents 0db09109 9e0ec38e
sudo: false
language: ruby language: ruby
rvm: rvm:
- 2.0.0 - 2.0.0
- 2.1.8 - 2.1.8
- 2.2.4 - 2.2.4
- 2.3.0 - 2.3.0
- jruby-19mode - jruby-9.0.5.0
env: env:
matrix:
- RAILS='~> 4.0.13' - RAILS='~> 4.0.13'
- RAILS='~> 4.1.10' - RAILS='~> 4.1.10'
- RAILS='~> 4.2.1' - RAILS='~> 4.2.1'
- RAILS='~> 5.0.0.beta1'
matrix:
exclude:
- env: RAILS='~> 5.0.0.beta1'
rvm: 2.0.0
- env: RAILS='~> 5.0.0.beta1'
rvm: 2.1.8
allow_failures:
- env: RAILS='~> 5.0.0.beta1'
rvm: jruby-9.0.5.0
# paranoia Changelog # paranoia Changelog
## 2.2.0 (unreleased)
* Ruby 2.0 or greater is required
* Rails 5.0.0.beta1.1 support [@pigeonworks](https://github.com/pigeonworks) [@halostatue](https://github.com/halostatue) and [@gagalago](https://github.com/gagalago)
* Previously `#really_destroyed?` may have been defined on non-paranoid models, it is now only available on paranoid models, use regular `#destroyed?` instead.
## 2.1.5 (2016-01-06)
* Ruby 2.3 support
## 2.1.4
## 2.1.3
## 2.1.2
## 2.1.1
## 2.1.0 (2015-01-23) ## 2.1.0 (2015-01-23)
### Major changes ### Major changes
......
# Paranoia # Paranoia
Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/technoweenie/acts_as_paranoid) for Rails 3 and Rails 4, using much, much, much less code. Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/ActsAsParanoid/acts_as_paranoid) for Rails 3/4/5, using much, much, much less code.
When your app is using Paranoia, calling `destroy` on an ActiveRecord object doesn't actually destroy the database record, but just *hides* it. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field. When your app is using Paranoia, calling `destroy` on an ActiveRecord object doesn't actually destroy the database record, but just *hides* it. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field.
...@@ -20,7 +20,7 @@ For Rails 3, please use version 1 of Paranoia: ...@@ -20,7 +20,7 @@ For Rails 3, please use version 1 of Paranoia:
gem "paranoia", "~> 1.0" gem "paranoia", "~> 1.0"
``` ```
For Rails 4, please use version 2 of Paranoia: For Rails 4 or 5, please use version 2 of Paranoia:
``` ruby ``` ruby
gem "paranoia", "~> 2.0" gem "paranoia", "~> 2.0"
...@@ -104,6 +104,17 @@ class Client < ActiveRecord::Base ...@@ -104,6 +104,17 @@ class Client < ActiveRecord::Base
end end
``` ```
If you want to skip adding the default scope:
``` ruby
class Client < ActiveRecord::Base
acts_as_paranoid without_default_scope: true
...
end
```
If you want to access soft-deleted associations, override the getter method: If you want to access soft-deleted associations, override the getter method:
``` ruby ``` ruby
...@@ -128,6 +139,12 @@ If you want to find all records, even those which are deleted: ...@@ -128,6 +139,12 @@ If you want to find all records, even those which are deleted:
Client.with_deleted Client.with_deleted
``` ```
If you want to exclude deleted records, when not able to use the default_scope (e.g. when using without_default_scope):
``` ruby
Client.without_deleted
```
If you want to find only the deleted records: If you want to find only the deleted records:
``` ruby ``` ruby
...@@ -187,7 +204,7 @@ Of course, this is not necessary for the indexes you always use in association w ...@@ -187,7 +204,7 @@ Of course, this is not necessary for the indexes you always use in association w
##### Unique Indexes ##### Unique Indexes
Becuse NULL != NULL in standard SQL, we can not simply create a unique index Because NULL != NULL in standard SQL, we can not simply create a unique index
on the deleted_at column and expect it to enforce that there only be one record on the deleted_at column and expect it to enforce that there only be one record
with a certain combination of values. with a certain combination of values.
...@@ -244,9 +261,9 @@ The `recover` method in `acts_as_paranoid` runs `update` callbacks. Paranoia's ...@@ -244,9 +261,9 @@ The `recover` method in `acts_as_paranoid` runs `update` callbacks. Paranoia's
## Callbacks ## Callbacks
Paranoia provides few callbacks. It triggers `destroy` callback when the record is marked as deleted and `real_destroy` when the record is completely removed from database. It also calls `restore` callback when record is restored via paranoia Paranoia provides several callbacks. It triggers `destroy` callback when the record is marked as deleted and `real_destroy` when the record is completely removed from database. It also calls `restore` callback when the record is restored via paranoia
For example if you want to index you records in some search engine you can do like this: For example if you want to index your records in some search engine you can go like this:
```ruby ```ruby
class Product < ActiveRecord::Base class Product < ActiveRecord::Base
......
...@@ -38,7 +38,7 @@ module Paranoia ...@@ -38,7 +38,7 @@ module Paranoia
quoted_paranoia_column = connection.quote_column_name(paranoia_column) quoted_paranoia_column = connection.quote_column_name(paranoia_column)
with_deleted.where("#{quoted_paranoia_column} IS NULL OR #{quoted_paranoia_column} != ?", paranoia_sentinel_value) with_deleted.where("#{quoted_paranoia_column} IS NULL OR #{quoted_paranoia_column} != ?", paranoia_sentinel_value)
end end
alias :deleted :only_deleted alias_method :deleted, :only_deleted
def restore(id_or_ids, opts = {}) def restore(id_or_ids, opts = {})
ids = Array(id_or_ids).flatten ids = Array(id_or_ids).flatten
...@@ -125,6 +125,30 @@ module Paranoia ...@@ -125,6 +125,30 @@ module Paranoia
end end
alias :deleted? :paranoia_destroyed? alias :deleted? :paranoia_destroyed?
def really_destroy!
transaction do
run_callbacks(:real_destroy) do
dependent_reflections = self.class.reflections.select do |name, reflection|
reflection.options[:dependent] == :destroy
end
if dependent_reflections.any?
dependent_reflections.each do |name, reflection|
association_data = self.send(name)
# has_one association can return nil
# .paranoid? will work for both instances and classes
next unless association_data && association_data.paranoid?
if reflection.collection?
next association_data.with_deleted.each(&:really_destroy!)
end
association_data.really_destroy!
end
end
write_attribute(paranoia_column, current_time_from_proper_timezone)
destroy_without_paranoia
end
end
end
private private
def paranoia_restore_attributes def paranoia_restore_attributes
...@@ -183,33 +207,9 @@ end ...@@ -183,33 +207,9 @@ end
class ActiveRecord::Base class ActiveRecord::Base
def self.acts_as_paranoid(options={}) def self.acts_as_paranoid(options={})
alias :really_destroyed? :destroyed? alias_method :really_destroyed?, :destroyed?
alias :really_delete :delete alias_method :really_delete, :delete
alias_method :destroy_without_paranoia, :destroy
alias :destroy_without_paranoia :destroy
def really_destroy!
transaction do
run_callbacks(:real_destroy) do
dependent_reflections = self.class.reflections.select do |name, reflection|
reflection.options[:dependent] == :destroy
end
if dependent_reflections.any?
dependent_reflections.each do |name, reflection|
association_data = self.send(name)
# has_one association can return nil
# .paranoid? will work for both instances and classes
next unless association_data && association_data.paranoid?
if reflection.collection?
next association_data.with_deleted.each(&:really_destroy!)
end
association_data.really_destroy!
end
end
write_attribute(paranoia_column, current_time_from_proper_timezone)
destroy_without_paranoia
end
end
end
include Paranoia include Paranoia
class_attribute :paranoia_column, :paranoia_sentinel_value class_attribute :paranoia_column, :paranoia_sentinel_value
...@@ -219,7 +219,11 @@ class ActiveRecord::Base ...@@ -219,7 +219,11 @@ class ActiveRecord::Base
def self.paranoia_scope def self.paranoia_scope
where(paranoia_column => paranoia_sentinel_value) where(paranoia_column => paranoia_sentinel_value)
end end
class << self; alias_method :without_deleted, :paranoia_scope end
unless options[:without_default_scope]
default_scope { paranoia_scope } default_scope { paranoia_scope }
end
before_restore { before_restore {
self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers) self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers)
...@@ -258,14 +262,21 @@ require 'paranoia/rspec' if defined? RSpec ...@@ -258,14 +262,21 @@ require 'paranoia/rspec' if defined? RSpec
module ActiveRecord module ActiveRecord
module Validations module Validations
class UniquenessValidator < ActiveModel::EachValidator module UniquenessParanoiaValidator
protected def build_relation(klass, table, attribute, value)
def build_relation_with_paranoia(klass, table, attribute, value) relation = super(klass, table, attribute, value)
relation = build_relation_without_paranoia(klass, table, attribute, value)
return relation unless klass.respond_to?(:paranoia_column) return relation unless klass.respond_to?(:paranoia_column)
relation.and(klass.arel_table[klass.paranoia_column].eq(klass.paranoia_sentinel_value)) arel_paranoia_scope = klass.arel_table[klass.paranoia_column].eq(klass.paranoia_sentinel_value)
if ActiveRecord::VERSION::STRING >= "5.0"
relation.where(arel_paranoia_scope)
else
relation.and(arel_paranoia_scope)
end
end end
alias_method_chain :build_relation, :paranoia end
class UniquenessValidator < ActiveModel::EachValidator
prepend UniquenessParanoiaValidator
end end
end end
end end
module Paranoia module Paranoia
VERSION = "2.1.4" VERSION = "2.2.0.alpha"
end end
...@@ -14,7 +14,9 @@ Gem::Specification.new do |s| ...@@ -14,7 +14,9 @@ Gem::Specification.new do |s|
s.required_rubygems_version = ">= 1.3.6" s.required_rubygems_version = ">= 1.3.6"
s.rubyforge_project = "paranoia" s.rubyforge_project = "paranoia"
s.add_dependency "activerecord", "~> 4.0" s.required_ruby_version = '>= 2.0'
s.add_dependency 'activerecord', '>= 4.0', '< 5.1'
s.add_development_dependency "bundler", ">= 1.0.0" s.add_development_dependency "bundler", ">= 1.0.0"
s.add_development_dependency "rake" s.add_development_dependency "rake"
......
...@@ -40,7 +40,8 @@ def setup! ...@@ -40,7 +40,8 @@ def setup!
'namespaced_paranoid_belongs_tos' => 'deleted_at DATETIME, paranoid_has_one_id INTEGER', 'namespaced_paranoid_belongs_tos' => 'deleted_at DATETIME, paranoid_has_one_id INTEGER',
'unparanoid_unique_models' => 'name VARCHAR(32), paranoid_with_unparanoids_id INTEGER', 'unparanoid_unique_models' => 'name VARCHAR(32), paranoid_with_unparanoids_id INTEGER',
'active_column_models' => 'deleted_at DATETIME, active BOOLEAN', 'active_column_models' => 'deleted_at DATETIME, active BOOLEAN',
'active_column_model_with_uniqueness_validations' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN' 'active_column_model_with_uniqueness_validations' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN',
'without_default_scope_models' => 'deleted_at DATETIME'
}.each do |table_name, columns_as_sql_string| }.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})" ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
end end
...@@ -56,8 +57,15 @@ setup! ...@@ -56,8 +57,15 @@ setup!
class ParanoiaTest < test_framework class ParanoiaTest < test_framework
def setup def setup
ActiveRecord::Base.connection.tables.each do |table| connection = ActiveRecord::Base.connection
ActiveRecord::Base.connection.execute "DELETE FROM #{table}" cleaner = ->(source) {
ActiveRecord::Base.connection.execute "DELETE FROM #{source}"
}
if ActiveRecord::VERSION::MAJOR < 5
connection.tables.each(&cleaner)
else
connection.data_sources.each(&cleaner)
end end
end end
...@@ -186,8 +194,10 @@ class ParanoiaTest < test_framework ...@@ -186,8 +194,10 @@ class ParanoiaTest < test_framework
assert_equal 0, parent1.paranoid_models.count assert_equal 0, parent1.paranoid_models.count
assert_equal 1, parent1.paranoid_models.only_deleted.count assert_equal 1, parent1.paranoid_models.only_deleted.count
assert_equal 1, parent1.paranoid_models.deleted.count assert_equal 1, parent1.paranoid_models.deleted.count
assert_equal 0, parent1.paranoid_models.without_deleted.count
p3 = ParanoidModel.create(:parent_model => parent1) p3 = ParanoidModel.create(:parent_model => parent1)
assert_equal 2, parent1.paranoid_models.with_deleted.count assert_equal 2, parent1.paranoid_models.with_deleted.count
assert_equal 1, parent1.paranoid_models.without_deleted.count
assert_equal [p1,p3], parent1.paranoid_models.with_deleted assert_equal [p1,p3], parent1.paranoid_models.with_deleted
end end
...@@ -212,6 +222,14 @@ class ParanoiaTest < test_framework ...@@ -212,6 +222,14 @@ class ParanoiaTest < test_framework
assert_equal nil, ParanoidModel.paranoia_sentinel_value assert_equal nil, ParanoidModel.paranoia_sentinel_value
end end
def test_without_default_scope_option
model = WithoutDefaultScopeModel.create
model.destroy
assert_equal 1, model.class.count
assert_equal 1, model.class.only_deleted.count
assert_equal 0, model.class.where(deleted_at: nil).count
end
def test_active_column_model def test_active_column_model
model = ActiveColumnModel.new model = ActiveColumnModel.new
assert_equal 0, model.class.count assert_equal 0, model.class.count
...@@ -291,8 +309,7 @@ class ParanoiaTest < test_framework ...@@ -291,8 +309,7 @@ class ParanoiaTest < test_framework
# Regression test for #24 # Regression test for #24
def test_chaining_for_paranoid_models def test_chaining_for_paranoid_models
scope = FeaturefulModel.where(:name => "foo").only_deleted scope = FeaturefulModel.where(:name => "foo").only_deleted
assert_equal "foo", scope.where_values_hash['name'] assert_equal({'name' => "foo"}, scope.where_values_hash)
assert_equal 2, scope.where_values.count
end end
def test_only_destroyed_scope_for_paranoid_models def test_only_destroyed_scope_for_paranoid_models
...@@ -391,9 +408,9 @@ class ParanoiaTest < test_framework ...@@ -391,9 +408,9 @@ class ParanoiaTest < test_framework
# Just to demonstrate the AR behaviour # Just to demonstrate the AR behaviour
model = NonParanoidModel.new model = NonParanoidModel.new
model.destroy! model.destroy!
assert model.really_destroyed? assert model.destroyed?
model.destroy! model.destroy!
assert model.really_destroyed? assert model.destroyed?
# Mirrors behaviour above # Mirrors behaviour above
model = ParanoidModel.new model = ParanoidModel.new
...@@ -956,7 +973,13 @@ class FailCallbackModel < ActiveRecord::Base ...@@ -956,7 +973,13 @@ class FailCallbackModel < ActiveRecord::Base
belongs_to :parent_model belongs_to :parent_model
acts_as_paranoid acts_as_paranoid
before_destroy { |_| false } before_destroy { |_|
if ActiveRecord::VERSION::MAJOR < 5
false
else
throw :abort
end
}
end end
class FeaturefulModel < ActiveRecord::Base class FeaturefulModel < ActiveRecord::Base
...@@ -1050,6 +1073,10 @@ class CustomSentinelModel < ActiveRecord::Base ...@@ -1050,6 +1073,10 @@ class CustomSentinelModel < ActiveRecord::Base
acts_as_paranoid sentinel_value: DateTime.new(0) acts_as_paranoid sentinel_value: DateTime.new(0)
end end
class WithoutDefaultScopeModel < ActiveRecord::Base
acts_as_paranoid without_default_scope: true
end
class ActiveColumnModel < ActiveRecord::Base class ActiveColumnModel < ActiveRecord::Base
acts_as_paranoid column: :active, sentinel_value: true acts_as_paranoid column: :active, sentinel_value: true
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment