permanent_records 2.3.0 → 3.0.0

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/Gemfile CHANGED
@@ -1,9 +1,16 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
- gem 'activerecord'
3
+ gem 'activerecord', '> 3.0.0'
4
4
 
5
5
  group :development do
6
- gem 'jeweler' # Because Bundler doesn't fucking have version:bump tasks
7
6
  gem 'rake'
7
+ gem 'pg'
8
+ gem 'activesupport'
8
9
  gem 'sqlite3'
9
10
  end
11
+
12
+ group :test do
13
+ gem 'awesome_print'
14
+ gem 'rspec'
15
+ gem 'database_cleaner'
16
+ end
data/Rakefile CHANGED
@@ -1,28 +1,13 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
3
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "permanent_records"
8
- gem.summary = %Q{Soft-delete your ActiveRecord records}
9
- gem.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.}
10
- gem.email = "gems@6brand.com"
11
- gem.homepage = "http://github.com/JackDanger/permanent_records"
12
- gem.authors = ["Jack Danger Canty", "David Sulc"]
4
+ namespace :db do
5
+ task :create do
6
+ `createdb permanent_records`
13
7
  end
14
- rescue LoadError
15
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
8
  end
17
9
 
18
- require 'rake/testtask'
19
- Rake::TestTask.new(:test) do |test|
20
- test.libs << 'lib' << 'test'
21
- test.ruby_opts << '-rubygems'
22
- test.pattern = 'test/*_test.rb'
23
- test.verbose = true
10
+ desc 'Run all tests'
11
+ task :spec => 'db:create' do
12
+ exec 'rspec'
24
13
  end
25
- task :spec => :test
26
-
27
- task :default => :test
28
-
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.3.0
1
+ 3.0.0
@@ -1,245 +1,168 @@
1
1
  module PermanentRecords
2
+
2
3
  def self.included(base)
3
4
 
4
- base.send :include, InstanceMethods
5
-
6
- # Rails 3
7
- if ActiveRecord::VERSION::MAJOR >= 3
8
- base.extend Scopes
9
- base.instance_eval { define_model_callbacks :revive }
10
- # Rails 2.x.x
11
- elsif base.respond_to?(:named_scope)
12
- base.named_scope :deleted, :conditions => 'deleted_at IS NOT NULL'
13
- base.named_scope :not_deleted, :conditions => { :deleted_at => nil }
14
- base.instance_eval { define_callbacks :before_revive, :after_revive }
15
- base.send :alias_method_chain, :destroy, :permanent_records
16
- # Early Rails code
17
- else
18
- base.extend EarlyRails
19
- base.instance_eval { define_callbacks :before_revive, :after_revive }
20
- end
5
+ base.extend Scopes
6
+ base.extend IsPermanent
7
+
21
8
  base.instance_eval do
9
+ define_model_callbacks :revive
10
+
22
11
  before_revive :revive_destroyed_dependent_records
23
- def is_permanent?
24
- columns.detect {|c| 'deleted_at' == c.name}
25
- end
26
12
  end
13
+
27
14
  end
28
-
15
+
29
16
  module Scopes
30
17
  def deleted
31
- where("#{table_name}.deleted_at IS NOT NULL")
18
+ where "#{table_name}.deleted_at IS NOT NULL"
32
19
  end
20
+
33
21
  def not_deleted
34
- where("#{table_name}.deleted_at IS NULL")
22
+ where "#{table_name}.deleted_at IS NULL"
35
23
  end
36
24
  end
37
-
38
- module EarlyRails
39
- def with_deleted
40
- with_scope :find => {:conditions => "#{quoted_table_name}.deleted_at IS NOT NULL"} do
41
- yield
42
- end
43
- end
44
-
45
- def with_not_deleted
46
- with_scope :find => {:conditions => "#{quoted_table_name}.deleted_at IS NULL"} do
47
- yield
48
- end
25
+
26
+ module IsPermanent
27
+ def is_permanent?
28
+ columns.detect {|c| 'deleted_at' == c.name}
49
29
  end
50
-
51
- # this next bit is basically stolen from the scope_out plugin
52
- [:deleted, :not_deleted].each do |name|
53
- define_method "find_#{name}" do |*args|
54
- send("with_#{name}") { find(*args) }
55
- end
30
+ end
56
31
 
57
- define_method "count_#{name}" do |*args|
58
- send("with_#{name}") { count(*args) }
59
- end
32
+ def is_permanent?
33
+ respond_to?(:deleted_at)
34
+ end
60
35
 
61
- define_method "calculate_#{name}" do |*args|
62
- send("with_#{name}") { calculate(*args) }
63
- end
36
+ def deleted?
37
+ deleted_at if is_permanent?
38
+ end
64
39
 
65
- define_method "find_all_#{name}" do |*args|
66
- send("with_#{name}") { find(:all, *args) }
67
- end
68
- end
40
+ def revive
41
+ _run_revive_callbacks { set_deleted_at nil }
42
+ self
69
43
  end
70
44
 
71
- module InstanceMethods
72
-
73
- def is_permanent?
74
- respond_to?(:deleted_at)
45
+ def destroy(force = nil)
46
+ unless is_permanent? && (:force != force)
47
+ return permanently_delete_records_after { super() }
75
48
  end
