coupa-acts_as_paranoid 1.0.20090228

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/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,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'acts_as_paranoid'
3
+ s.version = '1.0.20090228'
4
+ s.date = '2009-02-28'
5
+
6
+ s.summary = "Overrides some basic methods for the current model so that calling " +
7
+ "#destroy sets a 'deleted_at' field to the current timestamp. ActiveRecord is required."
8
+ s.description = "see README"
9
+
10
+ s.authors = ['Rick Olson']
11
+ s.email = 'jerry@coupa.com'
12
+ s.homepage = 'http://github.com/coupa/acts_as_paranoid'
13
+
14
+ s.has_rdoc = true
15
+ s.rdoc_options = ["--main", "README"]
16
+ s.extra_rdoc_files = ["README"]
17
+
18
+ s.add_dependency 'rails', ['>= 2.1']
19
+
20
+ s.files = [
21
+ "README",
22
+ "CHANGELOG",
23
+ "MIT-LICENSE",
24
+ "RUNNING_UNIT_TESTS",
25
+ "Rakefile",
26
+ "acts_as_paranoid.gemspec",
27
+ "lib/coupa-acts_as_paranoid.rb",
28
+ "lib/caboose/acts/belongs_to_with_deleted_association.rb",
29
+ "lib/caboose/acts/has_many_through_without_deleted_association.rb",
30
+ "lib/caboose/acts/paranoid.rb",
31
+ "lib/caboose/acts/paranoid_find_wrapper.rb",
32
+ ]
33
+
34
+ s.test_files = []
35
+
36
+ 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
@@ -0,0 +1,39 @@
1
+ require 'caboose/acts/belongs_to_with_deleted_association'
2
+ require 'caboose/acts/has_many_through_without_deleted_association'
3
+ require 'caboose/acts/paranoid'
4
+ require 'caboose/acts/paranoid_find_wrapper'
5
+
6
+ class << ActiveRecord::Base
7
+ def belongs_to_with_deleted(association_id, options = {})
8
+ with_deleted = options.delete :with_deleted
9
+ returning belongs_to_without_deleted(association_id, options) do
10
+ if with_deleted
11
+ reflection = reflect_on_association(association_id)
12
+ association_accessor_methods(reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
13
+ association_constructor_method(:build, reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
14
+ association_constructor_method(:create, reflection, Caboose::Acts::BelongsToWithDeletedAssociation)
15
+ end
16
+ end
17
+ end
18
+
19
+ def has_many_without_deleted(association_id, options = {}, &extension)
20
+ with_deleted = options.delete :with_deleted
21
+ returning has_many_with_deleted(association_id, options, &extension) do
22
+ if options[:through] && !with_deleted
23
+ reflection = reflect_on_association(association_id)
24
+ collection_reader_method(reflection, Caboose::Acts::HasManyThroughWithoutDeletedAssociation)
25
+ collection_accessor_methods(reflection, Caboose::Acts::HasManyThroughWithoutDeletedAssociation, false)
26
+ end
27
+ end
28
+ end
29
+
30
+ alias_method_chain :belongs_to, :deleted
31
+ alias_method :has_many_with_deleted, :has_many
32
+ alias_method :has_many, :has_many_without_deleted
33
+ alias_method :exists_with_deleted?, :exists?
34
+ end
35
+ ActiveRecord::Base.send :include, Caboose::Acts::Paranoid
36
+ ActiveRecord::Base.send :include, Caboose::Acts::ParanoidFindWrapper
37
+ class << ActiveRecord::Base
38
+ alias_method_chain :acts_as_paranoid, :find_wrapper
39
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coupa-acts_as_paranoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.20090228
5
+ platform: ruby
6
+ authors:
7
+ - Rick Olson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-28 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "2.1"
24
+ version:
25
+ description: see README
26
+ email: jerry@coupa.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - README
35
+ - CHANGELOG
36
+ - MIT-LICENSE
37
+ - RUNNING_UNIT_TESTS
38
+ - Rakefile
39
+ - acts_as_paranoid.gemspec
40
+ - lib/coupa-acts_as_paranoid.rb
41
+ - lib/caboose/acts/belongs_to_with_deleted_association.rb
42
+ - lib/caboose/acts/has_many_through_without_deleted_association.rb
43
+ - lib/caboose/acts/paranoid.rb
44
+ - lib/caboose/acts/paranoid_find_wrapper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/coupa/acts_as_paranoid
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --main
50
+ - README
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.2.0
69
+ signing_key:
70
+ specification_version: 2
71
+ summary: "Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the current timestamp. ActiveRecord is required."
72
+ test_files: []
73
+