Skip to content

Commit c559f3f

Browse files
committed
Replace core functionality with ActiveRecord::Frames
1 parent afabc52 commit c559f3f

13 files changed

Lines changed: 120 additions & 115 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,9 @@ class IndexDeletedAtColumns < ActiveRecord::Migration
102102
end
103103
```
104104

105-
106105
## [Upgrading](#upgrading)
107106

108-
If you've used `deleted_at` prior to v0.5.0, you'll need to migrate your schema. The new version of `deleted_at` no longer uses views, instead constructing a subselect on the relations. This significantly reduces code polution and monkey patching, as well as reducing the runtime memory usage for rails. Your Database will look (and be) a lot cleaner with no `deleted_at` views and your ERDs will be much cleaner as well.
107+
If you've used `deleted_at` prior to v0.5.0, you'll need to migrate your schema. The new version of `deleted_at` no longer uses views, instead constructing a common table expression (CTE) on the relations. This significantly reduces code polution and monkey patching, as well as reducing the runtime memory usage for rails. Your Database will look (and be) a lot cleaner with no `deleted_at` views (and your ERDs will be much cleaner as well).
109108

110109
Here is an example of a migration for upgrading
111110
```ruby

deleted_at.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
2828
spec.required_ruby_version = '>= 2.3'
2929

3030
spec.add_runtime_dependency 'activerecord', rails_versions
31+
spec.add_runtime_dependency 'active_record-framing', '~> 0.1.0-6'
32+
3133
spec.add_development_dependency 'pg'
3234
spec.add_development_dependency 'bundler'
3335
spec.add_development_dependency 'rspec'

lib/deleted_at.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
require 'active_record/framing'
2+
13
require 'deleted_at/version'
4+
require 'deleted_at/relation'
5+
require 'deleted_at/core'
6+
require 'deleted_at/table_definition'
27
require 'deleted_at/railtie' if defined?(Rails::Railtie)
38

49
module DeletedAt
510

6-
MissingColumn = Class.new(StandardError)
11+
MissingColumnError = Class.new(StandardError)
712

