pager-acts_as_paranoid 1.0.20080505

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/README ADDED
@@ -0,0 +1,26 @@
1
+ = acts_as_paranoid
2
+
3
+ Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the
4
+ current timestamp. ActiveRecord is required.
5
+
6
+ == Resources
7
+
8
+ Install
9
+
10
+ * gem install acts_as_paranoid
11
+
12
+ Rubyforge project
13
+
14
+ * http://rubyforge.org/projects/ar-paranoid
15
+
16
+ RDocs
17
+
18
+ * http://ar-paranoid.rubyforge.org
19
+
20
+ Subversion
21
+
22
+ * http://techno-weenie.net/svn/projects/acts_as_paranoid
23
+
24
+ Collaboa
25
+
26
+ * http://collaboa.techno-weenie.net/repository/browse/acts_as_paranoid
@@ -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,180 @@
1
+ require 'rubygems'
2
+
3
+ Gem::manage_gems
4
+
5
+ require 'rake/rdoctask'
6
+ require 'rake/packagetask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/testtask'
9
+ require 'rake/contrib/rubyforgepublisher'
10
+
11
+ PKG_NAME = 'acts_as_paranoid'
12
+ PKG_VERSION = '0.3.1'
13
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
14
+ PROD_HOST = "technoweenie@bidwell.textdrive.com"
15
+ RUBY_FORGE_PROJECT = 'ar-paranoid'
16
+ RUBY_FORGE_USER = 'technoweenie'
17
+
18
+ desc 'Default: run unit tests.'
19
+ task :default => :test
20
+
21
+ desc 'Test the calculations plugin.'
22
+ Rake::TestTask.new(:test) do |t|
23
+ t.libs << 'lib'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = true
26
+ end
27
+
28
+ desc 'Generate documentation for the acts_as_paranoid plugin.'
29
+ Rake::RDocTask.new do |rdoc|
30
+ rdoc.rdoc_dir = 'html'
31
+ rdoc.title = "#{PKG_NAME} -- protect your ActiveRecord objects from accidental deletion"
32
+ rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object'
33
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
34
+ rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS')
35
+ rdoc.rdoc_files.include('lib/**/*.rb')
36
+ end
37
+
38
+ spec = Gem::Specification.new do |s|
39
+ s.name = PKG_NAME
40
+ s.version = PKG_VERSION
41
+ s.platform = Gem::Platform::RUBY
42
+ s.summary = "acts_as_paranoid keeps models from actually being deleted by setting a deleted_at field."
43
+ s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS)
44
+ s.files.delete "acts_as_paranoid_plugin.sqlite.db"
45
+ s.files.delete "acts_as_paranoid_plugin.sqlite3.db"
46
+ s.require_path = 'lib'
47
+ s.autorequire = 'acts_as_paranoid'
48
+ s.has_rdoc = true
49
+ s.test_files = Dir['test/**/*_test.rb']
50
+ s.author = "Rick Olson"
51
+ s.email = "technoweenie@gmail.com"
52
+ s.homepage = "http://techno-weenie.net"
53
+ end
54
+
55
+ Rake::GemPackageTask.new(spec) do |pkg|
56
+ pkg.need_tar = true
57
+ end
58
+
59
+ desc "Publish the API documentation"
60
+ task :pdoc => [:rdoc] do
61
+ Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
62
+ end
63
+
64
+ desc 'Publish the gem and API docs'
65
+ task :publish => [:pdoc, :rubyforge_upload]
66
+
67
+ desc "Publish the release files to RubyForge."
68
+ task :rubyforge_upload => :package do
69
+ files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
70
+
71
+ if RUBY_FORGE_PROJECT then
72
+ require 'net/http'
73
+ require 'open-uri'
74
+
75
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
76
+ project_data = open(project_uri) { |data| data.read }
77
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
78
+ raise "Couldn't get group id" unless group_id
79
+
80
+ # This echos password to shell which is a bit sucky
81
+ if ENV["RUBY_FORGE_PASSWORD"]
82
+ password = ENV["RUBY_FORGE_PASSWORD"]
83
+ else
84
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
85
+ password = STDIN.gets.chomp
86
+ end
87
+
88
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
89
+ data = [
90
+ "login=1",
91
+ "form_loginname=#{RUBY_FORGE_USER}",
92
+ "form_pw=#{password}"
93
+ ].join("&")
94
+ http.post("/account/login.php", data)
95
+ end
96
+
97
+ cookie = login_response["set-cookie"]
98
+ raise "Login failed" unless cookie
99
+ headers = { "Cookie" => cookie }
100
+
101
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
102
+ release_data = open(release_uri, headers) { |data| data.read }
103
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
104
+ raise "Couldn't get package id" unless package_id
105
+
106
+ first_file = true
107
+ release_id = ""
108
+
109
+ files.each do |filename|
110
+ basename = File.basename(filename)
111
+ file_ext = File.extname(filename)
112
+ file_data = File.open(filename, "rb") { |file| file.read }
113
+
114
+ puts "Releasing #{basename}..."
115
+
116
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
117
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
118
+ type_map = {
119
+ ".zip" => "3000",
120
+ ".tgz" => "3110",
121
+ ".gz" => "3110",
122
+ ".gem" => "1400"
123
+ }; type_map.default = "9999"
124
+ type = type_map[file_ext]
125
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
126
+
127
+ query_hash = if first_file then
128
+ {
129
+ "group_id" => group_id,
130
+ "package_id" => package_id,
131
+ "release_name" => PKG_FILE_NAME,
132
+ "release_date" => release_date,
133
+ "type_id" => type,
134
+ "processor_id" => "8000", # Any
135
+ "release_notes" => "",
136
+ "release_changes" => "",
137
+ "preformatted" => "1",
138
+ "submit" => "1"
139
+ }
140
+ else
141
+ {
142
+ "group_id" => group_id,
143
+ "release_id" => release_id,
144
+ "package_id" => package_id,
145
+ "step2" => "1",
146
+ "type_id" => type,
147
+ "processor_id" => "8000", # Any
148
+ "submit" => "Add This File"
149
+ }
150
+ end
151
+
152
+ query = "?" + query_hash.map do |(name, value)|
153
+ [name, URI.encode(value)].join("=")
154
+ end.join("&")
155
+
156
+ data = [
157
+ "--" + boundary,
158
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
159
+ "Content-Type: application/octet-stream",
160
+ "Content-Transfer-Encoding: binary",
161
+ "", file_data, ""
162
+ ].join("\x0D\x0A")
163
+
164
+ release_headers = headers.merge(
165
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
166
+ )
167
+
168
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
169
+ http.post(target + query, data, release_headers)
170
+ end
171
+
172
+ if first_file then
173
+ release_id = release_response.body[/release_id=(\d+)/, 1]
174
+ raise("Couldn't get release id") unless release_id
175
+ end
176
+
177
+ first_file = false
178
+ end
179
+ end
180
+ 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,157 @@
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(:all, :with_deleted => true)
20
+ # # SELECT * FROM widgets
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.delete_all
35
+ # # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36'
36
+ #
37
+ # Widget.delete_all!
38
+ # # DELETE FROM widgets
39
+ #
40
+ # @widget.destroy
41
+ # # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36' WHERE id = 1
42
+ #
43
+ # @widget.destroy!
44
+ # # DELETE FROM widgets WHERE id = 1
45
+ #
46
+ module Paranoid
47
+ def self.included(base) # :nodoc:
48
+ base.extend ClassMethods
49
+ end
50
+
51
+ module ClassMethods
52
+ def acts_as_paranoid(options = {})
53
+ unless paranoid? # don't let AR call this twice
54
+ cattr_accessor :deleted_attribute
55
+ self.deleted_attribute = options[:with] || :deleted_at
56
+ alias_method :destroy_without_callbacks!, :destroy_without_callbacks
57
+ class << self
58
+ alias_method :find_every_with_deleted, :find_every
59
+ alias_method :calculate_with_deleted, :calculate
60
+ alias_method :delete_all!, :delete_all
61
+ end
62
+ end
63
+ include InstanceMethods
64
+ end
65
+
66
+ def paranoid?
67
+ self.included_modules.include?(InstanceMethods)
68
+ end
69
+ end
70
+
71
+ module InstanceMethods #:nodoc:
72
+ def self.included(base) # :nodoc:
73
+ base.extend ClassMethods
74
+ end
75
+
76
+ module ClassMethods
77
+ def find_with_deleted(*args)
78
+ options = args.extract_options!
79
+ validate_find_options(options)
80
+ set_readonly_option!(options)
81
+ options[:with_deleted] = true # yuck!
82
+
83
+ case args.first
84
+ when :first then find_initial(options)
85
+ when :all then find_every(options)
86
+ else find_from_ids(args, options)
87
+ end
88
+ end
89
+
90
+ def exists?(*args)
91
+ with_deleted_scope { exists_with_deleted?(*args) }
92
+ end
93
+
94
+ def count_with_deleted(*args)
95
+ calculate_with_deleted(:count, *construct_count_options_from_args(*args))
96
+ end
97
+
98
+ def count(*args)
99
+ with_deleted_scope { count_with_deleted(*args) }
100
+ end
101
+
102
+ def calculate(*args)
103
+ with_deleted_scope { calculate_with_deleted(*args) }
104
+ end
105
+
106
+ def delete_all(conditions = nil)
107
+ self.update_all ["#{self.deleted_attribute} = ?", current_time], conditions
108
+ end
109
+
110
+ protected
111
+ def current_time
112
+ default_timezone == :utc ? Time.now.utc : Time.now
113
+ end
114
+
115
+ def with_deleted_scope(&block)
116
+ with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > ?", current_time] } }, :merge, &block)
117
+ end
118
+
119
+ private
120
+ # all find calls lead here
121
+ def find_every(options)
122
+ options.delete(:with_deleted) ?
123
+ find_every_with_deleted(options) :
124
+ with_deleted_scope { find_every_with_deleted(options) }
125
+ end
126
+ end
127
+
128
+ def destroy_without_callbacks
129
+ unless new_record?
130
+ self.class.update_all self.class.send(:sanitize_sql, ["#{self.class.deleted_attribute} = ?", self.class.send(:current_time)]), ["#{self.class.primary_key} = ?", id]
131
+ end
132
+ freeze
133
+ end
134
+
135
+ def destroy_with_callbacks!
136
+ return false if callback(:before_destroy) == false
137
+ result = destroy_without_callbacks!
138
+ callback(:after_destroy)
139
+ result
140
+ end
141
+
142
+ def destroy!
143
+ transaction { destroy_with_callbacks! }
144
+ end
145
+
146
+ def deleted?
147
+ !!read_attribute(:deleted_at)
148
+ end
149
+
150
+ def recover!
151
+ self.deleted_at = nil
152
+ save!
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,30 @@
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
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'pager-acts_as_paranoid'
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,246 @@
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_exists_with_deleted
46
+ assert Widget.exists_with_deleted?(2)
47
+ assert !Widget.exists?(2)
48
+ end
49
+
50
+ def test_should_count_with_deleted
51
+ assert_equal 1, Widget.count
52
+ assert_equal 2, Widget.count_with_deleted
53
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
54
+ end
55
+
56
+ def test_should_set_deleted_at
57
+ assert_equal 1, Widget.count
58
+ assert_equal 1, Category.count
59
+ widgets(:widget_1).destroy
60
+ assert_equal 0, Widget.count
61
+ assert_equal 0, Category.count
62
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
63
+ assert_equal 4, Category.calculate_with_deleted(:count, :all)
64
+ end
65
+
66
+ def test_should_destroy
67
+ assert_equal 1, Widget.count
68
+ assert_equal 1, Category.count
69
+ widgets(:widget_1).destroy!
70
+ assert_equal 0, Widget.count
71
+ assert_equal 0, Category.count
72
+ assert_equal 1, Widget.calculate_with_deleted(:count, :all)
73
+ # Category doesn't get destroyed because the dependent before_destroy callback uses #destroy
74
+ assert_equal 4, Category.calculate_with_deleted(:count, :all)
75
+ end
76
+
77
+ def test_should_delete_all
78
+ assert_equal 1, Widget.count
79
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
80
+ assert_equal 1, Category.count
81
+ Widget.delete_all
82
+ assert_equal 0, Widget.count
83
+ # delete_all doesn't call #destroy, so the dependent callback never fires
84
+ assert_equal 1, Category.count
85
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
86
+ end
87
+
88
+ def test_should_delete_all_with_conditions
89
+ assert_equal 1, Widget.count
90
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
91
+ Widget.delete_all("id < 3")
92
+ assert_equal 0, Widget.count
93
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
94
+ end
95
+
96
+ def test_should_delete_all2
97
+ assert_equal 1, Category.count
98
+ assert_equal 4, Category.calculate_with_deleted(:count, :all)
99
+ Category.delete_all!
100
+ assert_equal 0, Category.count
101
+ assert_equal 0, Category.calculate_with_deleted(:count, :all)
102
+ end
103
+
104
+ def test_should_delete_all_with_conditions2
105
+ assert_equal 1, Category.count
106
+ assert_equal 4, Category.calculate_with_deleted(:count, :all)
107
+ Category.delete_all!("id < 3")
108
+ assert_equal 0, Category.count
109
+ assert_equal 2, Category.calculate_with_deleted(:count, :all)
110
+ end
111
+
112
+ def test_should_not_count_deleted
113
+ assert_equal 1, Widget.count
114
+ assert_equal 1, Widget.count(:all, :conditions => ['title=?', 'widget 1'])
115
+ assert_equal 2, Widget.calculate_with_deleted(:count, :all)
116
+ end
117
+
118
+ def test_should_not_find_deleted
119
+ assert_equal [widgets(:widget_1)], Widget.find(:all)
120
+ assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id }
121
+ end
122
+
123
+ def test_should_not_find_deleted_has_many_associations
124
+ assert_equal 1, widgets(:widget_1).categories.size
125
+ assert_equal [categories(:category_1)], widgets(:widget_1).categories
126
+ end
127
+
128
+ def test_should_not_find_deleted_habtm_associations
129
+ assert_equal 1, widgets(:widget_1).habtm_categories.size
130
+ assert_equal [categories(:category_1)], widgets(:widget_1).habtm_categories
131
+ end
132
+
133
+ def test_should_not_find_deleted_has_many_through_associations
134
+ assert_equal 1, widgets(:widget_1).tags.size
135
+ assert_equal [tags(:tag_2)], widgets(:widget_1).tags
136
+ end
137
+
138
+ def test_should_find_has_many_through_associations_with_deleted
139
+ assert_equal 2, widgets(:widget_1).any_tags.size
140
+ assert_equal Tag.find(:all), widgets(:widget_1).any_tags
141
+ end
142
+
143
+ def test_should_not_find_deleted_belongs_to_associations
144
+ assert_nil Category.find_with_deleted(3).widget
145
+ end
146
+
147
+ def test_should_find_belongs_to_assocation_with_deleted
148
+ assert_equal Widget.find_with_deleted(2), Category.find_with_deleted(3).any_widget
149
+ end
150
+
151
+ def test_should_find_first_with_deleted
152
+ assert_equal widgets(:widget_1), Widget.find(:first)
153
+ assert_equal 2, Widget.find_with_deleted(:first, :order => 'id desc').id
154
+ end
155
+
156
+ def test_should_find_single_id
157
+ assert Widget.find(1)
158
+ assert Widget.find_with_deleted(2)
159
+ assert_raises(ActiveRecord::RecordNotFound) { Widget.find(2) }
160
+ end
161
+
162
+ def test_should_find_multiple_ids
163
+ assert_equal [1,2], Widget.find_with_deleted(1,2).sort_by { |w| w.id }.collect { |w| w.id }
164
+ assert_equal [1,2], Widget.find_with_deleted([1,2]).sort_by { |w| w.id }.collect { |w| w.id }
165
+ assert_raises(ActiveRecord::RecordNotFound) { Widget.find(1,2) }
166
+ end
167
+
168
+ def test_should_ignore_multiple_includes
169
+ Widget.class_eval { acts_as_paranoid }
170
+ assert Widget.find(1)
171
+ end
172
+
173
+ def test_should_not_override_scopes_when_counting
174
+ assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.count }
175
+ assert_equal 0, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.count }
176
+ assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.calculate_with_deleted(:count, :all) }
177
+ end
178
+
179
+ def test_should_not_override_scopes_when_finding
180
+ assert_equal [1], Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.find(:all) }.ids
181
+ assert_equal [], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find(:all) }.ids
182
+ assert_equal [2], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find_with_deleted(:all) }.ids
183
+ end
184
+
185
+ def test_should_allow_multiple_scoped_calls_when_finding
186
+ Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do
187
+ assert_equal [2], Widget.find_with_deleted(:all).ids
188
+ assert_equal [2], Widget.find_with_deleted(:all).ids, "clobbers the constrain on the unmodified find"
189
+ assert_equal [], Widget.find(:all).ids
190
+ assert_equal [], Widget.find(:all).ids, 'clobbers the constrain on a paranoid find'
191
+ end
192
+ end
193
+
194
+ def test_should_allow_multiple_scoped_calls_when_counting
195
+ Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do
196
+ assert_equal 1, Widget.calculate_with_deleted(:count, :all)
197
+ assert_equal 1, Widget.calculate_with_deleted(:count, :all), "clobbers the constrain on the unmodified find"
198
+ assert_equal 0, Widget.count
199
+ assert_equal 0, Widget.count, 'clobbers the constrain on a paranoid find'
200
+ end
201
+ end
202
+
203
+ def test_should_give_paranoid_status
204
+ assert Widget.paranoid?
205
+ assert !NonParanoidAndroid.paranoid?
206
+ end
207
+
208
+ def test_should_give_record_status
209
+ assert_equal false, Widget.find(1).deleted?
210
+ Widget.find(1).destroy
211
+ assert Widget.find_with_deleted(1).deleted?
212
+ end
213
+
214
+ def test_should_find_deleted_has_many_assocations_on_deleted_records_by_default
215
+ w = Widget.find_with_deleted 2
216
+ assert_equal 2, w.categories.find_with_deleted(:all).length
217
+ assert_equal 2, w.categories.find_with_deleted(:all).size
218
+ end
219
+
220
+ def test_should_find_deleted_habtm_assocations_on_deleted_records_by_default
221
+ w = Widget.find_with_deleted 2
222
+ assert_equal 2, w.habtm_categories.find_with_deleted(:all).length
223
+ assert_equal 2, w.habtm_categories.find_with_deleted(:all).size
224
+ end
225
+
226
+ def test_dynamic_finders
227
+ assert Widget.find_by_id(1)
228
+ assert_nil Widget.find_by_id(2)
229
+ end
230
+
231
+ def test_custom_finder_methods
232
+ w = Widget.find_with_deleted(:all).inject({}) { |all, w| all.merge(w.id => w) }
233
+ assert_equal [1], Category.search('c').ids
234
+ assert_equal [1,2,3,4], Category.search_with_deleted('c', :order => 'id').ids
235
+ assert_equal [1], widgets(:widget_1).categories.search('c').collect(&:id)
236
+ assert_equal [1,2], widgets(:widget_1).categories.search_with_deleted('c').ids
237
+ assert_equal [], w[2].categories.search('c').ids
238
+ assert_equal [3,4], w[2].categories.search_with_deleted('c').ids
239
+ end
240
+ end
241
+
242
+ class Array
243
+ def ids
244
+ collect &:id
245
+ end
246
+ 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,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pager-acts_as_paranoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.20080505
5
+ platform: ruby
6
+ authors:
7
+ - technoweenie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-05 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: avanie@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - CHANGELOG
25
+ - MIT-LICENSE
26
+ - RUNNING_UNIT_TESTS
27
+ files:
28
+ - CHANGELOG
29
+ - Rakefile
30
+ - lib/caboose/acts/belongs_to_with_deleted_association.rb
31
+ - lib/caboose/acts/has_many_through_without_deleted_association.rb
32
+ - lib/caboose/acts/paranoid.rb
33
+ - lib/pager-acts_as_paranoid.rb
34
+ - MIT-LICENSE
35
+ - rails/init.rb
36
+ - README
37
+ - RUNNING_UNIT_TESTS
38
+ - test/database.yml
39
+ - test/fixtures/categories.yml
40
+ - test/fixtures/categories_widgets.yml
41
+ - test/fixtures/taggings.yml
42
+ - test/fixtures/tags.yml
43
+ - test/fixtures/widgets.yml
44
+ - test/paranoid_test.rb
45
+ - test/schema.rb
46
+ - test/test_helper.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/pager/acts_as_paranoid
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.0.1
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Gemified technoweenie acts_as_paranoid plugin
74
+ test_files:
75
+ - test/database.yml
76
+ - test/fixtures/categories.yml
77
+ - test/fixtures/categories_widgets.yml
78
+ - test/fixtures/taggings.yml
79
+ - test/fixtures/tags.yml
80
+ - test/fixtures/widgets.yml
81
+ - test/paranoid_test.rb
82
+ - test/schema.rb
83
+ - test/test_helper.rb