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 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
@@ -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,12 @@
1
+ cw_1:
2
+ category_id: 1
3
+ widget_id: 1
4
+ cw_2:
5
+ category_id: 2
6
+ widget_id: 1
7
+ cw_3:
8
+ category_id: 3
9
+ widget_id: 2
10
+ cw_4:
11
+ category_id: 4
12
+ widget_id: 2
@@ -0,0 +1,9 @@
1
+ tagging_1:
2
+ id: 1
3
+ tag_id: 1
4
+ widget_id: 1
5
+ deleted_at: '2005-01-01 00:00:00'
6
+ tagging_2:
7
+ id: 2
8
+ tag_id: 2
9
+ widget_id: 1
@@ -0,0 +1,6 @@
1
+ tag_1:
2
+ id: 1
3
+ name: 'tag 1'
4
+ tag_2:
5
+ id: 2
6
+ name: 'tag 1'
@@ -0,0 +1,8 @@
1
+ widget_1:
2
+ id: 1
3
+ title: 'widget 1'
4
+ widget_2:
5
+ id: 2
6
+ title: 'deleted widget 2'
7
+ deleted_at: '2005-01-01 00:00:00'
8
+ category_id: 3
@@ -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
@@ -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