mtoros-acts_as_paranoid 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +74 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +20 -0
- data/README.rdoc +1 -0
- data/RUNNING_UNIT_TESTS +41 -0
- data/Rakefile +17 -0
- data/acts_as_paranoid.gemspec +35 -0
- data/init.rb +34 -0
- data/lib/caboose/acts/belongs_to_with_deleted_association.rb +14 -0
- data/lib/caboose/acts/has_many_through_without_deleted_association.rb +27 -0
- data/lib/caboose/acts/paranoid.rb +196 -0
- data/lib/caboose/acts/paranoid_find_wrapper.rb +94 -0
- data/test/database.yml +18 -0
- data/test/fixtures/categories.yml +19 -0
- data/test/fixtures/categories_widgets.yml +12 -0
- data/test/fixtures/taggings.yml +9 -0
- data/test/fixtures/tags.yml +6 -0
- data/test/fixtures/widgets.yml +8 -0
- data/test/paranoid_test.rb +287 -0
- data/test/schema.rb +30 -0
- data/test/test_helper.rb +33 -0
- metadata +92 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
* (4 Oct 2007)
|
2
|
+
|
3
|
+
Update for Edge rails: remove support for legacy #count args
|
4
|
+
|
5
|
+
* (2 Feb 2007)
|
6
|
+
|
7
|
+
Add support for custom primary keys [Jeff Dean]
|
8
|
+
|
9
|
+
* (2 July 2006)
|
10
|
+
|
11
|
+
Add paranoid delete_all implementation [Marshall Roch]
|
12
|
+
|
13
|
+
* (23 May 2006)
|
14
|
+
|
15
|
+
Allow setting of future dates for content expiration.
|
16
|
+
|
17
|
+
* (15 May 2006)
|
18
|
+
|
19
|
+
Added support for dynamic finders
|
20
|
+
|
21
|
+
* (28 Mar 2006)
|
22
|
+
|
23
|
+
Updated for Rails 1.1. I love removing code.
|
24
|
+
|
25
|
+
Refactored #find method
|
26
|
+
Nested Scopes
|
27
|
+
|
28
|
+
*0.3.1* (20 Dec 2005)
|
29
|
+
|
30
|
+
* took out deleted association code for 'chainsaw butchery of base classes' [sorry Erik Terpstra]
|
31
|
+
* verified tests pass on Rails 1.0
|
32
|
+
|
33
|
+
*0.3* (27 Nov 2005)
|
34
|
+
|
35
|
+
* Deleted models will find deleted associations by default now [Erik Terpstra]
|
36
|
+
* Added :group as valid option for find [Michael Dabney]
|
37
|
+
* Changed the module namespace to Caboose::Acts::Paranoid
|
38
|
+
|
39
|
+
*0.2.0* (6 Nov 2005)
|
40
|
+
|
41
|
+
* Upgrade to Rails 1.0 RC4. ActiveRecord::Base#constrain has been replaced with scope_with.
|
42
|
+
|
43
|
+
*0.1.7* (22 Oct 2005)
|
44
|
+
|
45
|
+
* Added :with_deleted as a valid option of ActiveRecord::Base#find
|
46
|
+
|
47
|
+
*0.1.6* (25 Sep 2005)
|
48
|
+
|
49
|
+
* Fixed bug where nested constrains would get clobbered after multiple queries
|
50
|
+
|
51
|
+
*0.1.5* (22 Sep 2005)
|
52
|
+
|
53
|
+
* Fixed bug where acts_as_paranoid would clobber other constrains
|
54
|
+
* Simplified acts_as_paranoid mixin including.
|
55
|
+
|
56
|
+
*0.1.4* (18 Sep 2005)
|
57
|
+
|
58
|
+
* First RubyForge release
|
59
|
+
|
60
|
+
*0.1.3* (18 Sep 2005)
|
61
|
+
|
62
|
+
* ignore multiple calls to acts_as_paranoid on the same model
|
63
|
+
|
64
|
+
*0.1.2* (18 Sep 2005)
|
65
|
+
|
66
|
+
* fixed a bug that kept you from selecting the first deleted record
|
67
|
+
|
68
|
+
*0.1.1* (18 Sep 2005)
|
69
|
+
|
70
|
+
* Fixed bug that kept you from selecting deleted records by ID
|
71
|
+
|
72
|
+
*0.1* (17 Sep 2005)
|
73
|
+
|
74
|
+
* Initial gem
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005 Rick Olson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Manifest
|
2
|
+
README.rdoc
|
3
|
+
MIT-LICENSE
|
4
|
+
CHANGELOG
|
5
|
+
RUNNING_UNIT_TESTS
|
6
|
+
init.rb
|
7
|
+
Rakefile
|
8
|
+
lib/caboose/acts/belongs_to_with_deleted_association.rb
|
9
|
+
lib/caboose/acts/has_many_through_without_deleted_association.rb
|
10
|
+
lib/caboose/acts/paranoid.rb
|
11
|
+
lib/caboose/acts/paranoid_find_wrapper.rb
|
12
|
+
test/fixtures/categories.yml
|
13
|
+
test/fixtures/categories_widgets.yml
|
14
|
+
test/fixtures/taggings.yml
|
15
|
+
test/fixtures/tags.yml
|
16
|
+
test/fixtures/widgets.yml
|
17
|
+
test/database.yml
|
18
|
+
test/paranoid_test.rb
|
19
|
+
test/schema.rb
|
20
|
+
test/test_helper.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
empty
|
data/RUNNING_UNIT_TESTS
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
== Creating the test database
|
2
|
+
|
3
|
+
The default name for the test databases is "activerecord_paranoid". If you
|
4
|
+
want to use another database name then be sure to update the connection
|
5
|
+
adapter setups you want to test with in test/connections/<your database>/connection.rb.
|
6
|
+
When you have the database online, you can import the fixture tables with
|
7
|
+
the test/fixtures/db_definitions/*.sql files.
|
8
|
+
|
9
|
+
Make sure that you create database objects with the same user that you specified in i
|
10
|
+
connection.rb otherwise (on Postgres, at least) tests for default values will fail.
|
11
|
+
|
12
|
+
== Running with Rake
|
13
|
+
|
14
|
+
The easiest way to run the unit tests is through Rake. The default task runs
|
15
|
+
the entire test suite for all the adapters. You can also run the suite on just
|
16
|
+
one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite,
|
17
|
+
or test_postresql. For more information, checkout the full array of rake tasks with "rake -T"
|
18
|
+
|
19
|
+
Rake can be found at http://rake.rubyforge.org
|
20
|
+
|
21
|
+
== Running by hand
|
22
|
+
|
23
|
+
Unit tests are located in test directory. If you only want to run a single test suite,
|
24
|
+
or don't want to bother with Rake, you can do so with something like:
|
25
|
+
|
26
|
+
cd test; ruby -I "connections/native_mysql" base_test.rb
|
27
|
+
|
28
|
+
That'll run the base suite using the MySQL-Ruby adapter. Change the adapter
|
29
|
+
and test suite name as needed.
|
30
|
+
|
31
|
+
== Faster tests
|
32
|
+
|
33
|
+
If you are using a database that supports transactions, you can set the
|
34
|
+
"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
|
35
|
+
This gives a very large speed boost. With rake:
|
36
|
+
|
37
|
+
rake AR_TX_FIXTURES=yes
|
38
|
+
|
39
|
+
Or, by hand:
|
40
|
+
|
41
|
+
AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'echoe'
|
6
|
+
|
7
|
+
Echoe.new('acts_as_paranoid', '0.0.1') do |p|
|
8
|
+
p.summary = "Fork of acts_as_paranoid"
|
9
|
+
p.description = "check acts_as_paranoid"
|
10
|
+
p.author = ['Marko Toros']
|
11
|
+
p.email = "mtoros@gmail.com"
|
12
|
+
p.url = ""
|
13
|
+
end
|
14
|
+
rescue LoadError => boom8
|
15
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
16
|
+
puts "#{boom.to_s.capitalize}."
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{acts_as_paranoid}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Marko Toros"]
|
9
|
+
s.date = %q{2009-01-20}
|
10
|
+
s.description = %q{check acts_as_paranoid}
|
11
|
+
s.email = %q{mtoros@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["README.rdoc", "CHANGELOG", "lib/caboose/acts/belongs_to_with_deleted_association.rb", "lib/caboose/acts/has_many_through_without_deleted_association.rb", "lib/caboose/acts/paranoid.rb", "lib/caboose/acts/paranoid_find_wrapper.rb"]
|
13
|
+
s.files = ["Manifest", "README.rdoc", "MIT-LICENSE", "CHANGELOG", "RUNNING_UNIT_TESTS", "init.rb", "Rakefile", "lib/caboose/acts/belongs_to_with_deleted_association.rb", "lib/caboose/acts/has_many_through_without_deleted_association.rb", "lib/caboose/acts/paranoid.rb", "lib/caboose/acts/paranoid_find_wrapper.rb", "test/fixtures/categories.yml", "test/fixtures/categories_widgets.yml", "test/fixtures/taggings.yml", "test/fixtures/tags.yml", "test/fixtures/widgets.yml", "test/database.yml", "test/paranoid_test.rb", "test/schema.rb", "test/test_helper.rb", "acts_as_paranoid.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Acts_as_paranoid", "--main", "README.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{acts_as_paranoid}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{Fork of acts_as_paranoid}
|
21
|
+
s.test_files = ["test/test_helper.rb", "test/paranoid_test.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
34
|
+
end
|
35
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
class << ActiveRecord::Base
|
2
|
+
def belongs_to_with_deleted(association_id, options = {})
|
3
|
+
with_deleted = options.delete :with_deleted
|
4
|
+
returning belongs_to_without_deleted(association_id, options) do
|
5
|
+
if with_deleted
|
6
|
+
reflection = reflect_on_association(association_id)
|
7
|
+
association_accessor_methods(reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
|
8
|
+
association_constructor_method(:build, reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
|
9
|
+
association_constructor_method(:create, reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_many_without_deleted(association_id, options = {}, &extension)
|
15
|
+
with_deleted = options.delete :with_deleted
|
16
|
+
returning has_many_with_deleted(association_id, options, &extension) do
|
17
|
+
if options[:through] && !with_deleted
|
18
|
+
reflection = reflect_on_association(association_id)
|
19
|
+
collection_reader_method(reflection, Caboose::Acts::HasManyThroughWithoutDeletedAssociation)
|
20
|
+
collection_accessor_methods(reflection, Caboose::Acts::HasManyThroughWithoutDeletedAssociation, false)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method_chain :belongs_to, :deleted
|
26
|
+
alias_method :has_many_with_deleted, :has_many
|
27
|
+
alias_method :has_many, :has_many_without_deleted
|
28
|
+
alias_method :exists_with_deleted?, :exists?
|
29
|
+
end
|
30
|
+
ActiveRecord::Base.send :include, Caboose::Acts::Paranoid
|
31
|
+
ActiveRecord::Base.send :include, Caboose::Acts::ParanoidFindWrapper
|
32
|
+
class << ActiveRecord::Base
|
33
|
+
alias_method_chain :acts_as_paranoid, :find_wrapper
|
34
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Caboose # :nodoc:
|
2
|
+
module Acts # :nodoc:
|
3
|
+
class BelongsToWithDeletedAssociation < ActiveRecord::Associations::BelongsToAssociation
|
4
|
+
private
|
5
|
+
def find_target
|
6
|
+
@reflection.klass.find_with_deleted(
|
7
|
+
@owner[@reflection.primary_key_name],
|
8
|
+
:conditions => conditions,
|
9
|
+
:include => @reflection.options[:include]
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Caboose # :nodoc:
|
2
|
+
module Acts # :nodoc:
|
3
|
+
class HasManyThroughWithoutDeletedAssociation < ActiveRecord::Associations::HasManyThroughAssociation
|
4
|
+
protected
|
5
|
+
def current_time
|
6
|
+
ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
|
7
|
+
end
|
8
|
+
|
9
|
+
def construct_conditions
|
10
|
+
return super unless @reflection.through_reflection.klass.paranoid?
|
11
|
+
table_name = @reflection.through_reflection.table_name
|
12
|
+
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
|
13
|
+
"#{table_name}.#{attr} = #{value}"
|
14
|
+
end
|
15
|
+
|
16
|
+
deleted_attribute = @reflection.through_reflection.klass.deleted_attribute
|
17
|
+
quoted_current_time = @reflection.through_reflection.klass.quote_value(
|
18
|
+
current_time,
|
19
|
+
@reflection.through_reflection.klass.columns_hash[deleted_attribute.to_s])
|
20
|
+
conditions << "#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > #{quoted_current_time}"
|
21
|
+
|
22
|
+
conditions << sql_conditions if sql_conditions
|
23
|
+
"(" + conditions.join(') AND (') + ")"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module Caboose #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
# Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the current timestamp.
|
4
|
+
# This assumes the table has a deleted_at date/time field. Most normal model operations will work, but there will be some oddities.
|
5
|
+
#
|
6
|
+
# class Widget < ActiveRecord::Base
|
7
|
+
# acts_as_paranoid
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# Widget.find(:all)
|
11
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
12
|
+
#
|
13
|
+
# Widget.find(:first, :conditions => ['title = ?', 'test'], :order => 'title')
|
14
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL AND title = 'test' ORDER BY title LIMIT 1
|
15
|
+
#
|
16
|
+
# Widget.find_with_deleted(:all)
|
17
|
+
# # SELECT * FROM widgets
|
18
|
+
#
|
19
|
+
# Widget.find_only_deleted(:all)
|
20
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
21
|
+
#
|
22
|
+
# Widget.find_with_deleted(1).deleted?
|
23
|
+
# # Returns true if the record was previously destroyed, false if not
|
24
|
+
#
|
25
|
+
# Widget.count
|
26
|
+
# # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NULL
|
27
|
+
#
|
28
|
+
# Widget.count ['title = ?', 'test']
|
29
|
+
# # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NULL AND title = 'test'
|
30
|
+
#
|
31
|
+
# Widget.count_with_deleted
|
32
|
+
# # SELECT COUNT(*) FROM widgets
|
33
|
+
#
|
34
|
+
# Widget.count_only_deleted
|
35
|
+
# # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
36
|
+
#
|
37
|
+
# Widget.delete_all
|
38
|
+
# # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36'
|
39
|
+
#
|
40
|
+
# Widget.delete_all!
|
41
|
+
# # DELETE FROM widgets
|
42
|
+
#
|
43
|
+
# @widget.destroy
|
44
|
+
# # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36' WHERE id = 1
|
45
|
+
#
|
46
|
+
# @widget.destroy!
|
47
|
+
# # DELETE FROM widgets WHERE id = 1
|
48
|
+
#
|
49
|
+
module Paranoid
|
50
|
+
def self.included(base) # :nodoc:
|
51
|
+
base.extend ClassMethods
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
def acts_as_paranoid(options = {})
|
56
|
+
unless paranoid? # don't let AR call this twice
|
57
|
+
cattr_accessor :deleted_attribute
|
58
|
+
self.deleted_attribute = options[:with] || :deleted_at
|
59
|
+
alias_method :destroy_without_callbacks!, :destroy_without_callbacks
|
60
|
+
class << self
|
61
|
+
alias_method :find_every_with_deleted, :find_every
|
62
|
+
alias_method :calculate_with_deleted, :calculate
|
63
|
+
alias_method :delete_all!, :delete_all
|
64
|
+
end
|
65
|
+
end
|
66
|
+
include InstanceMethods
|
67
|
+
end
|
68
|
+
|
69
|
+
def paranoid?
|
70
|
+
self.included_modules.include?(InstanceMethods)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods #:nodoc:
|
75
|
+
def self.included(base) # :nodoc:
|
76
|
+
base.extend ClassMethods
|
77
|
+
end
|
78
|
+
|
79
|
+
module ClassMethods
|
80
|
+
def find_with_deleted(*args)
|
81
|
+
options = args.extract_options!
|
82
|
+
validate_find_options(options)
|
83
|
+
set_readonly_option!(options)
|
84
|
+
options[:with_deleted] = true # yuck!
|
85
|
+
|
86
|
+
case args.first
|
87
|
+
when :first then find_initial(options)
|
88
|
+
when :all then find_every(options)
|
89
|
+
else find_from_ids(args, options)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_only_deleted(*args)
|
94
|
+
options = args.extract_options!
|
95
|
+
validate_find_options(options)
|
96
|
+
set_readonly_option!(options)
|
97
|
+
options[:only_deleted] = true # yuck!
|
98
|
+
|
99
|
+
case args.first
|
100
|
+
when :first then find_initial(options)
|
101
|
+
when :all then find_every(options)
|
102
|
+
else find_from_ids(args, options)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def exists?(*args)
|
107
|
+
with_deleted_scope { exists_with_deleted?(*args) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def exists_only_deleted?(*args)
|
111
|
+
with_only_deleted_scope { exists_with_deleted?(*args) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def count_with_deleted(*args)
|
115
|
+
calculate_with_deleted(:count, *construct_count_options_from_args(*args))
|
116
|
+
end
|
117
|
+
|
118
|
+
def count_only_deleted(*args)
|
119
|
+
with_only_deleted_scope { count_with_deleted(*args) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def count(*args)
|
123
|
+
with_deleted_scope { count_with_deleted(*args) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def calculate(*args)
|
127
|
+
with_deleted_scope { calculate_with_deleted(*args) }
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete_all(conditions = nil)
|
131
|
+
self.update_all ["#{self.deleted_attribute} = ?", current_time], conditions
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
def current_time
|
136
|
+
default_timezone == :utc ? Time.now.utc : Time.now
|
137
|
+
end
|
138
|
+
|
139
|
+
def with_deleted_scope(&block)
|
140
|
+
with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > ?", current_time] } }, :merge, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def with_only_deleted_scope(&block)
|
144
|
+
with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NOT NULL AND #{table_name}.#{deleted_attribute} <= ?", current_time] } }, :merge, &block)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
# all find calls lead here
|
149
|
+
def find_every(options)
|
150
|
+
options.delete(:with_deleted) ?
|
151
|
+
find_every_with_deleted(options) :
|
152
|
+
options.delete(:only_deleted) ?
|
153
|
+
with_only_deleted_scope { find_every_with_deleted(options) } :
|
154
|
+
with_deleted_scope { find_every_with_deleted(options) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def destroy_without_callbacks
|
159
|
+
unless new_record?
|
160
|
+
self.class.update_all self.class.send(:sanitize_sql, ["#{self.class.deleted_attribute} = ?", (self.deleted_at = self.class.send(:current_time))]), ["#{self.class.primary_key} = ?", id]
|
161
|
+
end
|
162
|
+
freeze
|
163
|
+
end
|
164
|
+
|
165
|
+
def destroy_with_callbacks!
|
166
|
+
return false if callback(:before_destroy) == false
|
167
|
+
result = destroy_without_callbacks!
|
168
|
+
callback(:after_destroy)
|
169
|
+
result
|
170
|
+
end
|
171
|
+
|
172
|
+
def destroy!
|
173
|
+
transaction { destroy_with_callbacks! }
|
174
|
+
end
|
175
|
+
|
176
|
+
def deleted?
|
177
|
+
!!read_attribute(:deleted_at)
|
178
|
+
end
|
179
|
+
|
180
|
+
def recover!
|
181
|
+
self.deleted_at = nil
|
182
|
+
save!
|
183
|
+
end
|
184
|
+
|
185
|
+
def recover_with_associations!(*associations)
|
186
|
+
self.recover!
|
187
|
+
associations.to_a.each do |assoc|
|
188
|
+
self.send(assoc).find_with_deleted(:all).each do |a|
|
189
|
+
a.recover! if a.class.paranoid?
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Caboose #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
# Adds a wrapper find method which can identify :with_deleted or :only_deleted options
|
4
|
+
# and would call the corresponding acts_as_paranoid finders find_with_deleted or
|
5
|
+
# find_only_deleted methods.
|
6
|
+
#
|
7
|
+
# With this wrapper you can easily change from using this pattern:
|
8
|
+
#
|
9
|
+
# if some_condition_enabling_access_to_deleted_records?
|
10
|
+
# @post = Post.find_with_deleted(params[:id])
|
11
|
+
# else
|
12
|
+
# @post = Post.find(params[:id])
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# to this:
|
16
|
+
#
|
17
|
+
# @post = Post.find(params[:id], :with_deleted => some_condition_enabling_access_to_deleted_records?)
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# class Widget < ActiveRecord::Base
|
22
|
+
# acts_as_paranoid
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# Widget.find(:all)
|
26
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
27
|
+
#
|
28
|
+
# Widget.find(:all, :with_deleted => false)
|
29
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
30
|
+
#
|
31
|
+
# Widget.find_with_deleted(:all)
|
32
|
+
# # SELECT * FROM widgets
|
33
|
+
#
|
34
|
+
# Widget.find(:all, :with_deleted => true)
|
35
|
+
# # SELECT * FROM widgets
|
36
|
+
#
|
37
|
+
# Widget.find_only_deleted(:all)
|
38
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
39
|
+
#
|
40
|
+
# Widget.find(:all, :only_deleted => true)
|
41
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
42
|
+
#
|
43
|
+
# Widget.find(:all, :only_deleted => false)
|
44
|
+
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
45
|
+
#
|
46
|
+
module ParanoidFindWrapper
|
47
|
+
def self.included(base) # :nodoc:
|
48
|
+
base.extend ClassMethods
|
49
|
+
end
|
50
|
+
|
51
|
+
module ClassMethods
|
52
|
+
def acts_as_paranoid_with_find_wrapper(options = {})
|
53
|
+
unless paranoid? # don't let AR call this twice
|
54
|
+
acts_as_paranoid_without_find_wrapper(options)
|
55
|
+
class << self
|
56
|
+
alias_method :find_without_find_wrapper, :find
|
57
|
+
alias_method :validate_find_options_without_find_wrapper, :validate_find_options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
include InstanceMethods
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module InstanceMethods #:nodoc:
|
65
|
+
def self.included(base) # :nodoc:
|
66
|
+
base.extend ClassMethods
|
67
|
+
end
|
68
|
+
|
69
|
+
module ClassMethods
|
70
|
+
# This is a wrapper for the regular "find" so you can pass acts_as_paranoid related
|
71
|
+
# options and determine which finder to call.
|
72
|
+
def find(*args)
|
73
|
+
options = args.extract_options!
|
74
|
+
# Determine who to call.
|
75
|
+
finder_option = VALID_PARANOID_FIND_OPTIONS.detect { |key| options.delete(key) } || :without_find_wrapper
|
76
|
+
finder_method = "find_#{finder_option}".to_sym
|
77
|
+
# Put back the options in the args now that they don't include the extended keys.
|
78
|
+
args << options
|
79
|
+
send(finder_method, *args)
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
VALID_PARANOID_FIND_OPTIONS = [:with_deleted, :only_deleted]
|
85
|
+
|
86
|
+
def validate_find_options(options) #:nodoc:
|
87
|
+
cleaned_options = options.reject { |k, v| VALID_PARANOID_FIND_OPTIONS.include?(k) }
|
88
|
+
validate_find_options_without_find_wrapper(cleaned_options)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
sqlite:
|
2
|
+
:adapter: sqlite
|
3
|
+
:dbfile: acts_as_paranoid_plugin.sqlite.db
|
4
|
+
sqlite3:
|
5
|
+
:adapter: sqlite3
|
6
|
+
:dbfile: acts_as_paranoid_plugin.sqlite3.db
|
7
|
+
postgresql:
|
8
|
+
:adapter: postgresql
|
9
|
+
:username: postgres
|
10
|
+
:password: postgres
|
11
|
+
:database: acts_as_paranoid_plugin_test
|
12
|
+
:min_messages: ERROR
|
13
|
+
mysql:
|
14
|
+
:adapter: mysql
|
15
|
+
:host: localhost
|
16
|
+
:username: rails
|
17
|
+
:password:
|
18
|
+
:database: acts_as_paranoid_plugin_test
|
@@ -0,0 +1,19 @@
|
|
1
|
+
category_1:
|
2
|
+
id: 1
|
3
|
+
widget_id: 1
|
4
|
+
title: 'category 1'
|
5
|
+
category_2:
|
6
|
+
id: 2
|
7
|
+
widget_id: 1
|
8
|
+
title: 'category 2'
|
9
|
+
deleted_at: '2005-01-01 00:00:00'
|
10
|
+
category_3:
|
11
|
+
id: 3
|
12
|
+
widget_id: 2
|
13
|
+
title: 'category 3'
|
14
|
+
deleted_at: '2005-01-01 00:00:00'
|
15
|
+
category_4:
|
16
|
+
id: 4
|
17
|
+
widget_id: 2
|
18
|
+
title: 'category 4'
|
19
|
+
deleted_at: '2005-01-01 00:00:00'
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Widget < ActiveRecord::Base
|
4
|
+
acts_as_paranoid
|
5
|
+
has_many :categories, :dependent => :destroy
|
6
|
+
has_and_belongs_to_many :habtm_categories, :class_name => 'Category'
|
7
|
+
has_one :category
|
8
|
+
belongs_to :parent_category, :class_name => 'Category'
|
9
|
+
has_many :taggings
|
10
|
+
has_many :tags, :through => :taggings
|
11
|
+
has_many :any_tags, :through => :taggings, :class_name => 'Tag', :source => :tag, :with_deleted => true
|
12
|
+
end
|
13
|
+
|
14
|
+
class Category < ActiveRecord::Base
|
15
|
+
belongs_to :widget
|
16
|
+
belongs_to :any_widget, :class_name => 'Widget', :foreign_key => 'widget_id', :with_deleted => true
|
17
|
+
acts_as_paranoid
|
18
|
+
|
19
|
+
def self.search(name, options = {})
|
20
|
+
find :all, options.merge(:conditions => ['LOWER(title) LIKE ?', "%#{name.to_s.downcase}%"])
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.search_with_deleted(name, options = {})
|
24
|
+
find_with_deleted :all, options.merge(:conditions => ['LOWER(title) LIKE ?', "%#{name.to_s.downcase}%"])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Tag < ActiveRecord::Base
|
29
|
+
has_many :taggings
|
30
|
+
has_many :widgets, :through => :taggings
|
31
|
+
end
|
32
|
+
|
33
|
+
class Tagging < ActiveRecord::Base
|
34
|
+
belongs_to :tag
|
35
|
+
belongs_to :widget
|
36
|
+
acts_as_paranoid
|
37
|
+
end
|
38
|
+
|
39
|
+
class NonParanoidAndroid < ActiveRecord::Base
|
40
|
+
end
|
41
|
+
|
42
|
+
class ParanoidTest < Test::Unit::TestCase
|
43
|
+
fixtures :widgets, :categories, :categories_widgets, :tags, :taggings
|
44
|
+
|
45
|
+
def test_should_recognize_with_deleted_option
|
46
|
+
assert_equal [1, 2], Widget.find(:all, :with_deleted => true).collect { |w| w.id }
|
47
|
+
assert_equal [1], Widget.find(:all, :with_deleted => false).collect { |w| w.id }
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_should_recognize_only_deleted_option
|
51
|
+
assert_equal [2], Widget.find(:all, :only_deleted => true).collect { |w| w.id }
|
52
|
+
assert_equal [1], Widget.find(:all, :only_deleted => false).collect { |w| w.id }
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_should_exists_with_deleted
|
56
|
+
assert Widget.exists_with_deleted?(2)
|
57
|
+
assert !Widget.exists?(2)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_should_exists_only_deleted
|
61
|
+
assert Widget.exists_only_deleted?(2)
|
62
|
+
assert !Widget.exists_only_deleted?(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_should_count_with_deleted
|
66
|
+
assert_equal 1, Widget.count
|
67
|
+
assert_equal 2, Widget.count_with_deleted
|
68
|
+
assert_equal 1, Widget.count_only_deleted
|
69
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_should_set_deleted_at
|
73
|
+
assert_equal 1, Widget.count
|
74
|
+
assert_equal 1, Category.count
|
75
|
+
widgets(:widget_1).destroy
|
76
|
+
assert_equal 0, Widget.count
|
77
|
+
assert_equal 0, Category.count
|
78
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
79
|
+
assert_equal 4, Category.calculate_with_deleted(:count, :all)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_should_destroy
|
83
|
+
assert_equal 1, Widget.count
|
84
|
+
assert_equal 1, Category.count
|
85
|
+
widgets(:widget_1).destroy!
|
86
|
+
assert_equal 0, Widget.count
|
87
|
+
assert_equal 0, Category.count
|
88
|
+
assert_equal 1, Widget.count_only_deleted
|
89
|
+
assert_equal 1, Widget.calculate_with_deleted(:count, :all)
|
90
|
+
# Category doesn't get destroyed because the dependent before_destroy callback uses #destroy
|
91
|
+
assert_equal 4, Category.calculate_with_deleted(:count, :all)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_should_delete_all
|
95
|
+
assert_equal 1, Widget.count
|
96
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
97
|
+
assert_equal 1, Category.count
|
98
|
+
Widget.delete_all
|
99
|
+
assert_equal 0, Widget.count
|
100
|
+
# delete_all doesn't call #destroy, so the dependent callback never fires
|
101
|
+
assert_equal 1, Category.count
|
102
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_should_delete_all_with_conditions
|
106
|
+
assert_equal 1, Widget.count
|
107
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
108
|
+
Widget.delete_all("id < 3")
|
109
|
+
assert_equal 0, Widget.count
|
110
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_should_delete_all2
|
114
|
+
assert_equal 1, Category.count
|
115
|
+
assert_equal 4, Category.calculate_with_deleted(:count, :all)
|
116
|
+
Category.delete_all!
|
117
|
+
assert_equal 0, Category.count
|
118
|
+
assert_equal 0, Category.calculate_with_deleted(:count, :all)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_should_delete_all_with_conditions2
|
122
|
+
assert_equal 1, Category.count
|
123
|
+
assert_equal 4, Category.calculate_with_deleted(:count, :all)
|
124
|
+
Category.delete_all!("id < 3")
|
125
|
+
assert_equal 0, Category.count
|
126
|
+
assert_equal 2, Category.calculate_with_deleted(:count, :all)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_should_not_count_deleted
|
130
|
+
assert_equal 1, Widget.count
|
131
|
+
assert_equal 1, Widget.count(:all, :conditions => ['title=?', 'widget 1'])
|
132
|
+
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
133
|
+
assert_equal 1, Widget.count_only_deleted
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_should_find_only_deleted
|
137
|
+
assert_equal [2], Widget.find_only_deleted(:all).collect { |w| w.id }
|
138
|
+
assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id }
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_should_not_find_deleted
|
142
|
+
assert_equal [widgets(:widget_1)], Widget.find(:all)
|
143
|
+
assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id }
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_should_not_find_deleted_has_many_associations
|
147
|
+
assert_equal 1, widgets(:widget_1).categories.size
|
148
|
+
assert_equal [categories(:category_1)], widgets(:widget_1).categories
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_should_not_find_deleted_habtm_associations
|
152
|
+
assert_equal 1, widgets(:widget_1).habtm_categories.size
|
153
|
+
assert_equal [categories(:category_1)], widgets(:widget_1).habtm_categories
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_should_not_find_deleted_has_many_through_associations
|
157
|
+
assert_equal 1, widgets(:widget_1).tags.size
|
158
|
+
assert_equal [tags(:tag_2)], widgets(:widget_1).tags
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_should_find_has_many_through_associations_with_deleted
|
162
|
+
assert_equal 2, widgets(:widget_1).any_tags.size
|
163
|
+
assert_equal Tag.find(:all), widgets(:widget_1).any_tags
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_should_not_find_deleted_belongs_to_associations
|
167
|
+
assert_nil Category.find_with_deleted(3).widget
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_should_find_belongs_to_assocation_with_deleted
|
171
|
+
assert_equal Widget.find_with_deleted(2), Category.find_with_deleted(3).any_widget
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_should_find_first_with_deleted
|
175
|
+
assert_equal widgets(:widget_1), Widget.find(:first)
|
176
|
+
assert_equal 2, Widget.find_with_deleted(:first, :order => 'id desc').id
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_should_find_single_id
|
180
|
+
assert Widget.find(1)
|
181
|
+
assert Widget.find_with_deleted(2)
|
182
|
+
assert_raises(ActiveRecord::RecordNotFound) { Widget.find(2) }
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_should_find_multiple_ids
|
186
|
+
assert_equal [1,2], Widget.find_with_deleted(1,2).sort_by { |w| w.id }.collect { |w| w.id }
|
187
|
+
assert_equal [1,2], Widget.find_with_deleted([1,2]).sort_by { |w| w.id }.collect { |w| w.id }
|
188
|
+
assert_raises(ActiveRecord::RecordNotFound) { Widget.find(1,2) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_should_ignore_multiple_includes
|
192
|
+
Widget.class_eval { acts_as_paranoid }
|
193
|
+
assert Widget.find(1)
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_should_not_override_scopes_when_counting
|
197
|
+
assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.count }
|
198
|
+
assert_equal 0, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.count }
|
199
|
+
assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.calculate_with_deleted(:count, :all) }
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_should_not_override_scopes_when_finding
|
203
|
+
assert_equal [1], Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.find(:all) }.ids
|
204
|
+
assert_equal [], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find(:all) }.ids
|
205
|
+
assert_equal [2], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find_with_deleted(:all) }.ids
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_should_allow_multiple_scoped_calls_when_finding
|
209
|
+
Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do
|
210
|
+
assert_equal [2], Widget.find_with_deleted(:all).ids
|
211
|
+
assert_equal [2], Widget.find_with_deleted(:all).ids, "clobbers the constrain on the unmodified find"
|
212
|
+
assert_equal [], Widget.find(:all).ids
|
213
|
+
assert_equal [], Widget.find(:all).ids, 'clobbers the constrain on a paranoid find'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_should_allow_multiple_scoped_calls_when_counting
|
218
|
+
Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do
|
219
|
+
assert_equal 1, Widget.calculate_with_deleted(:count, :all)
|
220
|
+
assert_equal 1, Widget.calculate_with_deleted(:count, :all), "clobbers the constrain on the unmodified find"
|
221
|
+
assert_equal 0, Widget.count
|
222
|
+
assert_equal 0, Widget.count, 'clobbers the constrain on a paranoid find'
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_should_give_paranoid_status
|
227
|
+
assert Widget.paranoid?
|
228
|
+
assert !NonParanoidAndroid.paranoid?
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_should_give_record_status
|
232
|
+
assert_equal false, Widget.find(1).deleted?
|
233
|
+
Widget.find(1).destroy
|
234
|
+
assert Widget.find_with_deleted(1).deleted?
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_should_find_deleted_has_many_assocations_on_deleted_records_by_default
|
238
|
+
w = Widget.find_with_deleted 2
|
239
|
+
assert_equal 2, w.categories.find_with_deleted(:all).length
|
240
|
+
assert_equal 2, w.categories.find_with_deleted(:all).size
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_should_find_deleted_habtm_assocations_on_deleted_records_by_default
|
244
|
+
w = Widget.find_with_deleted 2
|
245
|
+
assert_equal 2, w.habtm_categories.find_with_deleted(:all).length
|
246
|
+
assert_equal 2, w.habtm_categories.find_with_deleted(:all).size
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_dynamic_finders
|
250
|
+
assert Widget.find_by_id(1)
|
251
|
+
assert_nil Widget.find_by_id(2)
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_custom_finder_methods
|
255
|
+
w = Widget.find_with_deleted(:all).inject({}) { |all, w| all.merge(w.id => w) }
|
256
|
+
assert_equal [1], Category.search('c').ids
|
257
|
+
assert_equal [1,2,3,4], Category.search_with_deleted('c', :order => 'id').ids
|
258
|
+
assert_equal [1], widgets(:widget_1).categories.search('c').collect(&:id)
|
259
|
+
assert_equal [1,2], widgets(:widget_1).categories.search_with_deleted('c').ids
|
260
|
+
assert_equal [], w[2].categories.search('c').ids
|
261
|
+
assert_equal [3,4], w[2].categories.search_with_deleted('c').ids
|
262
|
+
end
|
263
|
+
|
264
|
+
def test_should_recover_record
|
265
|
+
Widget.find(1).destroy
|
266
|
+
assert_equal true, Widget.find_with_deleted(1).deleted?
|
267
|
+
|
268
|
+
Widget.find_with_deleted(1).recover!
|
269
|
+
assert_equal false, Widget.find(1).deleted?
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_should_recover_record_and_has_many_associations
|
273
|
+
Widget.find(1).destroy
|
274
|
+
assert_equal true, Widget.find_with_deleted(1).deleted?
|
275
|
+
assert_equal true, Category.find_with_deleted(1).deleted?
|
276
|
+
|
277
|
+
Widget.find_with_deleted(1).recover_with_associations!(:categories)
|
278
|
+
assert_equal false, Widget.find(1).deleted?
|
279
|
+
assert_equal false, Category.find(1).deleted?
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class Array
|
284
|
+
def ids
|
285
|
+
collect &:id
|
286
|
+
end
|
287
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 1) do
|
2
|
+
|
3
|
+
create_table :widgets, :force => true do |t|
|
4
|
+
t.column :title, :string, :limit => 50
|
5
|
+
t.column :category_id, :integer
|
6
|
+
t.column :deleted_at, :timestamp
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :categories, :force => true do |t|
|
10
|
+
t.column :widget_id, :integer
|
11
|
+
t.column :title, :string, :limit => 50
|
12
|
+
t.column :deleted_at, :timestamp
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :categories_widgets, :force => true, :id => false do |t|
|
16
|
+
t.column :category_id, :integer
|
17
|
+
t.column :widget_id, :integer
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :tags, :force => true do |t|
|
21
|
+
t.column :name, :string, :limit => 50
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table :taggings, :force => true do |t|
|
25
|
+
t.column :tag_id, :integer
|
26
|
+
t.column :widget_id, :integer
|
27
|
+
t.column :deleted_at, :timestamp
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
|
5
|
+
require 'rubygems'
|
6
|
+
require 'active_record/fixtures'
|
7
|
+
|
8
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
9
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
10
|
+
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
11
|
+
|
12
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
13
|
+
|
14
|
+
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
15
|
+
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
|
16
|
+
|
17
|
+
class Test::Unit::TestCase #:nodoc:
|
18
|
+
def create_fixtures(*table_names)
|
19
|
+
if block_given?
|
20
|
+
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
21
|
+
else
|
22
|
+
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
27
|
+
self.use_transactional_fixtures = true
|
28
|
+
|
29
|
+
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
30
|
+
self.use_instantiated_fixtures = false
|
31
|
+
|
32
|
+
# Add more helper methods to be used by all tests here...
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mtoros-acts_as_paranoid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marko Toros
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-20 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: echoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
description: check acts_as_paranoid
|
25
|
+
email: mtoros@gmail.com
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.rdoc
|
32
|
+
- CHANGELOG
|
33
|
+
- lib/caboose/acts/belongs_to_with_deleted_association.rb
|
34
|
+
- lib/caboose/acts/has_many_through_without_deleted_association.rb
|
35
|
+
- lib/caboose/acts/paranoid.rb
|
36
|
+
- lib/caboose/acts/paranoid_find_wrapper.rb
|
37
|
+
files:
|
38
|
+
- Manifest
|
39
|
+
- README.rdoc
|
40
|
+
- MIT-LICENSE
|
41
|
+
- CHANGELOG
|
42
|
+
- RUNNING_UNIT_TESTS
|
43
|
+
- init.rb
|
44
|
+
- Rakefile
|
45
|
+
- lib/caboose/acts/belongs_to_with_deleted_association.rb
|
46
|
+
- lib/caboose/acts/has_many_through_without_deleted_association.rb
|
47
|
+
- lib/caboose/acts/paranoid.rb
|
48
|
+
- lib/caboose/acts/paranoid_find_wrapper.rb
|
49
|
+
- test/fixtures/categories.yml
|
50
|
+
- test/fixtures/categories_widgets.yml
|
51
|
+
- test/fixtures/taggings.yml
|
52
|
+
- test/fixtures/tags.yml
|
53
|
+
- test/fixtures/widgets.yml
|
54
|
+
- test/database.yml
|
55
|
+
- test/paranoid_test.rb
|
56
|
+
- test/schema.rb
|
57
|
+
- test/test_helper.rb
|
58
|
+
- acts_as_paranoid.gemspec
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: ""
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --line-numbers
|
64
|
+
- --inline-source
|
65
|
+
- --title
|
66
|
+
- Acts_as_paranoid
|
67
|
+
- --main
|
68
|
+
- README.rdoc
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "1.2"
|
82
|
+
version:
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project: acts_as_paranoid
|
86
|
+
rubygems_version: 1.2.0
|
87
|
+
signing_key:
|
88
|
+
specification_version: 2
|
89
|
+
summary: Fork of acts_as_paranoid
|
90
|
+
test_files:
|
91
|
+
- test/test_helper.rb
|
92
|
+
- test/paranoid_test.rb
|