76
-
77
- def deleted?
78
- deleted_at if is_permanent?
49
+ destroy_with_permanent_records force
50
+ end
51
+
52
+ protected
53
+
54
+ def set_deleted_at(value)
55
+ return self unless is_permanent?
56
+ record = self.class.unscoped.find(id)
57
+ record.deleted_at = value
58
+ begin
59
+ # we call save! instead of update_attribute so an ActiveRecord::RecordInvalid
60
+ # error will be raised if the record isn't valid. (This prevents reviving records that
61
+ # disregard validation constraints,)
62
+ record.save!
63
+ @attributes, @attributes_cache = record.attributes, record.attributes
64
+ rescue Exception => e
65
+ # trigger dependent record destruction (they were revived before this record,
66
+ # which cannot be revived due to validations)
67
+ record.destroy
68
+ raise e
79
69
  end
70
+ end
80
71
 
81
- def revive
82
- if active_record_3?
83
- _run_revive_callbacks do
84
- set_deleted_at nil
85
- end
86
- else
87
- run_callbacks :before_revive
88
- attempt_notifying_observers(:before_revive)
89
- set_deleted_at nil
90
- run_callbacks :after_revive
91
- attempt_notifying_observers(:after_revive)
92
- end
93
- self
72
+ def destroy_with_permanent_records(force = nil)
73
+ _run_destroy_callbacks do
74
+ deleted? || new_record? ? save : set_deleted_at(Time.now)
94
75
  end
76
+ self
77
+ end
95
78
 
96
- def destroy(force = nil)
97
- if active_record_3?
98
- unless is_permanent? && (:force != force)
99
- return permanently_delete_records_after{ super() }
79
+ def revive_destroyed_dependent_records
80
+ self.class.reflections.select do |name, reflection|
81
+ 'destroy' == reflection.options[:dependent].to_s && reflection.klass.is_permanent?
82
+ end.each do |name, reflection|
83
+ cardinality = reflection.macro.to_s.gsub('has_', '')
84
+ if cardinality == 'many'
85
+ records = send(name).unscoped.find(
86
+ :all,
87
+ :conditions => [
88
+ "#{reflection.quoted_table_name}.deleted_at > ?" +
89
+ " AND " +
90
+ "#{reflection.quoted_table_name}.deleted_at < ?",
91
+ deleted_at - 3.seconds,
92
+ deleted_at + 3.seconds
93
+ ]
94
+ )
95
+ elsif cardinality == 'one' or cardinality == 'belongs_to'
96
+ self.class.unscoped do
97
+ records = [] << send(name)
100
98
  end
101
99
  end
102
- destroy_with_permanent_records force
103
- end
104
-
105
- private
106
- def set_deleted_at(value)
107
- return self unless is_permanent?
108
- record = self.class
109
- record = record.unscoped if active_record_3?
110
- record = record.find(id)
111
- record.deleted_at = value
112
- begin
113
- # we call save! instead of update_attribute so an ActiveRecord::RecordInvalid
114
- # error will be raised if the record isn't valid. (This prevents reviving records that
115
- # disregard validation constraints,)
116
- record.save!
117
- @attributes, @attributes_cache = record.attributes, record.attributes
118
- rescue Exception => e
119
- # trigger dependent record destruction (they were revived before this record,
120
- # which cannot be revived due to validations)
121
- record.destroy
122
- raise e
100
+ [records].flatten.compact.each do |dependent|
101
+ dependent.revive
123
102
  end
103
+
104
+ # and update the reflection cache
105
+ send(name, :reload)
124
106
  end
107
+ end
125
108
 
126
- def destroy_with_permanent_records(force = nil)
127
- unless active_record_3?
128
- unless is_permanent? && (:force != force)
129
- return permanently_delete_records_after{ destroy_without_permanent_records }
130
- end
131
- end
132
- if active_record_3?
133
- _run_destroy_callbacks do
134
- deleted? || new_record? ? save : set_deleted_at(Time.now)
135
- end
136
- else
137
- run_callbacks :before_destroy
138
- deleted? || new_record? ? save : set_deleted_at(Time.now)
139
- run_callbacks :after_destroy
140
- end
141
- self
109
+ def attempt_notifying_observers(callback)
110
+ begin
111
+ notify_observers(callback)
112
+ rescue NoMethodError => e
113
+ # do nothing: this model isn't being observed
142
114
  end
115
+ end
143
116
 
144
- def revive_destroyed_dependent_records
145
- self.class.reflections.select do |name, reflection|
146
- 'destroy' == reflection.options[:dependent].to_s && reflection.klass.is_permanent?
147
- end.each do |name, reflection|
148
- cardinality = reflection.macro.to_s.gsub('has_', '')
149
- if cardinality == 'many'
150
- records = send(name)
151
- records = records.unscoped if active_record_3?
152
- records = records.find(:all,
153
- :conditions => [
154
- "#{reflection.quoted_table_name}.deleted_at > ?" +
155
- " AND " +
156
- "#{reflection.quoted_table_name}.deleted_at < ?",
157
- deleted_at - 3.seconds,
158
- deleted_at + 3.seconds
159
- ]
160
- )
161
- elsif cardinality == 'one' or cardinality == 'belongs_to'
162
- if active_record_3?
163
- self.class.unscoped do
164
- records = [] << send(name)
165
- end
166
- else
167
- records = [] << send(name)
168
- end
169
- end
170
- [records].flatten.compact.each do |dependent|
171
- dependent.revive
172
- end
117
+ # return the records corresponding to an association with the `:dependent => :destroy` option
118
+ def get_dependent_records
119
+ dependent_records = {}
173
120
 
