paranoia 2.1.3 → 2.1.4
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.
- 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,
|