permanent_records 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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