174
- # and update the reflection cache
175
- send(name, :reload)
176
- end
177
- end
178
-
179
- def attempt_notifying_observers(callback)
180
- begin
181
- notify_observers(callback)
182
- rescue NoMethodError => e
183
- # do nothing: this model isn't being observed
184
- end
185
- end
186
-
187
- # return the records corresponding to an association with the `:dependent => :destroy` option
188
- def get_dependent_records
189
- dependent_records = {}
190
-
191
- # check which dependent records are to be destroyed
192
- klass = self.class
193
- klass.reflections.each do |key, reflection|
194
- if reflection.options[:dependent] == :destroy
195
- next unless records = self.send(key) # skip if there are no dependent record instances
196
- if records.respond_to? :size
197
- next unless records.size > 0 # skip if there are no dependent record instances
198
- else
199
- records = [] << records
200
- end
201
- dependent_record = records.first
202
- next if dependent_record.nil?
203
- dependent_records[dependent_record.class] = records.map(&:id)
121
+ # check which dependent records are to be destroyed
122
+ klass = self.class
123
+ klass.reflections.each do |key, reflection|
124
+ if reflection.options[:dependent] == :destroy
125
+ next unless records = self.send(key) # skip if there are no dependent record instances
126
+ if records.respond_to? :size
127
+ next unless records.size > 0 # skip if there are no dependent record instances
128
+ else
129
+ records = [] << records
204
130
  end
131
+ dependent_record = records.first
132
+ next if dependent_record.nil?
133
+ dependent_records[dependent_record.class] = records.map(&:id)
205
134
  end
206
- dependent_records
207
- end
208
-
209
- # If we force the destruction of the record, we will need to force the destruction of dependent records if the
210
- # user specified `:dependent => :destroy` in the model.
211
- # By default, the call to super/destroy_with_permanent_records (i.e. the &block param) will only soft delete
212
- # the dependent records; we keep track of the dependent records
213
- # that have `:dependent => :destroy` and call destroy(force) on them after the call to super
214
- def permanently_delete_records_after(&block)
215
- dependent_records = get_dependent_records
216
- result = block.call
217
- if result
218
- permanently_delete_records(dependent_records)
219
- end
220
- result
221
135
  end
222
-
223
- # permanently delete the records (i.e. remove from database)
224
- def permanently_delete_records(dependent_records)
225
- dependent_records.each do |klass, ids|
226
- ids.each do |id|
227
- begin
228
- record = klass
229
- record = record.unscoped if active_record_3?
230
- record = record.find(id)
231
- rescue ActiveRecord::RecordNotFound
232
- next # the record has already been deleted, possibly due to another association with `:dependent => :destroy`
233
- end
234
- record.deleted_at = nil
235
- record.destroy(:force)
136
+ dependent_records
137
+ end
138
+
139
+ # If we force the destruction of the record, we will need to force the destruction of dependent records if the
140
+ # user specified `:dependent => :destroy` in the model.
141
+ # By default, the call to super/destroy_with_permanent_records (i.e. the &block param) will only soft delete
142
+ # the dependent records; we keep track of the dependent records
143
+ # that have `:dependent => :destroy` and call destroy(force) on them after the call to super
144
+ def permanently_delete_records_after(&block)
145
+ dependent_records = get_dependent_records
146
+ result = block.call
147
+ if result
148
+ permanently_delete_records(dependent_records)
149
+ end
150
+ result
151
+ end
152
+
153
+ # permanently delete the records (i.e. remove from database)
154
+ def permanently_delete_records(dependent_records)
155
+ dependent_records.each do |klass, ids|
156
+ ids.each do |id|
157
+ record = begin
158
+ klass.unscoped.find id
159
+ rescue ActiveRecord::RecordNotFound
160
+ next # the record has already been deleted, possibly due to another association with `:dependent => :destroy`
236
161
  end
162
+ record.deleted_at = nil
163
+ record.destroy(:force)
237
164
  end
238
165
  end
239
-
240
- def active_record_3?
241
- ActiveRecord::VERSION::MAJOR >= 3
242
- end
243
166
  end
244
167
  end
245
168
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "permanent_records"
8
- s.version = "2.3.0"
8
+ s.version = File.read('VERSION')
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jack Danger Canty", "David Sulc"]
@@ -26,20 +26,20 @@ Gem::Specification.new do |s|
26
26
  "VERSION",
27
27
  "lib/permanent_records.rb",
28
28
  "permanent_records.gemspec",
29
- "test/comment.rb",
30
- "test/database.yml",
31
- "test/difficulty.rb",
32
- "test/dirt.rb",
33
- "test/earthworm.rb",
34
- "test/hole.rb",
35
- "test/kitty.rb",
36
- "test/location.rb",
37
- "test/mole.rb",
38
- "test/muskrat.rb",
39
- "test/permanent_records_test.rb",
40
- "test/schema.rb",
41
- "test/test_helper.rb",
42
- "test/unused_model.rb"
29
+ "spec/spec_helper.rb",
30
+ "spec/permanent_records_spec.rb",
31
+ "spec/support/comment.rb",
32
+ "spec/support/database.yml",
33
+ "spec/support/difficulty.rb",
34
+ "spec/support/dirt.rb",
35
+ "spec/support/earthworm.rb",
36
+ "spec/support/hole.rb",
37
+ "spec/support/kitty.rb",
38
+ "spec/support/location.rb",
39
+ "spec/support/mole.rb",
40
+ "spec/support/muskrat.rb",
41
+ "spec/support/schema.rb",
42
+ "spec/support/unused_model.rb"
43
43
  ]
