organizze_permanent_records 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.
- data/.gitignore +6 -0
- data/Gemfile +14 -0
- data/Rakefile +1 -0
- data/lib/organizze_permanent_records.rb +250 -0
- data/lib/organizze_permanent_records/version.rb +3 -0
- data/organizze_permanent_records.gemspec +24 -0
- metadata +53 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
source 'http://rubygems.org'
|
4
|
+
|
5
|
+
gem 'activerecord'
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'jeweler' # Because Bundler doesn't fucking have version:bump tasks
|
9
|
+
gem 'rake'
|
10
|
+
gem 'sqlite3'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Specify your gem's dependencies in organizze_permanent_records.gemspec
|
14
|
+
gemspec
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require "organizze_permanent_records/version"
|
2
|
+
|
3
|
+
module OrganizzePermanentRecords
|
4
|
+
def self.included(base)
|
5
|
+
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
# Rails 3
|
9
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
10
|
+
base.extend Scopes
|
11
|
+
base.instance_eval { define_model_callbacks :revive }
|
12
|
+
# Rails 2.x.x
|
13
|
+
elsif base.respond_to?(:named_scope)
|
14
|
+
base.named_scope :deleted, :conditions => 'deleted_at IS NOT NULL'
|
15
|
+
base.named_scope :not_deleted, :conditions => { :deleted_at => nil }
|
16
|
+
base.instance_eval { define_callbacks :before_revive, :after_revive }
|
17
|
+
base.send :alias_method_chain, :destroy, :permanent_records
|
18
|
+
# Early Rails code
|
19
|
+
else
|
20
|
+
base.extend EarlyRails
|
21
|
+
base.instance_eval { define_callbacks :before_revive, :after_revive }
|
22
|
+
end
|
23
|
+
base.instance_eval do
|
24
|
+
before_revive :revive_destroyed_dependent_records
|
25
|
+
def is_permanent?
|
26
|
+
columns.detect {|c| 'deleted_at' == c.name}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Scopes
|
32
|
+
def deleted
|
33
|
+
where("#{table_name}.deleted_at IS NOT NULL")
|
34
|
+
end
|
35
|
+
def not_deleted
|
36
|
+
where("#{table_name}.deleted_at IS NULL")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module EarlyRails
|
41
|
+
def with_deleted
|
42
|
+
with_scope :find => {:conditions => "#{quoted_table_name}.deleted_at IS NOT NULL"} do
|
43
|
+
yield
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def with_not_deleted
|
48
|
+
with_scope :find => {:conditions => "#{quoted_table_name}.deleted_at IS NULL"} do
|
49
|
+
yield
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# this next bit is basically stolen from the scope_out plugin
|
54
|
+
[:deleted, :not_deleted].each do |name|
|
55
|
+
define_method "find_#{name}" do |*args|
|
56
|
+
send("with_#{name}") { find(*args) }
|
57
|
+
end
|
58
|
+
|
59
|
+
define_method "count_#{name}" do |*args|
|
60
|
+
send("with_#{name}") { count(*args) }
|
61
|
+
end
|
62
|
+
|
63
|
+
define_method "calculate_#{name}" do |*args|
|
64
|
+
send("with_#{name}") { calculate(*args) }
|
65
|
+
end
|
66
|
+
|
67
|
+
define_method "find_all_#{name}" do |*args|
|
68
|
+
send("with_#{name}") { find(:all, *args) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module InstanceMethods
|
74
|
+
|
75
|
+
def is_permanent?
|
76
|
+
respond_to?(:deleted_at)
|
77
|
+
end
|
78
|
+
|
79
|
+
def deleted?
|
80
|
+
deleted_at if is_permanent?
|
81
|
+
end
|
82
|
+
|
83
|
+
def revive
|
84
|
+
if active_record_3?
|
85
|
+
_run_revive_callbacks do
|
86
|
+
set_deleted_at nil
|
87
|
+
end
|
88
|
+
else
|
89
|
+
run_callbacks :before_revive
|
90
|
+
attempt_notifying_observers(:before_revive)
|
91
|
+
set_deleted_at nil
|
92
|
+
run_callbacks :after_revive
|
93
|
+
attempt_notifying_observers(:after_revive)
|
94
|
+
end
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def destroy(force = nil)
|
99
|
+
if active_record_3?
|
100
|
+
unless is_permanent? && (:force != force)
|
101
|
+
return permanently_delete_records_after{ super() }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
destroy_with_permanent_records force
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def set_deleted_at(value)
|
109
|
+
return self unless is_permanent?
|
110
|
+
record = self.class
|
111
|
+
record = record.unscoped if active_record_3?
|
112
|
+
record = record.find(id)
|
113
|
+
record.deleted_at = value
|
114
|
+
begin
|
115
|
+
# we call save! instead of update_attribute so an ActiveRecord::RecordInvalid
|
116
|
+
# error will be raised if the record isn't valid. (This prevents reviving records that
|
117
|
+
# disregard validation constraints,)
|
118
|
+
record.send(:save_without_callbacks)
|
119
|
+
@attributes, @attributes_cache = record.attributes, record.attributes
|
120
|
+
rescue Exception => e
|
121
|
+
# trigger dependent record destruction (they were revived before this record,
|
122
|
+
# which cannot be revived due to validations)
|
123
|
+
record.destroy
|
124
|
+
raise e
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def destroy_with_permanent_records(force = nil)
|
129
|
+
unless active_record_3?
|
130
|
+
unless is_permanent? && (:force != force)
|
131
|
+
return permanently_delete_records_after{ destroy_without_permanent_records }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
unless deleted? || new_record?
|
135
|
+
set_deleted_at Time.now
|
136
|
+
end
|
137
|
+
if active_record_3?
|
138
|
+
_run_destroy_callbacks do
|
139
|
+
save_without_callbacks
|
140
|
+
end
|
141
|
+
run_callbacks :before_destroy
|
142
|
+
save_without_callbacks
|
143
|
+
run_callbacks :after_destroy
|
144
|
+
end
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
def revive_destroyed_dependent_records
|
149
|
+
self.class.reflections.select do |name, reflection|
|
150
|
+
'destroy' == reflection.options[:dependent].to_s && reflection.klass.is_permanent?
|
151
|
+
end.each do |name, reflection|
|
152
|
+
cardinality = reflection.macro.to_s.gsub('has_', '')
|
153
|
+
if cardinality == 'many'
|
154
|
+
records = send(name)
|
155
|
+
records = records.unscoped if active_record_3?
|
156
|
+
records = records.find(:all,
|
157
|
+
:conditions => [
|
158
|
+
"#{reflection.quoted_table_name}.deleted_at > ?" +
|
159
|
+
" AND " +
|
160
|
+
"#{reflection.quoted_table_name}.deleted_at < ?",
|
161
|
+
deleted_at - 3.seconds,
|
162
|
+
deleted_at + 3.seconds
|
163
|
+
]
|
164
|
+
)
|
165
|
+
elsif cardinality == 'one' or cardinality == 'belongs_to'
|
166
|
+
if active_record_3?
|
167
|
+
self.class.unscoped do
|
168
|
+
records = [] << send(name)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
records = [] << send(name)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
[records].flatten.compact.each do |dependent|
|
175
|
+
dependent.revive
|
176
|
+
end
|
177
|
+
|
178
|
+
# and update the reflection cache
|
179
|
+
send(name, :reload)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def attempt_notifying_observers(callback)
|
184
|
+
begin
|
185
|
+
notify_observers(callback)
|
186
|
+
rescue NoMethodError => e
|
187
|
+
# do nothing: this model isn't being observed
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# return the records corresponding to an association with the `:dependent => :destroy` option
|
192
|
+
def get_dependent_records
|
193
|
+
dependent_records = {}
|
194
|
+
|
195
|
+
# check which dependent records are to be destroyed
|
196
|
+
klass = self.class
|
197
|
+
klass.reflections.each do |key, reflection|
|
198
|
+
if reflection.options[:dependent] == :destroy
|
199
|
+
next unless records = self.send(key) # skip if there are no dependent record instances
|
200
|
+
if records.respond_to? :size
|
201
|
+
next unless records.size > 0 # skip if there are no dependent record instances
|
202
|
+
else
|
203
|
+
records = [] << records
|
204
|
+
end
|
205
|
+
dependent_record = records.first
|
206
|
+
next if dependent_record.nil?
|
207
|
+
dependent_records[dependent_record.class] = records.map(&:id)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
dependent_records
|
211
|
+
end
|
212
|
+
|
213
|
+
# If we force the destruction of the record, we will need to force the destruction of dependent records if the
|
214
|
+
# user specified `:dependent => :destroy` in the model.
|
215
|
+
# By default, the call to super/destroy_with_permanent_records (i.e. the &block param) will only soft delete
|
216
|
+
# the dependent records; we keep track of the dependent records
|
217
|
+
# that have `:dependent => :destroy` and call destroy(force) on them after the call to super
|
218
|
+
def permanently_delete_records_after(&block)
|
219
|
+
dependent_records = get_dependent_records
|
220
|
+
result = block.call
|
221
|
+
if result
|
222
|
+
permanently_delete_records(dependent_records)
|
223
|
+
end
|
224
|
+
result
|
225
|
+
end
|
226
|
+
|
227
|
+
# permanently delete the records (i.e. remove from database)
|
228
|
+
def permanently_delete_records(dependent_records)
|
229
|
+
dependent_records.each do |klass, ids|
|
230
|
+
ids.each do |id|
|
231
|
+
begin
|
232
|
+
record = klass
|
233
|
+
record = record.unscoped if active_record_3?
|
234
|
+
record = record.find(id)
|
235
|
+
rescue ActiveRecord::RecordNotFound
|
236
|
+
next # the record has already been deleted, possibly due to another association with `:dependent => :destroy`
|
237
|
+
end
|
238
|
+
record.deleted_at = nil
|
239
|
+
record.destroy(:force)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def active_record_3?
|
245
|
+
ActiveRecord::VERSION::MAJOR >= 3
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
ActiveRecord::Base.send :include, PermanentRecords
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "organizze_permanent_records/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "organizze_permanent_records"
|
7
|
+
s.version = OrganizzePermanentRecords::VERSION
|
8
|
+
s.authors = ["Esdras Mayrink"]
|
9
|
+
s.email = ["falecom@oesdras.com.br"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{This gem is a clone of https://github.com/JackDanger/permanent_records. I customized it to fit my needs, this is what open source is all about, isn't it?}
|
12
|
+
s.description = %q{Never Lose Data. Rather than deleting rows this sets Record#deleted_at and gives you all the scopes you need to work with your data.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "organizze_permanent_records"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: organizze_permanent_records
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Esdras Mayrink
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-28 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Never Lose Data. Rather than deleting rows this sets Record#deleted_at
|
15
|
+
and gives you all the scopes you need to work with your data.
|
16
|
+
email:
|
17
|
+
- falecom@oesdras.com.br
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- Rakefile
|
25
|
+
- lib/organizze_permanent_records.rb
|
26
|
+
- lib/organizze_permanent_records/version.rb
|
27
|
+
- organizze_permanent_records.gemspec
|
28
|
+
homepage: ''
|
29
|
+
licenses: []
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project: organizze_permanent_records
|
48
|
+
rubygems_version: 1.8.6
|
49
|
+
signing_key:
|
50
|
+
specification_version: 3
|
51
|
+
summary: This gem is a clone of https://github.com/JackDanger/permanent_records. I
|
52
|
+
customized it to fit my needs, this is what open source is all about, isn't it?
|
53
|
+
test_files: []
|