paranoia 2.1.3 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +34 -0
- data/README.md +63 -24
- data/lib/paranoia.rb +81 -50
- data/lib/paranoia/version.rb +1 -1
- data/test/paranoia_test.rb +144 -12
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45933ae6db17d7ae34422587c41002a61915c588
|
4
|
+
data.tar.gz: 993a0cf0d2414179bcc916b3a3f00debf582332a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef70ba0f7801c7f6cc624159120fd149a25d15e3ba8a06b050a1549bedb75bfc4bcf16c70c2882104ee7623294890c4760c2baae0f99ef2eb18dca8093c3a3f1
|
7
|
+
data.tar.gz: 452e9212c38168259988b8df9e26656cb3799cae8ab093341a9679b5d6b5430eae3430da82d67e4a46aa92cdefd92354dc61d126394e96f934b3bd6fb6119a92
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Paranoia is an open source project and we encourage contributions.
|
2
|
+
|
3
|
+
## Filing an issue
|
4
|
+
|
5
|
+
When filing an issue on the Paranoia project, please provide these details:
|
6
|
+
|
7
|
+
* A comprehensive list of steps to reproduce the issue.
|
8
|
+
* What you're *expecting* to happen compared with what's *actually* happening.
|
9
|
+
* Your application's complete `Gemfile.lock`, and `Gemfile.lock` as text in a [Gist](https://gist.github.com) (*not as an image*)
|
10
|
+
* Any relevant stack traces ("Full trace" preferred)
|
11
|
+
|
12
|
+
In 99% of cases, this information is enough to determine the cause and solution
|
13
|
+
to the problem that is being described.
|
14
|
+
|
15
|
+
Please remember to format code using triple backticks (\`) so that it is neatly
|
16
|
+
formatted when the issue is posted.
|
17
|
+
|
18
|
+
## Pull requests
|
19
|
+
|
20
|
+
We gladly accept pull requests to add documentation, fix bugs and, in some circumstances,
|
21
|
+
add new features to Paranoia.
|
22
|
+
|
23
|
+
Here's a quick guide:
|
24
|
+
|
25
|
+
1. Fork the repo.
|
26
|
+
|
27
|
+
2. Run the tests. We only take pull requests with passing tests, and it's great
|
28
|
+
to know that you have a clean slate.
|
29
|
+
|
30
|
+
3. Create new branch then make changes and add tests for your changes. Only
|
31
|
+
refactoring and documentation changes require no new tests. If you are adding
|
32
|
+
functionality or fixing a bug, we need tests!
|
33
|
+
|
34
|
+
4. Push to your fork and submit a pull request.
|
data/README.md
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/technoweenie/acts_as_paranoid) for Rails 3 and Rails 4, using much, much, much less code.
|
4
4
|
|
5
|
-
|
5
|
+
When your app is using Paranoia, calling `destroy` on an ActiveRecord object doesn't actually destroy the database record, but just *hides* it. Paranoia does this by setting a `deleted_at` field to the current time when you `destroy` a record, and hides it by scoping all queries on your model to only include records which do not have a `deleted_at` field.
|
6
6
|
|
7
7
|
If you wish to actually destroy an object you may call `really_destroy!`. **WARNING**: This will also *really destroy* all `dependent: :destroy` records, so please aim this method away from face when using.
|
8
8
|
|
9
9
|
If a record has `has_many` associations defined AND those associations have `dependent: :destroy` set on them, then they will also be soft-deleted if `acts_as_paranoid` is set, otherwise the normal destroy will be called.
|
10
10
|
|
11
11
|
## Getting Started Video
|
12
|
-
Setup and basic usage of the paranoia gem
|
12
|
+
Setup and basic usage of the paranoia gem
|
13
13
|
[GoRails #41](https://gorails.com/episodes/soft-delete-with-paranoia)
|
14
14
|
|
15
15
|
## Installation & Usage
|
@@ -94,22 +94,6 @@ If you really want it gone *gone*, call `really_destroy!`:
|
|
94
94
|
# => client
|
95
95
|
```
|
96
96
|
|
97
|
-
If you want a method to be called on destroy, simply provide a `before_destroy` callback:
|
98
|
-
|
99
|
-
``` ruby
|
100
|
-
class Client < ActiveRecord::Base
|
101
|
-
acts_as_paranoid
|
102
|
-
|
103
|
-
before_destroy :some_method
|
104
|
-
|
105
|
-
def some_method
|
106
|
-
# do stuff
|
107
|
-
end
|
108
|
-
|
109
|
-
# ...
|
110
|
-
end
|
111
|
-
```
|
112
|
-
|
113
97
|
If you want to use a column other than `deleted_at`, you can pass it as an option:
|
114
98
|
|
115
99
|
``` ruby
|
@@ -180,12 +164,6 @@ Client.restore(id, :recursive => true)
|
|
180
164
|
client.restore(:recursive => true)
|
181
165
|
```
|
182
166
|
|
183
|
-
If you want callbacks to trigger before a restore:
|
184
|
-
|
185
|
-
``` ruby
|
186
|
-
before_restore :callback_name_goes_here
|
187
|
-
```
|
188
|
-
|
189
167
|
For more information, please look at the tests.
|
190
168
|
|
191
169
|
#### About indexes:
|
@@ -207,6 +185,49 @@ add_index :clients, [:group_id, :other_id], where: "deleted_at IS NULL"
|
|
207
185
|
|
208
186
|
Of course, this is not necessary for the indexes you always use in association with `with_deleted` or `only_deleted`.
|
209
187
|
|
188
|
+
##### Unique Indexes
|
189
|
+
|
190
|
+
Becuse NULL != NULL in standard SQL, we can not simply create a unique index
|
191
|
+
on the deleted_at column and expect it to enforce that there only be one record
|
192
|
+
with a certain combination of values.
|
193
|
+
|
194
|
+
If your database supports them, good alternatives include partial indexes
|
195
|
+
(above) and indexes on computed columns. E.g.
|
196
|
+
|
197
|
+
``` ruby
|
198
|
+
add_index :clients, [:group_id, 'COALESCE(deleted_at, false)'], unique: true
|
199
|
+
```
|
200
|
+
|
201
|
+
If not, an alternative is to create a separate column which is maintained
|
202
|
+
alongside deleted_at for the sake of enforcing uniqueness. To that end,
|
203
|
+
paranoia makes use of two method to make its destroy and restore actions:
|
204
|
+
paranoia_restore_attributes and paranoia_destroy_attributes.
|
205
|
+
|
206
|
+
``` ruby
|
207
|
+
add_column :clients, :active, :boolean
|
208
|
+
add_index :clients, [:group_id, :active], unique: true
|
209
|
+
|
210
|
+
class Client < ActiveRecord::Base
|
211
|
+
# optionally have paranoia make use of your unique column, so that
|
212
|
+
# your lookups will benefit from the unique index
|
213
|
+
acts_as_paranoid column: :active, sentinel_value: true
|
214
|
+
|
215
|
+
def paranoia_restore_attributes
|
216
|
+
{
|
217
|
+
deleted_at: nil,
|
218
|
+
active: true
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
def paranoia_destroy_attributes
|
223
|
+
{
|
224
|
+
deleted_at: current_time_from_proper_timezone,
|
225
|
+
active: nil
|
226
|
+
}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
210
231
|
## Acts As Paranoid Migration
|
211
232
|
|
212
233
|
You can replace the older `acts_as_paranoid` methods as follows:
|
@@ -221,6 +242,24 @@ You can replace the older `acts_as_paranoid` methods as follows:
|
|
221
242
|
The `recover` method in `acts_as_paranoid` runs `update` callbacks. Paranoia's
|
222
243
|
`restore` method does not do this.
|
223
244
|
|
245
|
+
## Callbacks
|
246
|
+
|
247
|
+
Paranoia provides few callbacks. It triggers `destroy` callback when the record is marked as deleted and `real_destroy` when the record is completely removed from database. It also calls `restore` callback when record is restored via paranoia
|
248
|
+
|
249
|
+
For example if you want to index you records in some search engine you can do like this:
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
class Product < ActiveRecord::Base
|
253
|
+
acts_as_paranoid
|
254
|
+
|
255
|
+
after_destroy :update_document_in_search_engine
|
256
|
+
after_restore :update_document_in_search_engine
|
257
|
+
after_real_destroy :remove_document_from_search_engine
|
258
|
+
end
|
259
|
+
```
|
260
|
+
|
261
|
+
You can use these events just like regular Rails callbacks with before, after and around hooks.
|
262
|
+
|
224
263
|
## License
|
225
264
|
|
226
265
|
This gem is released under the MIT license.
|
data/lib/paranoia.rb
CHANGED
@@ -22,14 +22,21 @@ module Paranoia
|
|
22
22
|
|
23
23
|
def with_deleted
|
24
24
|
if ActiveRecord::VERSION::STRING >= "4.1"
|
25
|
-
unscope where: paranoia_column
|
26
|
-
else
|
27
|
-
all.tap { |x| x.default_scoped = false }
|
25
|
+
return unscope where: paranoia_column
|
28
26
|
end
|
27
|
+
all.tap { |x| x.default_scoped = false }
|
29
28
|
end
|
30
29
|
|
31
30
|
def only_deleted
|
32
|
-
|
31
|
+
if paranoia_sentinel_value.nil?
|
32
|
+
return with_deleted.where.not(paranoia_column => paranoia_sentinel_value)
|
33
|
+
end
|
34
|
+
# if paranoia_sentinel_value is not null, then it is possible that
|
35
|
+
# some deleted rows will hold a null value in the paranoia column
|
36
|
+
# these will not match != sentinel value because "NULL != value" is
|
37
|
+
# NULL under the sql standard
|
38
|
+
quoted_paranoia_column = connection.quote_column_name(paranoia_column)
|
39
|
+
with_deleted.where("#{quoted_paranoia_column} IS NULL OR #{quoted_paranoia_column} != ?", paranoia_sentinel_value)
|
33
40
|
end
|
34
41
|
alias :deleted :only_deleted
|
35
42
|
|
@@ -47,18 +54,20 @@ module Paranoia
|
|
47
54
|
|
48
55
|
module Callbacks
|
49
56
|
def self.extended(klazz)
|
50
|
-
|
57
|
+
[:restore, :real_destroy].each do |callback_name|
|
58
|
+
klazz.define_callbacks callback_name
|
51
59
|
|
52
|
-
|
53
|
-
|
54
|
-
|
60
|
+
klazz.define_singleton_method("before_#{callback_name}") do |*args, &block|
|
61
|
+
set_callback(callback_name, :before, *args, &block)
|
62
|
+
end
|
55
63
|
|
56
|
-
|
57
|
-
|
58
|
-
|
64
|
+
klazz.define_singleton_method("around_#{callback_name}") do |*args, &block|
|
65
|
+
set_callback(callback_name, :around, *args, &block)
|
66
|
+
end
|
59
67
|
|
60
|
-
|
61
|
-
|
68
|
+
klazz.define_singleton_method("after_#{callback_name}") do |*args, &block|
|
69
|
+
set_callback(callback_name, :after, *args, &block)
|
70
|
+
end
|
62
71
|
end
|
63
72
|
end
|
64
73
|
end
|
@@ -66,16 +75,13 @@ module Paranoia
|
|
66
75
|
def destroy
|
67
76
|
transaction do
|
68
77
|
run_callbacks(:destroy) do
|
69
|
-
result =
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
78
|
+
result = delete
|
79
|
+
next result unless result && ActiveRecord::VERSION::STRING >= '4.2'
|
80
|
+
each_counter_cached_associations do |association|
|
81
|
+
foreign_key = association.reflection.foreign_key.to_sym
|
82
|
+
next if destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
|
83
|
+
next unless send(association.reflection.name)
|
84
|
+
association.decrement_counters
|
79
85
|
end
|
80
86
|
result
|
81
87
|
end
|
@@ -83,7 +89,16 @@ module Paranoia
|
|
83
89
|
end
|
84
90
|
|
85
91
|
def delete
|
86
|
-
|
92
|
+
raise ActiveRecord::ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
|
93
|
+
if persisted?
|
94
|
+
# if a transaction exists, add the record so that after_commit
|
95
|
+
# callbacks can be run
|
96
|
+
add_to_transaction
|
97
|
+
update_columns(paranoia_destroy_attributes)
|
98
|
+
elsif !frozen?
|
99
|
+
assign_attributes(paranoia_destroy_attributes)
|
100
|
+
end
|
101
|
+
self
|
87
102
|
end
|
88
103
|
|
89
104
|
def restore!(opts = {})
|
@@ -94,7 +109,7 @@ module Paranoia
|
|
94
109
|
noop_if_frozen = ActiveRecord.version < Gem::Version.new("4.1")
|
95
110
|
if (noop_if_frozen && !@attributes.frozen?) || !noop_if_frozen
|
96
111
|
write_attribute paranoia_column, paranoia_sentinel_value
|
97
|
-
|
112
|
+
update_columns(paranoia_restore_attributes)
|
98
113
|
end
|
99
114
|
restore_associated_records if opts[:recursive]
|
100
115
|
end
|
@@ -111,16 +126,16 @@ module Paranoia
|
|
111
126
|
|
112
127
|
private
|
113
128
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
129
|
+
def paranoia_restore_attributes
|
130
|
+
{
|
131
|
+
paranoia_column => paranoia_sentinel_value
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def paranoia_destroy_attributes
|
136
|
+
{
|
137
|
+
paranoia_column => current_time_from_proper_timezone
|
138
|
+
}
|
124
139
|
end
|
125
140
|
|
126
141
|
# restore associated records that have been soft deleted when
|
@@ -154,7 +169,7 @@ module Paranoia
|
|
154
169
|
association_find_conditions = { association_foreign_key => self.id }
|
155
170
|
end
|
156
171
|
|
157
|
-
association_class =
|
172
|
+
association_class = association_class_name.constantize
|
158
173
|
if association_class.paranoid?
|
159
174
|
association_class.only_deleted.where(association_find_conditions).first.try!(:restore, recursive: true)
|
160
175
|
end
|
@@ -172,25 +187,27 @@ class ActiveRecord::Base
|
|
172
187
|
|
173
188
|
alias :destroy_without_paranoia :destroy
|
174
189
|
def really_destroy!
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
association_data.
|
186
|
-
|
190
|
+
transaction do
|
191
|
+
run_callbacks(:real_destroy) do
|
192
|
+
dependent_reflections = self.class.reflections.select do |name, reflection|
|
193
|
+
reflection.options[:dependent] == :destroy
|
194
|
+
end
|
195
|
+
if dependent_reflections.any?
|
196
|
+
dependent_reflections.each do |name, reflection|
|
197
|
+
association_data = self.send(name)
|
198
|
+
# has_one association can return nil
|
199
|
+
# .paranoid? will work for both instances and classes
|
200
|
+
next unless association_data && association_data.paranoid?
|
201
|
+
if reflection.collection?
|
202
|
+
next association_data.with_deleted.each(&:really_destroy!)
|
203
|
+
end
|
187
204
|
association_data.really_destroy!
|
188
205
|
end
|
189
206
|
end
|
207
|
+
write_attribute(paranoia_column, current_time_from_proper_timezone)
|
208
|
+
destroy_without_paranoia
|
190
209
|
end
|
191
210
|
end
|
192
|
-
write_attribute(paranoia_column, current_time_from_proper_timezone)
|
193
|
-
destroy_without_paranoia
|
194
211
|
end
|
195
212
|
|
196
213
|
include Paranoia
|
@@ -237,3 +254,17 @@ class ActiveRecord::Base
|
|
237
254
|
end
|
238
255
|
|
239
256
|
require 'paranoia/rspec' if defined? RSpec
|
257
|
+
|
258
|
+
module ActiveRecord
|
259
|
+
module Validations
|
260
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
261
|
+
protected
|
262
|
+
def build_relation_with_paranoia(klass, table, attribute, value)
|
263
|
+
relation = build_relation_without_paranoia(klass, table, attribute, value)
|
264
|
+
return relation unless klass.respond_to?(:paranoia_column)
|
265
|
+
relation.and(klass.arel_table[klass.paranoia_column].eq(klass.paranoia_sentinel_value))
|
266
|
+
end
|
267
|
+
alias_method_chain :build_relation, :paranoia
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
data/lib/paranoia/version.rb
CHANGED
data/test/paranoia_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
require 'bundler/setup'
|
1
2
|
require 'active_record'
|
2
|
-
ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::VERSION::STRING >= '4.2'
|
3
|
-
|
4
3
|
require 'minitest/autorun'
|
5
|
-
|
4
|
+
require 'paranoia'
|
6
5
|
|
7
|
-
|
6
|
+
test_framework = defined?(MiniTest::Test) ? MiniTest::Test : MiniTest::Unit::TestCase
|
7
|
+
ActiveRecord::Base.raise_in_transactional_callbacks = true if ActiveRecord::VERSION::STRING >= '4.2'
|
8
8
|
|
9
9
|
def connect!
|
10
10
|
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', database: ':memory:'
|
@@ -28,7 +28,7 @@ def setup!
|
|
28
28
|
'fail_callback_models' => 'deleted_at DATETIME',
|
29
29
|
'related_models' => 'parent_model_id INTEGER, parent_model_with_counter_cache_column_id INTEGER, deleted_at DATETIME',
|
30
30
|
'asplode_models' => 'parent_model_id INTEGER, deleted_at DATETIME',
|
31
|
-
'employers' => 'deleted_at DATETIME',
|
31
|
+
'employers' => 'name VARCHAR(32), deleted_at DATETIME',
|
32
32
|
'employees' => 'deleted_at DATETIME',
|
33
33
|
'jobs' => 'employer_id INTEGER NOT NULL, employee_id INTEGER NOT NULL, deleted_at DATETIME',
|
34
34
|
'custom_column_models' => 'destroyed_at DATETIME',
|
@@ -37,6 +37,9 @@ def setup!
|
|
37
37
|
'polymorphic_models' => 'parent_id INTEGER, parent_type STRING, deleted_at DATETIME',
|
38
38
|
'namespaced_paranoid_has_ones' => 'deleted_at DATETIME, paranoid_belongs_tos_id INTEGER',
|
39
39
|
'namespaced_paranoid_belongs_tos' => 'deleted_at DATETIME, paranoid_has_one_id INTEGER',
|
40
|
+
'unparanoid_unique_models' => 'name VARCHAR(32), paranoid_with_unparanoids_id INTEGER',
|
41
|
+
'active_column_models' => 'deleted_at DATETIME, active BOOLEAN',
|
42
|
+
'active_column_model_with_uniqueness_validations' => 'name VARCHAR(32), deleted_at DATETIME, active BOOLEAN'
|
40
43
|
}.each do |table_name, columns_as_sql_string|
|
41
44
|
ActiveRecord::Base.connection.execute "CREATE TABLE #{table_name} (id INTEGER NOT NULL PRIMARY KEY, #{columns_as_sql_string})"
|
42
45
|
end
|
@@ -120,6 +123,22 @@ class ParanoiaTest < test_framework
|
|
120
123
|
model.remove_called_variables # clear called callback flags
|
121
124
|
model.delete
|
122
125
|
|
126
|
+
assert_equal nil, model.instance_variable_get(:@update_callback_called)
|
127
|
+
assert_equal nil, model.instance_variable_get(:@save_callback_called)
|
128
|
+
assert_equal nil, model.instance_variable_get(:@validate_called)
|
129
|
+
assert_equal nil, model.instance_variable_get(:@destroy_callback_called)
|
130
|
+
assert_equal nil, model.instance_variable_get(:@after_destroy_callback_called)
|
131
|
+
assert_equal nil, model.instance_variable_get(:@after_commit_callback_called)
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_delete_in_transaction_behavior_for_plain_models_callbacks
|
135
|
+
model = CallbackModel.new
|
136
|
+
model.save
|
137
|
+
model.remove_called_variables # clear called callback flags
|
138
|
+
CallbackModel.transaction do
|
139
|
+
model.delete
|
140
|
+
end
|
141
|
+
|
123
142
|
assert_equal nil, model.instance_variable_get(:@update_callback_called)
|
124
143
|
assert_equal nil, model.instance_variable_get(:@save_callback_called)
|
125
144
|
assert_equal nil, model.instance_variable_get(:@validate_called)
|
@@ -184,6 +203,38 @@ class ParanoiaTest < test_framework
|
|
184
203
|
assert_equal nil, ParanoidModel.paranoia_sentinel_value
|
185
204
|
end
|
186
205
|
|
206
|
+
def test_active_column_model
|
207
|
+
model = ActiveColumnModel.new
|
208
|
+
assert_equal 0, model.class.count
|
209
|
+
model.save!
|
210
|
+
assert_nil model.deleted_at
|
211
|
+
assert_equal true, model.active
|
212
|
+
assert_equal 1, model.class.count
|
213
|
+
model.destroy
|
214
|
+
|
215
|
+
assert_equal false, model.deleted_at.nil?
|
216
|
+
assert_nil model.active
|
217
|
+
assert model.paranoia_destroyed?
|
218
|
+
|
219
|
+
assert_equal 0, model.class.count
|
220
|
+
assert_equal 1, model.class.unscoped.count
|
221
|
+
assert_equal 1, model.class.only_deleted.count
|
222
|
+
assert_equal 1, model.class.deleted.count
|
223
|
+
end
|
224
|
+
|
225
|
+
def test_active_column_model_with_uniqueness_validation_only_checks_non_deleted_records
|
226
|
+
a = ActiveColumnModelWithUniquenessValidation.create!(name: "A")
|
227
|
+
a.destroy
|
228
|
+
b = ActiveColumnModelWithUniquenessValidation.new(name: "A")
|
229
|
+
assert b.valid?
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_active_column_model_with_uniqueness_validation_still_works_on_non_deleted_records
|
233
|
+
a = ActiveColumnModelWithUniquenessValidation.create!(name: "A")
|
234
|
+
b = ActiveColumnModelWithUniquenessValidation.new(name: "A")
|
235
|
+
refute b.valid?
|
236
|
+
end
|
237
|
+
|
187
238
|
def test_sentinel_value_for_custom_sentinel_models
|
188
239
|
model = CustomSentinelModel.new
|
189
240
|
assert_equal 0, model.class.count
|
@@ -453,6 +504,14 @@ class ParanoiaTest < test_framework
|
|
453
504
|
assert RelatedModel.unscoped.exists?(child_2.id)
|
454
505
|
end
|
455
506
|
|
507
|
+
def test_really_destroy_behavior_for_callbacks
|
508
|
+
model = CallbackModel.new
|
509
|
+
model.save
|
510
|
+
model.really_destroy!
|
511
|
+
|
512
|
+
assert model.instance_variable_get(:@real_destroy_callback_called)
|
513
|
+
end
|
514
|
+
|
456
515
|
def test_really_delete
|
457
516
|
model = ParanoidModel.new
|
458
517
|
model.save
|
@@ -685,6 +744,19 @@ class ParanoiaTest < test_framework
|
|
685
744
|
# essentially, we're just ensuring that this doesn't crash
|
686
745
|
end
|
687
746
|
|
747
|
+
def test_validates_uniqueness_only_checks_non_deleted_records
|
748
|
+
a = Employer.create!(name: "A")
|
749
|
+
a.destroy
|
750
|
+
b = Employer.new(name: "A")
|
751
|
+
assert b.valid?
|
752
|
+
end
|
753
|
+
|
754
|
+
def test_validates_uniqueness_still_works_on_non_deleted_records
|
755
|
+
a = Employer.create!(name: "A")
|
756
|
+
b = Employer.new(name: "A")
|
757
|
+
refute b.valid?
|
758
|
+
end
|
759
|
+
|
688
760
|
def test_i_am_the_destroyer
|
689
761
|
expected = %Q{
|
690
762
|
Sharon: "There should be a method called I_AM_THE_DESTROYER!"
|
@@ -800,6 +872,13 @@ class ParanoiaTest < test_framework
|
|
800
872
|
# assert related_model.instance_variable_get(:@after_commit_on_destroy_callback_called)
|
801
873
|
end
|
802
874
|
|
875
|
+
def test_uniqueness_for_unparanoid_associated
|
876
|
+
parent_model = ParanoidWithUnparanoids.create
|
877
|
+
related = parent_model.unparanoid_unique_models.create
|
878
|
+
# will raise exception if model is not checked for paranoia
|
879
|
+
related.valid?
|
880
|
+
end
|
881
|
+
|
803
882
|
# TODO: find a fix for Rails 4.1
|
804
883
|
if ActiveRecord::VERSION::STRING !~ /\A4\.1/
|
805
884
|
def test_counter_cache_column_update_on_really_destroy
|
@@ -841,6 +920,16 @@ class ParanoidModel < ActiveRecord::Base
|
|
841
920
|
acts_as_paranoid
|
842
921
|
end
|
843
922
|
|
923
|
+
class ParanoidWithUnparanoids < ActiveRecord::Base
|
924
|
+
self.table_name = 'plain_models'
|
925
|
+
has_many :unparanoid_unique_models
|
926
|
+
end
|
927
|
+
|
928
|
+
class UnparanoidUniqueModel < ActiveRecord::Base
|
929
|
+
belongs_to :paranoid_with_unparanoids
|
930
|
+
validates :name, :uniqueness => true
|
931
|
+
end
|
932
|
+
|
844
933
|
class FailCallbackModel < ActiveRecord::Base
|
845
934
|
belongs_to :parent_model
|
846
935
|
acts_as_paranoid
|
@@ -853,20 +942,25 @@ class FeaturefulModel < ActiveRecord::Base
|
|
853
942
|
validates :name, :presence => true, :uniqueness => true
|
854
943
|
end
|
855
944
|
|
945
|
+
class NonParanoidChildModel < ActiveRecord::Base
|
946
|
+
validates :name, :presence => true, :uniqueness => true
|
947
|
+
end
|
948
|
+
|
856
949
|
class PlainModel < ActiveRecord::Base
|
857
950
|
end
|
858
951
|
|
859
952
|
class CallbackModel < ActiveRecord::Base
|
860
953
|
acts_as_paranoid
|
861
|
-
before_destroy
|
862
|
-
before_restore
|
863
|
-
before_update
|
864
|
-
before_save
|
954
|
+
before_destroy { |model| model.instance_variable_set :@destroy_callback_called, true }
|
955
|
+
before_restore { |model| model.instance_variable_set :@restore_callback_called, true }
|
956
|
+
before_update { |model| model.instance_variable_set :@update_callback_called, true }
|
957
|
+
before_save { |model| model.instance_variable_set :@save_callback_called, true}
|
958
|
+
before_real_destroy { |model| model.instance_variable_set :@real_destroy_callback_called, true }
|
865
959
|
|
866
|
-
after_destroy
|
867
|
-
after_commit
|
960
|
+
after_destroy { |model| model.instance_variable_set :@after_destroy_callback_called, true }
|
961
|
+
after_commit { |model| model.instance_variable_set :@after_commit_callback_called, true }
|
868
962
|
|
869
|
-
validate
|
963
|
+
validate { |model| model.instance_variable_set :@validate_called, true }
|
870
964
|
|
871
965
|
def remove_called_variables
|
872
966
|
instance_variables.each {|name| (name.to_s.end_with?('_called')) ? remove_instance_variable(name) : nil}
|
@@ -909,6 +1003,7 @@ end
|
|
909
1003
|
|
910
1004
|
class Employer < ActiveRecord::Base
|
911
1005
|
acts_as_paranoid
|
1006
|
+
validates_uniqueness_of :name
|
912
1007
|
has_many :jobs
|
913
1008
|
has_many :employees, :through => :jobs
|
914
1009
|
end
|
@@ -933,6 +1028,43 @@ class CustomSentinelModel < ActiveRecord::Base
|
|
933
1028
|
acts_as_paranoid sentinel_value: DateTime.new(0)
|
934
1029
|
end
|
935
1030
|
|
1031
|
+
class ActiveColumnModel < ActiveRecord::Base
|
1032
|
+
acts_as_paranoid column: :active, sentinel_value: true
|
1033
|
+
|
1034
|
+
def paranoia_restore_attributes
|
1035
|
+
{
|
1036
|
+
deleted_at: nil,
|
1037
|
+
active: true
|
1038
|
+
}
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def paranoia_destroy_attributes
|
1042
|
+
{
|
1043
|
+
deleted_at: current_time_from_proper_timezone,
|
1044
|
+
active: nil
|
1045
|
+
}
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
class ActiveColumnModelWithUniquenessValidation < ActiveRecord::Base
|
1050
|
+
validates :name, :uniqueness => true
|
1051
|
+
acts_as_paranoid column: :active, sentinel_value: true
|
1052
|
+
|
1053
|
+
def paranoia_restore_attributes
|
1054
|
+
{
|
1055
|
+
deleted_at: nil,
|
1056
|
+
active: true
|
1057
|
+
}
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
def paranoia_destroy_attributes
|
1061
|
+
{
|
1062
|
+
deleted_at: current_time_from_proper_timezone,
|
1063
|
+
active: nil
|
1064
|
+
}
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
|
936
1068
|
class NonParanoidModel < ActiveRecord::Base
|
937
1069
|
end
|
938
1070
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paranoia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radarlistener@gmail.com
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- ".gitignore"
|
67
67
|
- ".travis.yml"
|
68
68
|
- CHANGELOG.md
|
69
|
+
- CONTRIBUTING.md
|
69
70
|
- Gemfile
|
70
71
|
- LICENSE
|
71
72
|
- README.md
|
@@ -94,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
95
|
version: 1.3.6
|
95
96
|
requirements: []
|
96
97
|
rubyforge_project: paranoia
|
97
|
-
rubygems_version: 2.4.
|
98
|
+
rubygems_version: 2.4.5.1
|
98
99
|
signing_key:
|
99
100
|
specification_version: 4
|
100
101
|
summary: Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much,
|