44
44
  s.homepage = "http://github.com/JackDanger/permanent_records"
45
45
  s.require_paths = ["lib"]
@@ -0,0 +1,212 @@
1
+ require 'spec_helper'
2
+
3
+ describe PermanentRecords do
4
+
5
+ let!(:frozen_moment) { Time.now }
6
+ let!(:dirt) { Dirt.create! }
7
+ let!(:earthworm) { dirt.create_earthworm }
8
+ let!(:hole) { dirt.create_hole }
9
+ let!(:muskrat) { hole.muskrats.create! }
10
+ let!(:mole) { hole.moles.create! }
11
+ let!(:location) { hole.create_location }
12
+ let!(:difficulty) { hole.create_difficulty }
13
+ let!(:comments) { 2.times.map {hole.comments.create!} }
14
+ let!(:kitty) { Kitty.create! }
15
+
16
+
17
+ describe '#destroy' do
18
+
19
+ let(:record) { hole }
20
+ let(:should_force) { false }
21
+
22
+ subject { record.destroy should_force }
23
+
24
+ it 'returns the record' do
25
+ subject.should == record
26
+ end
27
+
28
+ it 'makes deleted? return true' do
29
+ subject.should be_deleted
30
+ end
31
+
32
+ it 'sets the deleted_at attribute' do
33
+ subject.deleted_at.should be_within(0.1).of(Time.now)
34
+ end
35
+
36
+ it 'does not really remove the record' do
37
+ expect { subject }.to_not change { record.class.count }
38
+ end
39
+
40
+ context 'with force argument set to truthy' do
41
+ let(:should_force) { :force }
42
+
43
+ it 'does really remove the record' do
44
+ expect { subject }.to change { record.class.count }.by(-1)
45
+ end
46
+ end
47
+
48
+ context 'when model has no deleted_at column' do
49
+ let(:record) { kitty }
50
+
51
+ it 'really removes the record' do
52
+ expect { subject }.to change { record.class.count }.by(-1)
53
+ end
54
+ end
55
+
56
+ context 'with dependent records' do
57
+ context 'that are permanent' do
58
+ it '' do
59
+ expect { subject }.to_not change { Muskrat.count }
60
+ end
61
+
62
+ context 'with has_many cardinality' do
63
+ it 'marks records as deleted' do
64
+ subject.muskrats.each {|m| m.should be_deleted }
65
+ end
66
+ context 'with force delete' do
67
+ let(:should_force) { :force }
68
+ it('') { expect { subject }.to change { Muskrat.count }.by(-1) }
69
+ it('') { expect { subject }.to change { Comment.count }.by(-2) }
70
+ end
71
+ end
72
+
73
+ context 'with has_one cardinality' do
74
+ it 'marks records as deleted' do
75
+ subject.location.should be_deleted
76
+ end
77
+ context 'with force delete' do
78
+ let(:should_force) { :force }
79
+ it('') { expect { subject }.to change { Muskrat.count }.by(-1) }
80
+ it('') { expect { subject }.to change { Location.count }.by(-1) }
81
+ end
82
+ end
83
+ end
84
+ context 'that are non-permanent' do
85
+ it 'removes them' do
86
+ expect { subject }.to change { Mole.count }.by(-1)
87
+ end
88
+ end
89
+ context 'as default scope' do
90
+ context 'with :has_many cardinality' do
91
+ its('comments.size') { should == 2 }
92
+ it 'deletes them' do
93
+ subject.comments.each {|c| c.should be_deleted }
94
+ subject.comments.each {|c| Comment.find_by_id(c.id).should be_nil }
95
+ end
96
+ end
97
+ context 'with :has_one cardinality' do
98
+ it 'deletes them' do
99
+ subject.difficulty.should be_deleted
100
+ Difficulty.find_by_id(subject.difficulty.id).should be_nil
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#revive' do
108
+
109
+ let!(:record) { hole.destroy }
110
+
111
+ subject { record.revive }
112
+
113
+ it 'returns the record' do
114
+ subject.should == record
115
+ end
116
+
117
+ it 'unsets deleted_at' do
118
+ expect { subject }.to change {
119
+ record.deleted_at
120
+ }.to(nil)
121
+ end
122
+
123
+ it 'makes deleted? return false' do
124
+ subject.should_not be_deleted
125
+ end
126
+
127
+ context 'when validations fail' do
128
+ before {
129
+ Hole.any_instance.stub(:valid?).and_return(false)
130
+ }
131
+ it 'raises' do
132
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
133
+ end
134
+ end
135
+
136
+ context 'with dependent records' do
137
+ context 'that are permanent' do
138
+ it '' do
139
+ expect { subject }.to_not change { Muskrat.count }
140
+ end
141
+
142
+ context 'that were deleted previously' do
143
+ before { muskrat.update_attributes! :deleted_at => 2.minutes.ago }
144
+ it 'does not restore' do
145
+ expect { subject }.to_not change { muskrat.deleted? }
146
+ end
147
+ end
148
+
149
+ context 'with has_many cardinality' do
150
+ it 'revives them' do
151
+ subject.muskrats.each {|m| m.should_not be_deleted }
152
+ end
153
+ end
154
+
155
+ context 'with has_one cardinality' do
156
+ it 'revives them' do
157
+ subject.location.should_not be_deleted
158
+ end
159
+ end
160
+ end
161
+ context 'that are non-permanent' do
162
+ it 'cannot revive them' do
163
+ expect { subject }.to_not change { Mole.count }
164
+ end
165
+ end
166
+ context 'as default scope' do
167
+ context 'with :has_many cardinality' do
168
+ its('comments.size') { should == 2 }
169
+ it 'revives them' do
170
+ subject.comments.each {|c| c.should_not be_deleted }
171
+ subject.comments.each {|c| Comment.find_by_id(c.id).should == c }
172
+ end
173
+ end
174
+ context 'with :has_one cardinality' do
175
+ it 'revives them' do
176
+ subject.difficulty.should_not be_deleted
177
+ Difficulty.find_by_id(subject.difficulty.id).should == difficulty
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ describe 'scopes' do
185
+
186
+ before {
187
+ 3.times { Muskrat.create! }
188
+ 6.times { Muskrat.create!.destroy }
189
+ }
190
+
191
+ context '.not_deleted' do
192
+
193
+ it 'counts' do
194
+ Muskrat.not_deleted.count.should == Muskrat.all.reject(&:deleted?).size
195
+ end
196
+
197
+ it 'has no deleted records' do
198
+ Muskrat.not_deleted.each {|m| m.should_not be_deleted }
199
+ end
200
+ end
201
+
202
+ context '.deleted' do
203
+ it 'counts' do
204
+ Muskrat.deleted.count.should == Muskrat.all.select(&:deleted?).size
205
+ end
206
+
207
+ it 'has no non-deleted records' do
208
+ Muskrat.deleted.each {|m| m.should be_deleted }
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,48 @@
1
+
2
+ # Include this file in your test by copying the following line to your test:
3
+ # require File.expand_path(File.dirname(__FILE__) + "/test_helper")
4
+
5
+ lib = Pathname.new File.expand_path('../../lib', File.dirname(__FILE__))
6
+ support = Pathname.new File.expand_path('../spec/support', File.dirname(__FILE__))
7
+ $:.unshift lib
8
+ $:.unshift support
9
+ RAILS_ROOT = File.dirname(__FILE__)
10
+
11
+ require 'active_record'
12
+ require 'active_support'
13
+ require 'permanent_records'
14
+ require 'awesome_print'
15
+
16
+ module Rails
17
+ def self.env; 'test'end
18
+ end
19
+
20
+ require 'logger'
21
+ ActiveRecord::Base.logger = Logger.new support.join("debug.log")
22
+ ActiveRecord::Base.configurations = YAML::load_file support.join('database.yml')
23
+ ActiveRecord::Base.establish_connection
24
+
25
+ load 'schema.rb' if File.exist?(support.join('schema.rb'))
26
+
27
+ Dir.glob(support.join('*.rb')).each do |file|
28
+ autoload File.basename(file).chomp('.rb').camelcase.intern, file
29
+ end.each do |file|
30
+ require file
31
+ end
32
+
33
+ require 'database_cleaner'
34
+
35
+ RSpec.configure do |config|
36
+ config.before(:suite) do
37
+ DatabaseCleaner.strategy = :transaction
38
+ DatabaseCleaner.clean_with(:truncation)
39
+ end
40
+
41
+ config.before(:each) do
42
+ DatabaseCleaner.start
43
+ end
44
+
45
+ config.after(:each) do
46
+ DatabaseCleaner.clean
47
+ end
48
+ end
@@ -1,6 +1,6 @@
1
1
  class Comment < ActiveRecord::Base
