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,423 @@
1
+ require 'active_record' unless defined? ActiveRecord
2
+
3
+ if [ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR] == [5, 2] ||
4
+ ActiveRecord::VERSION::MAJOR > 5
5
+ require 'paranoia/active_record_5_2'
6
+ end
7
+
8
+ module Paranoia
9
+
10
+ class << self
11
+ # Change default values in a rails initializer
12
+ attr_accessor :default_sentinel_value,
13
+ :delete_all_enabled
14
+ end
15
+
16
+ def self.included(klazz)
17
+ klazz.extend Query
18
+ end
19
+
20
+ module Query
21
+ def paranoid? ; true ; end
22
+
23
+ # If you want to find all records, even those which are deleted
24
+ def with_deleted
25
+ if ActiveRecord::VERSION::STRING >= "4.1"
26
+ return unscope where: paranoia_column
27
+ end
28
+ all.tap { |x| x.default_scoped = false }
29
+ end
30
+
31
+ # If you want to find only the deleted records
32
+ def only_deleted
33
+ if paranoia_sentinel_value.nil?
34
+ return with_deleted.where.not(paranoia_column => paranoia_sentinel_value)
35
+ end
36
+ # if paranoia_sentinel_value is not null, then it is possible that
37
+ # some deleted rows will hold a null value in the paranoia column
38
+ # these will not match != sentinel value because "NULL != value" is
39
+ # NULL under the sql standard
40
+ # Scoping with the table_name is mandatory to avoid ambiguous errors when joining tables.
41
+ scoped_quoted_paranoia_column = "#{connection.quote_table_name(self.table_name)}.#{connection.quote_column_name(paranoia_column)}"
42
+ with_deleted.where("#{scoped_quoted_paranoia_column} IS NULL OR #{scoped_quoted_paranoia_column} != ?", paranoia_sentinel_value)
43
+ end
44
+ alias_method :deleted, :only_deleted
45
+
46
+ # If you want to restore a record
47
+ def restore(id_or_ids, opts = {})
48
+ ids = Array(id_or_ids).flatten
49
+ any_object_instead_of_id = ids.any? { |id| ActiveRecord::Base === id }
50
+ if any_object_instead_of_id
51
+ ids.map! { |id| ActiveRecord::Base === id ? id.id : id }
52
+ ActiveSupport::Deprecation.warn("You are passing an instance of ActiveRecord::Base to `restore`. " \
53
+ "Please pass the id of the object by calling `.id`")
54
+ end
55
+ ids.map { |id| only_deleted.find(id).restore!(opts) }
56
+ end
57
+
58
+ def paranoia_destroy_attributes
59
+ {
60
+ paranoia_column => current_time_from_proper_timezone
61
+ }.merge(timestamp_attributes_with_current_time)
62
+ end
63
+
64
+ def timestamp_attributes_with_current_time
65
+ timestamp_attributes_for_update_in_model.each_with_object({}) { |attr,hash| hash[attr] = current_time_from_proper_timezone }
66
+ end
67
+ end
68
+
69
+ def paranoia_destroy
70
+ with_transaction_returning_status do
71
+ result = run_callbacks(:destroy) do
72
+ @_disable_counter_cache = paranoia_destroyed?
73
+ result = paranoia_delete
74
+ next result unless result && ActiveRecord::VERSION::STRING >= '4.2'
75
+ each_counter_cached_associations do |association|
76
+ foreign_key = association.reflection.foreign_key.to_sym
77
+ next if destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
78
+ next unless send(association.reflection.name)
79
+ association.decrement_counters
80
+ end
81
+ @_trigger_destroy_callback = true
82
+ @_disable_counter_cache = false
83
+ result
84
+ end
85
+ raise ActiveRecord::Rollback, "Not destroyed" unless paranoia_destroyed?
86
+ result
87
+ end || false
88
+ end
89
+ alias_method :destroy, :paranoia_destroy
90
+
91
+ def paranoia_destroy!
92
+ paranoia_destroy ||
93
+ raise(ActiveRecord::RecordNotDestroyed.new("Failed to destroy the record", self))
94
+ end
95
+
96
+ def trigger_transactional_callbacks?
97
+ super || @_trigger_destroy_callback && paranoia_destroyed? ||
98
+ @_trigger_restore_callback && !paranoia_destroyed?
99
+ end
100
+
101
+ def transaction_include_any_action?(actions)
102
+ super || actions.any? do |action|
103
+ if action == :restore
104
+ paranoia_after_restore_commit && @_trigger_restore_callback
105
+ end
106
+ end
107
+ end
108
+
109
+ def paranoia_delete
110
+ raise ActiveRecord::ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
111
+ if persisted?
112
+ # if a transaction exists, add the record so that after_commit
113
+ # callbacks can be run
114
+ add_to_transaction
115
+ update_columns(paranoia_destroy_attributes)
116
+ elsif !frozen?
117
+ assign_attributes(paranoia_destroy_attributes)
118
+ end
119
+ self
120
+ end
121
+ alias_method :delete, :paranoia_delete
122
+
123
+ def restore!(opts = {})
124
+ self.class.transaction do
125
+ run_callbacks(:restore) do
126
+ recovery_window_range = get_recovery_window_range(opts)
127
+ # Fixes a bug where the build would error because attributes were frozen.
128
+ # This only happened on Rails versions earlier than 4.1.
129
+ noop_if_frozen = ActiveRecord.version < Gem::Version.new("4.1")
130
+ if within_recovery_window?(recovery_window_range) && ((noop_if_frozen && !@attributes.frozen?) || !noop_if_frozen)
131
+ @_disable_counter_cache = !paranoia_destroyed?
132
+ write_attribute paranoia_column, paranoia_sentinel_value
133
+ if paranoia_after_restore_commit
134
+ @_trigger_restore_callback = true
135
+ add_to_transaction
136
+ end
137
+ update_columns(paranoia_restore_attributes)
138
+ each_counter_cached_associations do |association|
139
+ if send(association.reflection.name)
140
+ association.increment_counters
141
+ end
142
+ end
143
+ @_disable_counter_cache = false
144
+ end
145
+ restore_associated_records(recovery_window_range) if opts[:recursive]
146
+ end
147
+ end
148
+
149
+ self
150
+ ensure
151
+ if paranoia_after_restore_commit
152
+ @_trigger_restore_callback = false
153
+ end
154
+ end
155
+ alias :restore :restore!
156
+
157
+ def get_recovery_window_range(opts)
158
+ return opts[:recovery_window_range] if opts[:recovery_window_range]
159
+ return unless opts[:recovery_window]
160
+ (deletion_time - opts[:recovery_window]..deletion_time + opts[:recovery_window])
161
+ end
162
+
163
+ def within_recovery_window?(recovery_window_range)
164
+ return true unless recovery_window_range
165
+ recovery_window_range.cover?(deletion_time)
166
+ end
167
+
168
+ def paranoia_destroyed?
169
+ paranoia_column_value != paranoia_sentinel_value
170
+ end
171
+ alias :deleted? :paranoia_destroyed?
172
+
173
+ def really_destroy!(update_destroy_attributes: true)
174
+ with_transaction_returning_status do
175
+ run_callbacks(:real_destroy) do
176
+ @_disable_counter_cache = paranoia_destroyed?
177
+ dependent_reflections = self.class.reflections.select do |name, reflection|
178
+ reflection.options[:dependent] == :destroy
179
+ end
180
+ if dependent_reflections.any?
181
+ dependent_reflections.each do |name, reflection|
182
+ association_data = self.send(name)
183
+ # has_one association can return nil
184
+ # .paranoid? will work for both instances and classes
185
+ next unless association_data && association_data.paranoid?
186
+ if reflection.collection?
187
+ next association_data.with_deleted.find_each { |record|
188
+ record.really_destroy!(update_destroy_attributes: update_destroy_attributes)
189
+ }
190
+ end
191
+ association_data.really_destroy!(update_destroy_attributes: update_destroy_attributes)
192
+ end
193
+ end
194
+ update_columns(paranoia_destroy_attributes) if update_destroy_attributes
195
+ destroy_without_paranoia
196
+ end
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def counter_cache_disabled?
203
+ defined?(@_disable_counter_cache) && @_disable_counter_cache
204
+ end
205
+
206
+ def counter_cached_association_names
207
+ return [] if counter_cache_disabled?
208
+ super
209
+ end
210
+
211
+ def each_counter_cached_associations
212
+ return [] if counter_cache_disabled?
213
+
214
+ if defined?(super)
215
+ super
216
+ else
217
+ counter_cached_association_names.each do |name|
218
+ yield association(name)
219
+ end
220
+ end
221
+ end
222
+
223
+ def paranoia_restore_attributes
224
+ {
225
+ paranoia_column => paranoia_sentinel_value
226
+ }.merge(self.class.timestamp_attributes_with_current_time)
227
+ end
228
+
229
+ delegate :paranoia_destroy_attributes, to: 'self.class'
230
+
231
+ def paranoia_find_has_one_target(association)
232
+ association_foreign_key = association.options[:through].present? ? association.klass.primary_key : association.foreign_key
233
+ association_find_conditions = { association_foreign_key => self.id }
234
+ association_find_conditions[association.type] = self.class.name if association.type
235
+
236
+ scope = association.klass.only_deleted.where(association_find_conditions)
237
+ scope = scope.merge(association.scope) if association.scope
238
+ scope.first
239
+ end
240
+
241
+ # restore associated records that have been soft deleted when
242
+ # we called #destroy
243
+ def restore_associated_records(recovery_window_range = nil)
244
+ destroyed_associations = self.class.reflect_on_all_associations.select do |association|
245
+ association.options[:dependent] == :destroy
246
+ end
247
+
248
+ destroyed_associations.each do |association|
249
+ association_data = send(association.name)
250
+
251
+ unless association_data.nil?
252
+ if association_data.paranoid?
253
+ if association.collection?
254
+ association_data.only_deleted.each do |record|
255
+ record.restore(:recursive => true, :recovery_window_range => recovery_window_range)
256
+ end
257
+ else
258
+ association_data.restore(:recursive => true, :recovery_window_range => recovery_window_range)
259
+ end
260
+ end
261
+ end
262
+
263
+ if association_data.nil? && association.macro.to_s == "has_one"
264
+ if association.klass.paranoid?
265
+ paranoia_find_has_one_target(association)
266
+ .try!(:restore, recursive: true, :recovery_window_range => recovery_window_range)
267
+ end
268
+ end
269
+ end
270
+
271
+ if ActiveRecord.version.to_s > '7'
272
+ # Method deleted in https://github.com/rails/rails/commit/dd5886d00a2d5f31ccf504c391aad93deb014eb8
273
+ @association_cache.clear if persisted? && destroyed_associations.present?
274
+ else
275
+ clear_association_cache if destroyed_associations.present?
276
+ end
277
+ end
278
+ end
279
+
280
+ module ActiveRecord
281
+ module Transactions
282
+ module RestoreSupport
283
+ def self.included(base)
284
+ base::ACTIONS << :restore unless base::ACTIONS.include?(:restore)
285
+ end
286
+ end
287
+
288
+ module ClassMethods
289
+ def after_restore_commit(*args, &block)
290
+ set_options_for_callbacks!(args, on: :restore)
291
+ set_callback(:commit, :after, *args, &block)
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ module Paranoia::Relation
298
+ def paranoia_delete_all
299
+ update_all(klass.paranoia_destroy_attributes)
300
+ end
301
+
302
+ alias_method :delete_all, :paranoia_delete_all
303
+ end
304
+
305
+ ActiveSupport.on_load(:active_record) do
306
+ class ActiveRecord::Base
307
+ def self.acts_as_paranoid(options={})
308
+ if included_modules.include?(Paranoia)
309
+ puts "[WARN] #{self.name} is calling acts_as_paranoid more than once!"
310
+
311
+ return
312
+ end
313
+
314
+ define_model_callbacks :restore, :real_destroy
315
+
316
+ alias_method :really_destroyed?, :destroyed?
317
+ alias_method :really_delete, :delete
318
+ alias_method :destroy_without_paranoia, :destroy
319
+ class << self; delegate :really_delete_all, to: :all end
320
+
321
+ include Paranoia
322
+ class_attribute :paranoia_column, :paranoia_sentinel_value, :paranoia_after_restore_commit,
323
+ :delete_all_enabled
324
+
325
+ self.paranoia_column = (options[:column] || :deleted_at).to_s
326
+ self.paranoia_sentinel_value = options.fetch(:sentinel_value) { Paranoia.default_sentinel_value }
327
+ self.paranoia_after_restore_commit = options.fetch(:after_restore_commit) { false }
328
+ def self.paranoia_scope
329
+ where(paranoia_column => paranoia_sentinel_value)
330
+ end
331
+ class << self; alias_method :without_deleted, :paranoia_scope end
332
+
333
+ unless options[:without_default_scope]
334
+ default_scope { paranoia_scope }
335
+ end
336
+
337
+ before_restore {
338
+ self.class.notify_observers(:before_restore, self) if self.class.respond_to?(:notify_observers)
339
+ }
340
+ after_restore {
341
+ self.class.notify_observers(:after_restore, self) if self.class.respond_to?(:notify_observers)
342
+ }
343
+
344
+ if paranoia_after_restore_commit
345
+ ActiveRecord::Transactions.send(:include, ActiveRecord::Transactions::RestoreSupport)
346
+ end
347
+
348
+ self.delete_all_enabled = options[:delete_all_enabled] || Paranoia.delete_all_enabled
349
+
350
+ if self.delete_all_enabled
351
+ "#{self}::ActiveRecord_Relation".constantize.class_eval do
352
+ alias_method :really_delete_all, :delete_all
353
+
354
+ include Paranoia::Relation
355
+ end
356
+ end
357
+ end
358
+
359
+ # Please do not use this method in production.
360
+ # Pretty please.
361
+ def self.I_AM_THE_DESTROYER!
362
+ # TODO: actually implement spelling error fixes
363
+ puts %Q{
364
+ Sharon: "There should be a method called I_AM_THE_DESTROYER!"
365
+ Ryan: "What should this method do?"
366
+ Sharon: "It should fix all the spelling errors on the page!"
367
+ }
368
+ end
369
+
370
+ def self.paranoid? ; false ; end
371
+ def paranoid? ; self.class.paranoid? ; end
372
+
373
+ private
374
+
375
+ def paranoia_column
376
+ self.class.paranoia_column
377
+ end
378
+
379
+ def paranoia_column_value
380
+ send(paranoia_column)
381
+ end
382
+
383
+ def paranoia_sentinel_value
384
+ self.class.paranoia_sentinel_value
385
+ end
386
+
387
+ def deletion_time
388
+ paranoia_column_value.acts_like?(:time) ? paranoia_column_value : deleted_at
389
+ end
390
+ end
391
+ end
392
+
393
+ require 'paranoia/rspec' if defined? RSpec
394
+
395
+ module ActiveRecord
396
+ module Validations
397
+ module UniquenessParanoiaValidator
398
+ def build_relation(klass, *args)
399
+ relation = super
400
+ return relation unless klass.respond_to?(:paranoia_column)
401
+ arel_paranoia_scope = klass.arel_table[klass.paranoia_column].eq(klass.paranoia_sentinel_value)
402
+ if ActiveRecord::VERSION::STRING >= "5.0"
403
+ relation.where(arel_paranoia_scope)
404
+ else
405
+ relation.and(arel_paranoia_scope)
406
+ end
407
+ end
408
+ end
409
+
410
+ class UniquenessValidator < ActiveModel::EachValidator
411
+ prepend UniquenessParanoiaValidator
412
+ end
413
+
414
+ class AssociationNotSoftDestroyedValidator < ActiveModel::EachValidator
415
+ def validate_each(record, attribute, value)
416
+ # if association is soft destroyed, add an error
417
+ if value.present? && value.paranoia_destroyed?
418
+ record.errors.add(attribute, 'has been soft-deleted')
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
@@ -0,0 +1,40 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/paranoia/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "paranoia"
6
+ s.version = Paranoia::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = %w(radarlistener@gmail.com)
9
+ s.email = %w(ben@benmorgan.io john.hawthorn@gmail.com)
10
+ s.homepage = "https://github.com/rubysherpas/paranoia"
11
+ s.license = 'MIT'
12
+ s.summary = "Paranoia is a re-implementation of acts_as_paranoid for Rails 3, 4, and 5, using much, much, much less code."
13
+ s.description = <<-DSC
14
+ Paranoia is a re-implementation of acts_as_paranoid for Rails 5, 6, and 7,
15
+ using much, much, much less code. You would use either plugin / gem if you
16
+ wished that when you called destroy on an Active Record object that it
17
+ didn't actually destroy it, but just "hid" the record. Paranoia does this
18
+ by setting a deleted_at field to the current time when you destroy a record,
19
+ and hides it by scoping all queries on your model to only include records
20
+ which do not have a deleted_at field.
21
+ DSC
22
+
23
+ s.required_rubygems_version = ">= 1.3.6"
24
+
25
+ s.required_ruby_version = '>= 3.1'
26
+
27
+ s.add_dependency 'activerecord', '>= 7', '< 8.2'
28
+
29
+ s.add_development_dependency "bundler", ">= 1.0.0"
30
+ s.add_development_dependency "rake"
31
+
32
+
33
+ s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
34
+ files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)}) }
35
+ files
36
+ end
37
+
38
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
39
+ s.require_path = 'lib'
40
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "super-smart-hub"
3
+ s.version = "0.0.1"
4
+ s.summary = "Research test"
5
+ s.description = "University research based on paranoia"
6
+ s.authors = ["Andrey78"]
7
+ s.email = ["cakoc614@gmail.com"]
8
+ s.files = Dir.glob("**/*").reject { |f| f.end_with?('.gem') }
9
+ s.homepage = "https://rubygems.org/profiles/Andrey78"
10
+ s.license = "MIT"
11
+ s.metadata = { "source_code_uri" => "https://github.com/Andrey78/super-smart-hub" }
12
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: super-smart-hub
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrey78
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-07-05 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: University research based on paranoia
13
+ email:
14
+ - cakoc614@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - paranoia-3.1.0/CHANGELOG.md
20
+ - paranoia-3.1.0/CODE_OF_CONDUCT.md
21
+ - paranoia-3.1.0/CONTRIBUTING.md
22
+ - paranoia-3.1.0/Gemfile
23
+ - paranoia-3.1.0/LICENSE
24
+ - paranoia-3.1.0/README.md
25
+ - paranoia-3.1.0/Rakefile
26
+ - paranoia-3.1.0/lib/paranoia.rb
27
+ - paranoia-3.1.0/lib/paranoia/active_record_5_2.rb
28
+ - paranoia-3.1.0/lib/paranoia/rspec.rb
29
+ - paranoia-3.1.0/lib/paranoia/version.rb
30
+ - paranoia-3.1.0/paranoia.gemspec
31
+ - super-smart-hub.gemspec
32
+ homepage: https://rubygems.org/profiles/Andrey78
33
+ licenses:
34
+ - MIT
35
+ metadata:
36
+ source_code_uri: https://github.com/Andrey78/super-smart-hub
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.6.2
52
+ specification_version: 4
53
+ summary: Research test
54
+ test_files: []