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 ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
6
+ *.swo
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,3 @@
1
+ module OrganizzePermanentRecords
2
+ VERSION = "0.0.1"
3
+ end
@@ -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: []