2
2
  belongs_to :hole
3
-
3
+
4
4
  if ActiveRecord::VERSION::MAJOR >= 3
5
5
  default_scope where(:deleted_at => nil)
6
6
  else
@@ -0,0 +1,16 @@
1
+ test:
2
+ :adapter: postgresql
3
+ :database: permanent_records
4
+ :min_messages: ERROR
5
+ # sqlite:
6
+ # :adapter: sqlite
7
+ # :database: plugin.sqlite.db
8
+ # sqlite3:
9
+ # :adapter: sqlite3
10
+ # :database: ":memory:"
11
+ # mysql:
12
+ # :adapter: mysql
13
+ # :host: localhost
14
+ # :username: rails
15
+ # :password:
16
+ # :database: plugin_test
@@ -0,0 +1,5 @@
1
+ class Difficulty < ActiveRecord::Base
2
+ belongs_to :hole
3
+
4
+ default_scope where(:deleted_at => nil)
5
+ end
File without changes
@@ -1,11 +1,11 @@
1
1
  class Earthworm < ActiveRecord::Base
2
2
  belongs_to :dirt
3
-
3
+
4
4
  # Earthworms have been known to complain if they're left on their deathbeds without any dirt
5
5
  before_destroy :complain!
6
-
6
+
7
7
  def complain!
8
8
  raise "Where's my dirt?!" if Dirt.not_deleted.find(self.dirt_id).nil?
9
9
  end
10
-
10
+
11
11
  end
@@ -1,14 +1,14 @@
1
1
  class Hole < ActiveRecord::Base
2
2
  # Because when we're destroying a mole hole we're obviously using high explosives.
3
3
  belongs_to :dirt, :dependent => :destroy