813
DEFAULT_OPTIONS = {
914
column: :deleted_at,

lib/deleted_at/active_record.rb

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,73 @@
1-
require 'active_record'
2-
require 'deleted_at/relation'
3-
41
module DeletedAt
52
module ActiveRecord
63

74
def self.prepended(subclass)
8-
subclass.init_deleted_at_relations
95
subclass.extend(ClassMethods)
6+
7+
subclass.class_eval do
8+
init_deleted_at_relations
9+
default_frame { where(deleted_at[:column] => nil) }
10+
frame :all, -> {}
11+
frame :deleted, -> { where.not(deleted_at[:column] => nil) }
12+
end
1013
end
1114

1215
def initialize(*args)
13-
super
14-
@destroyed = !deleted_at.nil?
16+
super.tap do
17+
@destroyed = deleted_at_nil?
18+
end
1519
end
1620

1721
def destroy
18-
soft_delete
19-
super
22+
run_callbacks(:destroy) do
23+
soft_delete
24+
end
2025
end
2126

2227
def delete
2328
soft_delete
24-
super
29+
end
30+
31+
def destroy!
32+
run_callbacks(:destroy) do
33+
soft_delete
34+
end
35+
end
36+
37+
def delete!
38+
soft_delete
2539
end
2640

2741
private
2842

2943
def soft_delete
44+
return if destroyed?
3045
update_columns(self.class.deleted_at_attributes)
3146
@destroyed = true
47+
freeze
48+
self
49+
end
50+
51+
def deleted_at_nil?
52+
!read_attribute(self.class.deleted_at[:column]).nil?
3253
end
3354

3455
module ClassMethods
3556

3657
def inherited(subclass)
3758
super
38-
subclass.init_deleted_at_relations
59+
# subclass.init_deleted_at_relations if deleted_at[:inherit]
60+
end
61+
62+
def deleted_at_attributes
63+
attributes = {
64+
deleted_at[:column] => deleted_at[:proc].call
65+
}
3966
end
4067

41-
def const_missing(const)
42-
case const
43-
when :All, :Deleted
44-
all.tap do |_query|
45-
_query.deleted_at_scope = const
46-
end
47-
else super
68+
def init_deleted_at_relations
69+
instance_variable_get(:@relation_delegate_cache).each do |base, klass|
70+
klass.send(:prepend, DeletedAt::Relation)
4871
end
4972
end
5073

lib/deleted_at/core.rb

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require 'deleted_at/active_record'
2-
32
module DeletedAt
43

54
module Core
@@ -15,39 +14,33 @@ class << subclass
1514

1615
def self.raise_missing(klass)
1716
message = "Missing `#{klass.deleted_at[:column]}` in `#{klass.name}` when trying to employ `deleted_at`"
18-
raise(DeletedAt::MissingColumn, message)
17+
raise(DeletedAt::MissingColumnError, message)
1918
end
2019

2120
def self.has_deleted_at_column?(klass)
2221
klass.columns.map(&:name).include?(klass.deleted_at.dig(:column).to_s)
2322
end
2423

24+
def self.deleted_at_ready?(klass)
25+
!::DeletedAt.disabled? &&
26+
klass != ::ActiveRecord::Base &&
27+
!klass.abstract_class? &&
28+
klass.connected? &&
29+
klass.table_exists? &&
30+
!(klass < DeletedAt::ActiveRecord)
31+
end
32+
2533
module ClassMethods
2634

2735
def with_deleted_at(options={}, &block)
2836
self.deleted_at = DeletedAt::DEFAULT_OPTIONS.merge(options)
2937
self.deleted_at[:proc] = block if block_given?
3038

31-
return if ::DeletedAt.disabled?
32-
39+
return unless Core.deleted_at_ready?(self)
3340
DeletedAt::Core.raise_missing(self) unless Core.has_deleted_at_column?(self)
3441

3542
self.prepend(DeletedAt::ActiveRecord)
36-
3743
end
38-
39-
def deleted_at_attributes
40-
attributes = {
41-
deleted_at[:column] => deleted_at[:proc].call
42-
}
43-
end
44-
45-
def init_deleted_at_relations
46-
instance_variable_get(:@relation_delegate_cache).each do |base, klass|
47-
klass.send(:prepend, DeletedAt::Relation)
48-
end
49-
end
50-
5144
end # End ClassMethods
5245

5346
end

lib/deleted_at/railtie.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
require 'rails/railtie'
2-
require 'deleted_at/core'
3-
require 'deleted_at/table_definition'
42

53
module DeletedAt
64
class Railtie < Rails::Railtie

lib/deleted_at/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module DeletedAt
2-
VERSION = "0.6.0-2"
2+
VERSION = "0.6.0-4"
33
end

spec/deleted_at/active_record_spec.rb

Lines changed: 0 additions & 18 deletions
This file was deleted.

spec/deleted_at/core_spec.rb

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
it "raises exception when using with_deleted_at" do
88
expected_stderr = "Missing `deleted_at` in `Comment` when trying to employ `deleted_at`"
99
allow(Comment).to receive(:has_deleted_at_views?).and_return(true)
10-
expect{ Comment.with_deleted_at }.to raise_exception(DeletedAt::MissingColumn)
10+
expect{ Comment.with_deleted_at }.to raise_exception(DeletedAt::MissingColumnError)
1111
end
1212

1313
end
@@ -25,11 +25,21 @@
2525
User.create(name: 'john')
2626
User.create(name: 'sally')
2727

28-
User.first.destroy
28+
u = User.first
29+
u.destroy
2930

3031
expect(User::Deleted.first.class).to eq(User)
3132
end
3233

34+
it "doesn't obstruct destroy callbacks" do
35+
User.create(name: 'sally')
36+
37+
u = User.first
38+
expect_any_instance_of(User).to receive(:say_something)
39+
40+
u.destroy
41+
end
42+
3343
it 'works with complex eager loading' do
3444
bob = User.create(name: 'bob')
3545
(1..5).each do |i|
@@ -42,19 +52,4 @@
4252
expect(User.eager_load(posts: :comments).find_by(name: 'bob')).to eq(bob)
4353
end
4454

45-
context 'with default_scope' do
46-
it 'should have the default scope in the subquery' do
47-
Admin.create(name: 'bob', kind: 1)
48-
Admin.create(name: 'john', kind: 1)
49-
Admin.create(name: 'sally', kind: 0)
50-
51-
Admin.first.destroy
52-
53-
User.first
54-
55-
# SELECT "users".* FROM (SELECT "users".* FROM "users" WHERE "users"."kind" = $1 AND "users"."deleted_at" IS NULL) "users" WHERE "users"."kind" = 1
56-
expect(Admin::Deleted.first.class).to eq(Admin)
57-
end
58-
end
59-
6055
end

spec/deleted_at/relation_spec.rb

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
describe DeletedAt::Core do
44

5+
context 'models with dependent: :destroy' do
6+
it 'should also destroy dependents' do
7+
user = User.create(name: 'bob')
8+
9+
4.times do
10+
Post.create(user: user)
11+
end
12+
13+
expect{user.destroy}.to_not raise_exception
14+
end
15+
end
16+
517
context "models using deleted_at" do
618

719
it "#destroy should set deleted_at" do
@@ -28,21 +40,18 @@
2840
expect(User::Deleted.count).to eq(1)
2941
end
3042

31-
context 'associations' do
32-
33-
it 'should scope properly' do
34-
35-
user = User.create(name: 'bob')
36-
(1..4).each do
37-
Post.create(user: user)
38-
end
39-
40-
post = user.posts.first.delete
41-
42-
expect(user.posts.count).to eq(3)
43+
it "#destroy twice should set deleted_at and not fail" do
44+
User.create(name: 'bob')
45+
User.create(name: 'john')
46+
User.create(name: 'sally')
4347

44-
end
48+
u = User.first
49+
u.destroy!
50+
u.destroy!
4551

52+
expect(User.count).to eq(2)
53+
expect(User::All.count).to eq(3)
54+
expect(User::Deleted.count).to eq(1)
4655
end
4756

4857
context '#destroy_all' do
@@ -65,6 +74,7 @@
6574

6675
User.where(name: 'bob').destroy_all
6776

77+
6878
expect(User.count).to eq(2)
6979
expect(User::All.count).to eq(3)
7080
expect(User::Deleted.count).to eq(1)
@@ -73,28 +83,28 @@
7383

7484
context '#delete_all' do
7585
it "should set deleted_at" do
76-
Animals::Dog.create(name: 'bob')
77-
Animals::Dog.create(name: 'john')
78-
Animals::Dog.create(name: 'sally')
86+
User.create(name: 'bob')
87+
User.create(name: 'john')
88+
User.create(name: 'sally')
7989

8090
# conditions should not matter
81-
Animals::Dog.all.delete_all(name: 'bob')
91+
User.all.delete_all(name: 'bob')
8292

83-
expect(Animals::Dog.count).to eq(0)
84-
expect(Animals::Dog::All.count).to eq(3)
85-
expect(Animals::Dog::Deleted.count).to eq(3)
93+
expect(User.count).to eq(0)
94+
expect(User::All.count).to eq(3)
95+
expect(User::Deleted.count).to eq(3)
8696
end
8797

8898
it "with conditions should set deleted_at" do
89-
Animals::Dog.create(name: 'bob')
90-
Animals::Dog.create(name: 'john')
91-
Animals::Dog.create(name: 'sally')
99+
User.create(name: 'bob')
100+
User.create(name: 'john')
101+
User.create(name: 'sally')
92102

93-
Animals::Dog.where(name: 'bob').delete_all
103+
User.where(name: 'bob').delete_all
94104

95-
expect(Animals::Dog.count).to eq(2)
96-
expect(Animals::Dog::All.count).to eq(3)
97-
expect(Animals::Dog::Deleted.count).to eq(1)
105+
expect(User.count).to eq(2)
106+
expect(User::All.count).to eq(3)
107+
expect(User::Deleted.count).to eq(1)
98108
end
99109
end
100110

0 commit comments

Comments
 (0)