Commit f04b13ac by Emanuel Evans

Merge branch 'rails4' into fix_null_transaction

parents 0db09109 9e0ec38e
sudo: false
language: ruby
rvm:
- 2.0.0
- 2.1.8
- 2.2.4
- 2.3.0
- jruby-19mode
- jruby-9.0.5.0
env:
matrix:
- RAILS='~> 4.0.13'
- RAILS='~> 4.1.10'
- 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
## 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)
### Major changes
......
# 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.
......@@ -20,7 +20,7 @@ For Rails 3, please use version 1 of Paranoia:
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
gem "paranoia", "~> 2.0"
......@@ -104,6 +104,17 @@ class Client < ActiveRecord::Base
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:
``` ruby
......@@ -128,6 +139,12 @@ If you want to find all records, even those which are 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:
``` ruby
......@@ -187,7 +204,7 @@ Of course, this is not necessary for the indexes you always use in association w
##### 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
with a certain combination of values.
......@@ -244,9 +261,9 @@ The `recover` method in `acts_as_paranoid` runs `update` callbacks. Paranoia's
## 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
class Product < ActiveRecord::Base
......
......@@ -38,7 +38,7 @@ module Paranoia
quoted_paranoia_column = connection.quote_column_name(paranoia_column)
with_deleted.where("#{quoted_paranoia_column} IS NULL OR #{quoted_paranoia_column} != ?", paranoia_sentinel_value)
end
alias :deleted :only_deleted
alias_method :deleted, :only_deleted
def restore(id_or_ids, opts = {})
ids = Array(id_or_ids).flatten
......@@ -125,6 +125,30 @@ module Paranoia
end
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
def paranoia_restore_attributes
......@@ -183,33 +207,9 @@ end
class ActiveRecord::Base
def self.acts_as_paranoid(options={})
alias :really_destroyed? :destroyed?
alias :really_delete :delete
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
alias_method :really_destroyed?, :destroyed?
alias_method :really_delete, :delete
alias_method :destroy_without_paranoia, :destroy
include Paranoia
class_attribute :paranoia_column, :paranoia_sentinel_value
......@@ -219,7 +219,11 @@ class ActiveRecord::Base
def self.paranoia_scope
where(paranoia_column => paranoia_sentinel_value)
end
class << self; alias_method :without_deleted, :paranoia_scope end
unless options[:without_default_scope]
default_scope { paranoia_scope }
end
before_restore {
self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers)
......@@ -258,14 +262,21 @@ require 'paranoia/rspec' if defined? RSpec
module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator
protected
def build_relation_with_paranoia(klass, table, attribute, value)
relation = build_relation_without_paranoia(klass, table, attribute, value)
module UniquenessParanoiaValidator
def build_relation(klass, table, attribute, value)
relation = super(klass, table, attribute, value)
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
alias_method_chain :build_relation, :paranoia
end
class UniquenessValidator < ActiveModel::EachValidator
prepend UniquenessParanoiaValidator
end
end
end
module Paranoia
VERSION = "2.1.4"
VERSION = "2.2.0.alpha"
end
......@@ -14,7 +14,9 @@ Gem::Specification.new do |s|
s.required_rubygems_version = ">= 1.3.6"
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 "rake"
......
......@@ -40,7 +40,8 @@ def setup!
'namespaced_paranoid_belongs_tos' => 'deleted_at DATETIME, paranoid_has_one_id INTEGER',
'unparanoid_unique_models' => 'name VARCHAR(32), paranoid_with_unparanoids_id INTEGER',
'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|
ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
end
......@@ -56,8 +57,15 @@ setup!
class ParanoiaTest < test_framework
def setup
ActiveRecord::Base.connection.tables.each do |table|
ActiveRecord::Base.connection.execute "DELETE FROM #{table}"
connection = ActiveRecord::Base.connection
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
......@@ -186,8 +194,10 @@ class ParanoiaTest < test_framework
assert_equal 0, parent1.paranoid_models.count
assert_equal 1, parent1.paranoid_models.only_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)
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
end
......@@ -212,6 +222,14 @@ class ParanoiaTest < test_framework
assert_equal nil, ParanoidModel.paranoia_sentinel_value
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
model = ActiveColumnModel.new
assert_equal 0, model.class.count
......@@ -291,8 +309,7 @@ class ParanoiaTest < test_framework
# Regression test for #24
def test_chaining_for_paranoid_models
scope = FeaturefulModel.where(:name => "foo").only_deleted
assert_equal "foo", scope.where_values_hash['name']
assert_equal 2, scope.where_values.count
assert_equal({'name' => "foo"}, scope.where_values_hash)
end
def test_only_destroyed_scope_for_paranoid_models
......@@ -391,9 +408,9 @@ class ParanoiaTest < test_framework
# Just to demonstrate the AR behaviour
model = NonParanoidModel.new
model.destroy!
assert model.really_destroyed?
assert model.destroyed?
model.destroy!
assert model.really_destroyed?
assert model.destroyed?
# Mirrors behaviour above
model = ParanoidModel.new
......@@ -956,7 +973,13 @@ class FailCallbackModel < ActiveRecord::Base
belongs_to :parent_model
acts_as_paranoid
before_destroy { |_| false }
before_destroy { |_|
if ActiveRecord::VERSION::MAJOR < 5
false
else
throw :abort
end
}
end
class FeaturefulModel < ActiveRecord::Base
......@@ -1050,6 +1073,10 @@ class CustomSentinelModel < ActiveRecord::Base
acts_as_paranoid sentinel_value: DateTime.new(0)
end
class WithoutDefaultScopeModel < ActiveRecord::Base
acts_as_paranoid without_default_scope: true
end
class ActiveColumnModel < ActiveRecord::Base
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