4
-
4
+
5
5
  # muskrats are permanent
6
6
  has_many :muskrats, :dependent => :destroy
7
7
  # moles are not permanent
8
8
  has_many :moles, :dependent => :destroy
9
-
9
+
10
10
  has_one :location, :dependent => :destroy
11
11
  has_one :unused_model, :dependent => :destroy
12
12
  has_one :difficulty, :dependent => :destroy
13
13
  has_many :comments, :dependent => :destroy
14
- end
14
+ end
File without changes
@@ -1,5 +1,5 @@
1
1
  class Location < ActiveRecord::Base
2
2
  belongs_to :hole
3
-
3
+
4
4
  validates_uniqueness_of :name, :scope => :deleted_at
5
5
  end
File without changes
File without changes
File without changes
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: permanent_records
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-10-07 00:00:00.000000000 Z
13
+ date: 2012-02-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -94,20 +94,20 @@ files:
94
94
  - VERSION
95
95
  - lib/permanent_records.rb
96
96
  - permanent_records.gemspec
97
- - test/comment.rb
98
- - test/database.yml
99
- - test/difficulty.rb
100
- - test/dirt.rb
101
- - test/earthworm.rb
102
- - test/hole.rb
103
- - test/kitty.rb
104
- - test/location.rb
105
- - test/mole.rb
106
- - test/muskrat.rb
107
- - test/permanent_records_test.rb
108
- - test/schema.rb
109
- - test/test_helper.rb
110
- - test/unused_model.rb
97
+ - spec/spec_helper.rb
98
+ - spec/permanent_records_spec.rb
99
+ - spec/support/comment.rb
100
+ - spec/support/database.yml
101
+ - spec/support/difficulty.rb
102
+ - spec/support/dirt.rb
103
+ - spec/support/earthworm.rb
104
+ - spec/support/hole.rb
105
+ - spec/support/kitty.rb
106
+ - spec/support/location.rb
107
+ - spec/support/mole.rb
108
+ - spec/support/muskrat.rb
109
+ - spec/support/schema.rb
110
+ - spec/support/unused_model.rb
111
111
  homepage: http://github.com/JackDanger/permanent_records
112
112
  licenses: []
113
113
  post_install_message:
@@ -122,7 +122,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
122
  version: '0'
123
123
  segments:
124
124
  - 0
125
- hash: -2829435595636432217
125
+ hash: 1142090409982771536
126
126
  required_rubygems_version: !ruby/object:Gem::Requirement
127
127
  none: false
128
128
  requirements:
