super-smart-hub 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,413 @@
1
+ [![Gem Version](https://badge.fury.io/rb/paranoia.svg)](https://badge.fury.io/rb/paranoia)
2
+ [![build](https://github.com/rubysherpas/paranoia/actions/workflows/build.yml/badge.svg)](https://github.com/rubysherpas/paranoia/actions/workflows/build.yml)
3
+
4
+ **Notice:**
5
+
6
+ `paranoia` has some surprising behaviour (like overriding ActiveRecord's `delete` and `destroy`) and is not recommended for new projects. See [`discard`'s README](https://github.com/jhawthorn/discard#why-not-paranoia-or-acts_as_paranoid) for more details.
7
+
8
+ Paranoia will continue to accept bug fixes and support new versions of Rails but isn't accepting new features.
9
+
10
+ # Paranoia
11
+
12
+ Paranoia is a re-implementation of [acts\_as\_paranoid](http://github.com/ActsAsParanoid/acts_as_paranoid) for Rails 3/4/5, using much, much, much less code.
13
+
14
+ 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.
15
+
16
+ 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.
17
+
18
+ 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. ***See [Destroying through association callbacks](#destroying-through-association-callbacks) for clarifying examples.***
19
+
20
+ ## Getting Started Video
21
+ Setup and basic usage of the paranoia gem
22
+ [GoRails #41](https://gorails.com/episodes/soft-delete-with-paranoia)
23
+
24
+ ## Installation & Usage
25
+
26
+ For Rails 3, please use version 1 of Paranoia:
27
+
28
+ ``` ruby
29
+ gem "paranoia", "~> 1.0"
30
+ ```
31
+
32
+ For Rails 4 and 5, please use version 2 of Paranoia (2.2 or greater required for rails 5):
33
+
34
+ ``` ruby
35
+ gem "paranoia", "~> 2.2"
36
+ ```
37
+
38
+ Of course you can install this from GitHub as well from one of these examples:
39
+
40
+ ``` ruby
41
+ gem "paranoia", github: "rubysherpas/paranoia", branch: "rails3"
42
+ gem "paranoia", github: "rubysherpas/paranoia", branch: "rails4"
43
+ gem "paranoia", github: "rubysherpas/paranoia", branch: "rails5"
44
+ ```
45
+
46
+ Then run:
47
+
48
+ ``` shell
49
+ bundle install
50
+ ```
51
+
52
+ Updating is as simple as `bundle update paranoia`.
53
+
54
+ #### Run your migrations for the desired models
55
+
56
+ Run:
57
+
58
+ ``` shell
59
+ bin/rails generate migration AddDeletedAtToClients deleted_at:datetime:index
60
+ ```
61
+
62
+ and now you have a migration
63
+
64
+ ``` ruby
65
+ class AddDeletedAtToClients < ActiveRecord::Migration
66
+ def change
67
+ add_column :clients, :deleted_at, :datetime
68
+ add_index :clients, :deleted_at
69
+ end
70
+ end
71
+ ```
72
+
73
+ ### Usage
74
+
75
+ #### In your model:
76
+
77
+ ``` ruby
78
+ class Client < ActiveRecord::Base
79
+ acts_as_paranoid
80
+
81
+ # ...
82
+ end
83
+ ```
84
+
85
+ Hey presto, it's there! Calling `destroy` will now set the `deleted_at` column:
86
+
87
+
88
+ ``` ruby
89
+ >> client.deleted_at
90
+ # => nil
91
+ >> client.destroy
92
+ # => client
93
+ >> client.deleted_at
94
+ # => [current timestamp]
95
+ ```
96
+
97
+ If you really want it gone *gone*, call `really_destroy!`:
98
+
99
+ ``` ruby
100
+ >> client.deleted_at
101
+ # => nil
102
+ >> client.really_destroy!
103
+ # => client
104
+ ```
105
+
106
+ If you need skip updating timestamps for deleting records, call `really_destroy!(update_destroy_attributes: false)`.
107
+ When we call `really_destroy!(update_destroy_attributes: false)` on the parent `client`, then each child `email` will also have `really_destroy!(update_destroy_attributes: false)` called.
108
+
109
+ ``` ruby
110
+ >> client.really_destroy!(update_destroy_attributes: false)
111
+ # => client
112
+ ```
113
+
114
+ If you want to use a column other than `deleted_at`, you can pass it as an option:
115
+
116
+ ``` ruby
117
+ class Client < ActiveRecord::Base
118
+ acts_as_paranoid column: :destroyed_at
119
+
120
+ ...
121
+ end
122
+ ```
123
+
124
+
125
+ If you want to skip adding the default scope:
126
+
127
+ ``` ruby
128
+ class Client < ActiveRecord::Base
129
+ acts_as_paranoid without_default_scope: true
130
+
131
+ ...
132
+ end
133
+ ```
134
+
135
+ If you want to access soft-deleted associations, override the getter method:
136
+
137
+ ``` ruby
138
+ def product
139
+ Product.unscoped { super }
140
+ end
141
+ ```
142
+
143
+ If you want to include associated soft-deleted objects, you can (un)scope the association:
144
+
145
+ ``` ruby
146
+ class Person < ActiveRecord::Base
147
+ belongs_to :group, -> { with_deleted }
148
+ end
149
+
150
+ Person.includes(:group).all
151
+ ```
152
+
153
+ If you want to find all records, even those which are deleted:
154
+
155
+ ``` ruby
156
+ Client.with_deleted
157
+ ```
158
+
159
+ If you want to exclude deleted records, when not able to use the default_scope (e.g. when using without_default_scope):
160
+
161
+ ``` ruby
162
+ Client.without_deleted
163
+ ```
164
+
165
+ If you want to find only the deleted records:
166
+
167
+ ``` ruby
168
+ Client.only_deleted
169
+ ```
170
+
171
+ If you want to check if a record is soft-deleted:
172
+
173
+ ``` ruby
174
+ client.paranoia_destroyed?
175
+ # or
176
+ client.deleted?
177
+ ```
178
+
179
+ If you want to restore a record:
180
+
181
+ ``` ruby
182
+ Client.restore(id)
183
+ # or
184
+ client.restore
185
+ ```
186
+
187
+ If you want to restore a whole bunch of records:
188
+
189
+ ``` ruby
190
+ Client.restore([id1, id2, ..., idN])
191
+ ```
192
+
193
+ If you want to restore a record and their dependently destroyed associated records:
194
+
195
+ ``` ruby
196
+ Client.restore(id, :recursive => true)
197
+ # or
198
+ client.restore(:recursive => true)
199
+ ```
200
+
201
+ If you want to restore a record and only those dependently destroyed associated records that were deleted within 2 minutes of the object upon which they depend:
202
+
203
+ ``` ruby
204
+ Client.restore(id, :recursive => true, :recovery_window => 2.minutes)
205
+ # or
206
+ client.restore(:recursive => true, :recovery_window => 2.minutes)
207
+ ```
208
+
209
+ If you want to trigger an after_commit callback when restoring a record:
210
+
211
+ ``` ruby
212
+ class Client < ActiveRecord::Base
213
+ acts_as_paranoid after_restore_commit: true
214
+
215
+ after_commit :commit_called, on: :restore
216
+ # or
217
+ after_restore_commit :commit_called
218
+ ...
219
+ end
220
+ ```
221
+
222
+ Note that by default paranoia will not prevent that a soft destroyed object can't be associated with another object of a different model.
223
+ A Rails validator is provided should you require this functionality:
224
+ ``` ruby
225
+ validates :some_assocation, association_not_soft_destroyed: true
226
+ ```
227
+ This validator makes sure that `some_assocation` is not soft destroyed. If the object is soft destroyed the main object is rendered invalid and an validation error is added.
228
+
229
+ For more information, please look at the tests.
230
+
231
+ #### About indexes:
232
+
233
+ Beware that you should adapt all your indexes for them to work as fast as previously.
234
+ For example,
235
+
236
+ ``` ruby
237
+ add_index :clients, :group_id
238
+ add_index :clients, [:group_id, :other_id]
239
+ ```
240
+
241
+ should be replaced with
242
+
243
+ ``` ruby
244
+ add_index :clients, :group_id, where: "deleted_at IS NULL"
245
+ add_index :clients, [:group_id, :other_id], where: "deleted_at IS NULL"
246
+ ```
247
+
248
+ Of course, this is not necessary for the indexes you always use in association with `with_deleted` or `only_deleted`.
249
+
250
+ ##### Unique Indexes
251
+
252
+ Because NULL != NULL in standard SQL, we can not simply create a unique index
253
+ on the deleted_at column and expect it to enforce that there only be one record
254
+ with a certain combination of values.
255
+
256
+ If your database supports them, good alternatives include partial indexes
257
+ (above) and indexes on computed columns. E.g.
258
+
259
+ ``` ruby
260
+ add_index :clients, [:group_id, 'COALESCE(deleted_at, false)'], unique: true
261
+ ```
262
+
263
+ If not, an alternative is to create a separate column which is maintained
264
+ alongside deleted_at for the sake of enforcing uniqueness. To that end,
265
+ paranoia makes use of two method to make its destroy and restore actions:
266
+ paranoia_restore_attributes and paranoia_destroy_attributes.
267
+
268
+ ``` ruby
269
+ add_column :clients, :active, :boolean
270
+ add_index :clients, [:group_id, :active], unique: true
271
+
272
+ class Client < ActiveRecord::Base
273
+ # optionally have paranoia make use of your unique column, so that
274
+ # your lookups will benefit from the unique index
275
+ acts_as_paranoid column: :active, sentinel_value: true
276
+
277
+ def paranoia_restore_attributes
278
+ {
279
+ deleted_at: nil,
280
+ active: true
281
+ }
282
+ end
283
+
284
+ def paranoia_destroy_attributes
285
+ {
286
+ deleted_at: current_time_from_proper_timezone,
287
+ active: nil
288
+ }
289
+ end
290
+ end
291
+ ```
292
+
293
+ ##### Destroying through association callbacks
294
+
295
+ When dealing with `dependent: :destroy` associations and `acts_as_paranoid`, it's important to remember that whatever method is called on the parent model will be called on the child model. For example, given both models of an association have `acts_as_paranoid` defined:
296
+
297
+ ``` ruby
298
+ class Client < ActiveRecord::Base
299
+ acts_as_paranoid
300
+
301
+ has_many :emails, dependent: :destroy
302
+ end
303
+
304
+ class Email < ActiveRecord::Base
305
+ acts_as_paranoid
306
+
307
+ belongs_to :client
308
+ end
309
+ ```
310
+
311
+ When we call `destroy` on the parent `client`, it will call `destroy` on all of its associated children `emails`:
312
+
313
+ ``` ruby
314
+ >> client.emails.count
315
+ # => 5
316
+ >> client.destroy
317
+ # => client
318
+ >> client.deleted_at
319
+ # => [current timestamp]
320
+ >> Email.where(client_id: client.id).count
321
+ # => 0
322
+ >> Email.with_deleted.where(client_id: client.id).count
323
+ # => 5
324
+ ```
325
+
326
+ Similarly, when we call `really_destroy!` on the parent `client`, then each child `email` will also have `really_destroy!` called:
327
+
328
+ ``` ruby
329
+ >> client.emails.count
330
+ # => 5
331
+ >> client.id
332
+ # => 12345
333
+ >> client.really_destroy!
334
+ # => client
335
+ >> Client.find 12345
336
+ # => ActiveRecord::RecordNotFound
337
+ >> Email.with_deleted.where(client_id: client.id).count
338
+ # => 0
339
+ ```
340
+
341
+ However, if the child model `Email` does not have `acts_as_paranoid` set, then calling `destroy` on the parent `client` will also call `destroy` on each child `email`, thereby actually destroying them:
342
+
343
+ ``` ruby
344
+ class Client < ActiveRecord::Base
345
+ acts_as_paranoid
346
+
347
+ has_many :emails, dependent: :destroy
348
+ end
349
+
350
+ class Email < ActiveRecord::Base
351
+ belongs_to :client
352
+ end
353
+
354
+ >> client.emails.count
355
+ # => 5
356
+ >> client.destroy
357
+ # => client
358
+ >> Email.where(client_id: client.id).count
359
+ # => 0
360
+ >> Email.with_deleted.where(client_id: client.id).count
361
+ # => NoMethodError: undefined method `with_deleted' for #<Class:0x0123456>
362
+ ```
363
+
364
+ #### delete_all:
365
+
366
+ The gem supports `delete_all` method, however it is disabled by default, to enable it add this in your `environment` file
367
+
368
+ ``` ruby
369
+ Paranoia.delete_all_enabled = true
370
+ ```
371
+ alternatively, you can enable/disable it for specific models as follow:
372
+
373
+ ``` ruby
374
+ class User < ActiveRecord::Base
375
+ acts_as_paranoid(delete_all_enabled: true)
376
+ end
377
+ ```
378
+
379
+ ## Acts As Paranoid Migration
380
+
381
+ You can replace the older `acts_as_paranoid` methods as follows:
382
+
383
+ | Old Syntax | New Syntax |
384
+ |:-------------------------- |:------------------------------ |
385
+ |`find_with_deleted(:all)` | `Client.with_deleted` |
386
+ |`find_with_deleted(:first)` | `Client.with_deleted.first` |
387
+ |`find_with_deleted(id)` | `Client.with_deleted.find(id)` |
388
+
389
+
390
+ The `recover` method in `acts_as_paranoid` runs `update` callbacks. Paranoia's
391
+ `restore` method does not do this.
392
+
393
+ ## Callbacks
394
+
395
+ Paranoia provides several 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 the record is restored via paranoia
396
+
397
+ For example if you want to index your records in some search engine you can go like this:
398
+
399
+ ```ruby
400
+ class Product < ActiveRecord::Base
401
+ acts_as_paranoid
402
+
403
+ after_destroy :update_document_in_search_engine
404
+ after_restore :update_document_in_search_engine
405
+ after_real_destroy :remove_document_from_search_engine
406
+ end
407
+ ```
408
+
409
+ You can use these events just like regular Rails callbacks with before, after and around hooks.
410
+
411
+ ## License
412
+
413
+ This gem is released under the MIT license.
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :test do
5
+ Dir['test/*_test.rb'].each do |testfile|
6
+ load testfile
7
+ end
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,41 @@
1
+ module HandleParanoiaDestroyedInBelongsToAssociation
2
+ def handle_dependency
3
+ return unless load_target
4
+
5
+ case options[:dependent]
6
+ when :destroy
7
+ target.destroy
8
+ if target.respond_to?(:paranoia_destroyed?)
9
+ raise ActiveRecord::Rollback unless target.paranoia_destroyed?
10
+ else
11
+ raise ActiveRecord::Rollback unless target.destroyed?
12
+ end
13
+ else
14
+ target.send(options[:dependent])
15
+ end
16
+ end
17
+ end
18
+
19
+ module HandleParanoiaDestroyedInHasOneAssociation
20
+ def delete(method = options[:dependent])
21
+ if load_target
22
+ case method
23
+ when :delete
24
+ target.delete
25
+ when :destroy
26
+ target.destroyed_by_association = reflection
27
+ target.destroy
28
+ if target.respond_to?(:paranoia_destroyed?)
29
+ throw(:abort) unless target.paranoia_destroyed?
30
+ else
31
+ throw(:abort) unless target.destroyed?
32
+ end
33
+ when :nullify
34
+ target.update_columns(reflection.foreign_key => nil) if target.persisted?
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ ActiveRecord::Associations::BelongsToAssociation.prepend HandleParanoiaDestroyedInBelongsToAssociation
41
+ ActiveRecord::Associations::HasOneAssociation.prepend HandleParanoiaDestroyedInHasOneAssociation
@@ -0,0 +1,26 @@
1
+ if defined?(RSpec)
2
+ require 'rspec/expectations'
3
+
4
+ # Validate the subject's class did call "acts_as_paranoid"
5
+ RSpec::Matchers.define :act_as_paranoid do
6
+ match { |subject| subject.class.ancestors.include?(Paranoia) }
7
+
8
+ failure_message_proc = lambda do
9
+ "expected #{subject.class} to use `acts_as_paranoid`"
10
+ end
11
+
12
+ failure_message_when_negated_proc = lambda do
13
+ "expected #{subject.class} not to use `acts_as_paranoid`"
14
+ end
15
+
16
+ if respond_to?(:failure_message_when_negated)
17
+ failure_message(&failure_message_proc)
18
+ failure_message_when_negated(&failure_message_when_negated_proc)
19
+ else
20
+ # RSpec 2 compatibility:
21
+ failure_message_for_should(&failure_message_proc)
22
+ failure_message_for_should_not(&failure_message_when_negated_proc)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,3 @@
1
+ module Paranoia
2
+ VERSION = '3.1.0'.freeze
3
+ end