mtoros-acts_as_paranoid 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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