data/test/database.yml DELETED
@@ -1,18 +0,0 @@
1
- sqlite:
2
- :adapter: sqlite
3
- :database: plugin.sqlite.db
4
- sqlite3:
5
- :adapter: sqlite3
6
- :database: ":memory:"
7
- postgresql:
8
- :adapter: postgresql
9
- :username: postgres
10
- :password: postgres
11
- :database: plugin_test
12
- :min_messages: ERROR
13
- mysql:
14
- :adapter: mysql
15
- :host: localhost
16
- :username: rails
17
- :password:
18
- :database: plugin_test
data/test/difficulty.rb DELETED
@@ -1,11 +0,0 @@
1
- class Difficulty < ActiveRecord::Base
2
- belongs_to :hole
3
-
4
- if ActiveRecord::VERSION::MAJOR >= 3
5
- default_scope where(:deleted_at => nil)
6
- else
7
- def self.unscoped
8
- self
9
- end
10
- end
11
- end
@@ -1,270 +0,0 @@
1
- # note: functionality to deal with default scopes (and the `unscoped` scope) has been added (and will be tested) for Rails 3
2
- # this default scope functionality is skipped for Rails 2, because Rails 2 provides no functional means of altering the default scope
3
- # in particular, setting the default scope in Rails 3 to ignore soft deleted records will work properly, whereas in Rails 2, it will
4
- # lead to a world of pain
5
-
6
- require File.expand_path(File.dirname(__FILE__) + "/test_helper")
7
-
8
- %w(earthworm dirt hole mole muskrat kitty location comment difficulty unused_model).each do |a|
9
- require File.expand_path(File.dirname(__FILE__) + "/" + a)
10
- end
11
-
12
- class PermanentRecordsTest < ActiveSupport::TestCase
13
-
14
- def setup
15
- super
16
- Muskrat.delete_all
17
- @active = Muskrat.create!(:name => 'Wakko')
18
- @deleted = Muskrat.create!(:name => 'Yakko', :deleted_at => 4.days.ago)
19
- @the_girl = Muskrat.create!(:name => 'Dot')
20
- Kitty.delete_all
21
- @kitty = Kitty.create!(:name => 'Meow Meow')
22
- @dirt = Dirt.create!(:color => 'Brown')
23
- @hole = Hole.create(:number => 14, :dirt => @dirt)
24
- @hole.muskrats.create(:name => "Active Muskrat")
25
- @hole.muskrats.create(:name => "Deleted Muskrat", :deleted_at => 5.days.ago)
26
- Location.delete_all
27
- @location = Location.create(:name => "South wall")
28
- @hole.location = @location
29
- @hole.save!
30
- @mole = @hole.moles.create(:name => "Grabowski")
31
-
32
- if ActiveRecord::VERSION::MAJOR >= 3
33
- Difficulty.unscoped.delete_all
34
- Comment.unscoped.delete_all
35
- else
36
- Difficulty.delete_all
37
- Comment.delete_all
38
- end
39
- # test has_one cardinality with model having a default scope
40
- @hole_with_difficulty = Hole.create(:number => 16)
41
- @hole_with_difficulty.difficulty = Difficulty.create!(:name => 'Hard')
42
- @hole_with_difficulty.save!
43
-
44
- # test has_many cardinality with model having a default scope
45
- @hole_with_comments = Hole.create(:number => 16)
46
- @hole_with_comments.comments << Comment.create!(:text => "Beware of the pond.")
47
- @hole_with_comments.comments << Comment.create!(:text => "Muskrats live here.")
48
- end
49
-
50
- def teardown
51
- setup
52
- end
53
-
54
- def test_destroy_should_return_the_record
55
- muskrat = @active
56
- assert_equal muskrat, muskrat.destroy
57
- end
58
-
59
- def test_revive_should_return_the_record
60
- muskrat = @deleted
61
- assert_equal muskrat, muskrat.revive
62
- end
63
-
64
- def test_destroy_should_set_deleted_at_attribute
65
- assert @active.destroy.deleted_at
66
- end
67
-
68
- def test_destroy_should_save_deleted_at_attribute
69
- assert Muskrat.find(@active.destroy.id).deleted_at
70
- end
71
-
72
- def test_destroy_should_not_really_remove_the_record
73
- assert Muskrat.find(@active.destroy.id)
74
- end
75
-
76
- def test_destroy_should_recognize_a_force_parameter
77
- assert_raises(ActiveRecord::RecordNotFound) { @active.destroy(:force).reload }
78
- end
79
-
80
- def test_destroy_should_ignore_other_parameters
81
- assert Muskrat.find(@active.destroy(:hula_dancer).id)
82
- end
83
-
84
- def test_revive_should_unfreeze_record
85
- assert !@deleted.revive.frozen?
86
- end
87
-
88
- def test_revive_should_unset_deleted_at
89
- assert !@deleted.revive.deleted_at
90
- end
91
-
92
- def test_revive_should_make_deleted_return_false
93
- assert !@deleted.revive.deleted?
94
- end
95
-
96
- def test_deleted_returns_true_for_deleted_records
97
- assert @deleted.deleted?
98
- end
99
-
100
- def test_destroy_returns_record_with_modified_attributes
101
- assert @active.destroy.deleted?
102
- end
103
-
104
- def test_revive_and_destroy_should_be_chainable
105
- assert @active.destroy.revive.destroy.destroy.revive.revive.destroy.deleted?
106
- assert !@deleted.destroy.revive.revive.destroy.destroy.revive.deleted?
107
- end
108
-
109
- def test_with_counting_on_deleted_limits_scope_to_count_deleted_records
110
- assert_equal Muskrat.deleted.length,
111
- Muskrat.deleted.count
112
- end
113
-
114
- def test_with_counting_on_not_deleted_limits_scope_to_count_not_deleted_records
115
- assert_equal Muskrat.not_deleted.length,
116
- Muskrat.not_deleted.count
117
- end
118
-
119
- def test_with_deleted_limits_scope_to_deleted_records
120
- assert Muskrat.deleted.all?(&:deleted?)
121
- end
122
-
123
- def test_with_not_deleted_limits_scope_to_not_deleted_records
124
- assert !Muskrat.not_deleted.any?(&:deleted?)
125
- end
126
-
127
- def test_models_without_a_deleted_at_column_should_destroy_as_normal
128
- assert_raises(ActiveRecord::RecordNotFound) {@kitty.destroy.reload}
129
- end
130
-
131
- def test_dependent_non_permanent_records_should_be_destroyed
132
- assert @hole.is_permanent?
133
- assert !@hole.moles.first.is_permanent?
134
- assert_difference "Mole.count", -1 do
135
- @hole.destroy
136
- end
137
- end
138
-
139
- def test_dependent_permanent_records_with_has_many_cardinality_should_be_marked_as_deleted
140
- assert @hole.is_permanent?
141
- assert @hole.muskrats.first.is_permanent?
142
- assert_no_difference "Muskrat.count" do
143
- @hole.destroy
144
- end
145
-
146
- @hole.reload
147
- assert @hole.muskrats.first.deleted?
148
- end
149
-
150
- def test_dependent_permanent_records_with_has_one_cardinality_should_be_marked_as_deleted
151
- assert @hole.is_permanent?
152
- assert @hole.location.is_permanent?
153
- assert_no_difference "Location.count" do
154
- @hole.destroy
155
- end
156
- assert @hole.location.deleted?
157
- assert Location.find_by_name("South wall").deleted?
158
- end
159
-
160
- def test_dependent_permanent_records_with_has_many_cardinality_should_be_revived_when_parent_is_revived
161
- assert @hole.is_permanent?
162
- @hole.destroy
163
- assert @dirt.deleted?
164
- assert @hole.muskrats.find_by_name("Active Muskrat").deleted?
165
- @hole.revive
166
- assert !@hole.muskrats.find_by_name("Active Muskrat").deleted?
167
- assert !@dirt.deleted?
168
- end
169
-
170
- def test_dependent_permanent_records_with_has_one_cardinality_should_be_revived_when_parent_is_revived
171
- assert @hole.is_permanent?
172
- @hole.destroy
173
- assert Location.find_by_name("South wall").deleted?
174
- @hole.revive
175
- assert !Location.find_by_name("South wall").deleted?
176
- end
177
-
178
- def test_before_callbacks_for_dependent_records_fire_before_destroy_occurs
179
- @earthworm = Earthworm.create(:dirt => @dirt)
180
-
181
- assert_nothing_raised do
182
- @hole.destroy
183
- end
184
- end
185
-
186
- # see comment at top of file for reasoning behind conditional testing of default scope
187
- if ActiveRecord::VERSION::MAJOR >= 3
188
- def test_dependent_permanent_records_with_has_one_cardinality_and_default_scope_should_be_revived_when_parent_is_revived
189
- assert @hole_with_difficulty.is_permanent?
190
- assert_difference("Difficulty.count", -1) do
191
- @hole_with_difficulty.destroy
192
- end
193
- assert_nil Difficulty.find_by_name("Hard")
194
- assert Difficulty.unscoped.find_by_name("Hard").deleted?
195
- @hole_with_difficulty.revive
196
- assert_not_nil Difficulty.find_by_name("Hard")
197
- assert !Difficulty.unscoped.find_by_name("Hard").deleted?
198
- end
199
-
200
- def test_dependent_permanent_records_with_has_many_cardinality_and_default_scope_should_be_revived_when_parent_is_revived
201
- assert @hole_with_comments.is_permanent?
202
- assert_difference("Comment.count", -2) do
203
- @hole_with_comments.destroy
204
- end
205
- assert_nil Comment.find_by_text("Beware of the pond.")
206
- assert Comment.unscoped.find_by_text("Beware of the pond.").deleted?
207
- @hole_with_comments.revive
208
- assert_not_nil Comment.find_by_text("Beware of the pond.")
209
- assert !Comment.unscoped.find_by_text("Beware of the pond.").deleted?
210
- end
211
- end
212
-
213
- def test_inexistent_dependent_models_should_not_cause_errors
214
- hole_with_unused_model = Hole.create!(:number => 1)
215
- hole_with_unused_model.destroy
216
- assert_nothing_raised do
217
- hole_with_unused_model.revive
218
- end
219
- end
220
-
221
- def test_old_dependent_permanent_records_should_not_be_revived
222
- assert @hole.is_permanent?
223
- @hole.destroy
224
- assert @hole.muskrats.find_by_name("Deleted Muskrat").deleted?
225
- @hole.revive
226
- assert @hole.muskrats.find_by_name("Deleted Muskrat").deleted?
227
- end
228
-
229
- def test_validate_records_before_revival
230
- duplicate_location = Location.new(@location.attributes)
231
- @location.destroy
232
- @location.reload
233
- duplicate_location.save!
234
- assert_equal duplicate_location.name, @location.name
235
- assert_no_difference('Location.not_deleted.count') do
236
- assert_raise (ActiveRecord::RecordInvalid) do
237
- @location.revive
238
- end
239
- end
240
- end
241
-
242
- def test_force_deleting_a_record_with_has_one_force_deletes_dependent_records
243
- hole = Hole.create(:number => 1)
244
- location = Location.create(:name => "Near the clubhouse")
245
- hole.location = location
246
- hole.save!
247
-
248
- assert_difference(monitor_for('Hole'), -1) do
249
- assert_difference(monitor_for('Location'), -1) do
250
- hole.destroy(:force)
251
- end
252
- end
253
- end
254
-
255
- def test_force_deleting_a_record_with_has_many_force_deletes_dependent_records
256
- assert_difference(monitor_for('Hole'), -1) do
257
- assert_difference(monitor_for('Comment'), -2) do
258
- @hole_with_comments.destroy(:force)
259
- end
260
- end
261
- end
262
-
263
- def test_force_deletign_with_multiple_associations
264
- assert_difference(monitor_for('Muskrat'), -2) do
265
- assert_difference(monitor_for('Mole'), -1) do
266
- @hole.destroy(:force)
267
- end
268
- end
269
- end
270
- end
data/test/test_helper.rb DELETED
@@ -1,46 +0,0 @@
1
- # Include this file in your test by copying the following line to your test:
2
- # require File.expand_path(File.dirname(__FILE__) + "/test_helper")
3
-
4
- $:.unshift(File.dirname(__FILE__) + '/../lib')
5
- RAILS_ROOT = File.dirname(__FILE__)
6
-
7
- require 'rubygems'
8
- require 'test/unit'
9
- require 'active_record'
10
- require 'active_record/fixtures'
11
- require File.expand_path(File.dirname(__FILE__) + '/../lib/permanent_records')
12
-
13
- require File.expand_path(File.dirname(__FILE__) + "/muskrat")
14
-
15
- config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
16
- ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
17
- ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
18
-
19
- load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
20
-
21
- class ActiveSupport::TestCase #:nodoc:
22
- include ActiveRecord::TestFixtures
23
- # def create_fixtures(*table_names)
24
- # if block_given?
25
- # Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
26
- # else
27
- # Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
28
- # end
29
- # end
30
-
31
- self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
32
- $LOAD_PATH.unshift(fixture_path)
33
-
34
- # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
35
- self.use_transactional_fixtures = true
36
-
37
- # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
38
- self.use_instantiated_fixtures = false
39
-
40
- # Add more helper methods to be used by all tests here...
41
- def monitor_for(class_name)
42
- result = class_name
43
- result += '.unscoped' if ActiveRecord::VERSION::MAJOR >= 3
44
- result += '.count'
45
- end
46
- end