concerns_on_rails 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/README.md +55 -14
- data/lib/concerns_on_rails/publishable.rb +9 -13
- data/lib/concerns_on_rails/sluggable.rb +2 -7
- data/lib/concerns_on_rails/soft_deletable.rb +32 -35
- data/lib/concerns_on_rails/sortable.rb +6 -6
- data/lib/concerns_on_rails/version.rb +1 -1
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 603d571510be78d62d3d71f071b703dbf70bf505e6463e1766e4a99c4c039012
|
|
4
|
+
data.tar.gz: eefa6a75c74a0ff0db01e129cc85c73af7ae3b16fb38c2ceab757ff5fc1e2656
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 01b37a0a698df95131be947ebc0874c970dc1f4cadf591aca2de84b1bc4da8075967cc5d469a45713165d07c763e55c7296b34a621248bffa82e7af127c5b5b0
|
|
7
|
+
data.tar.gz: e0978b66b4208ef653563602e9b153f10f56867cd2ad821ae8323f12fad8eff9c917127f64ae567a0ffa62945b562c5244aed72e743419e47a22a1de8f121ad1
|
data/README.md
CHANGED
|
@@ -127,29 +127,70 @@ Soft delete records using a timestamp field (default: `deleted_at`).
|
|
|
127
127
|
class User < ApplicationRecord
|
|
128
128
|
include ConcernsOnRails::SoftDeletable
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
# Optional: customize field and touch behavior
|
|
131
|
+
soft_deletable_by :deleted_at, touch: true
|
|
131
132
|
end
|
|
133
|
+
```
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
user.deleted? # => true
|
|
136
|
-
|
|
135
|
+
#### Scopes
|
|
136
|
+
```ruby
|
|
137
137
|
User.without_deleted # => returns only active users
|
|
138
138
|
User.soft_deleted # => returns soft-deleted users
|
|
139
|
+
User.active # => same as without_deleted
|
|
139
140
|
User.all # => returns only non-deleted by default (default_scope applied)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Soft delete and restore
|
|
144
|
+
```ruby
|
|
145
|
+
user.soft_delete! # Soft delete the user (sets deleted_at)
|
|
146
|
+
user.deleted? # => true
|
|
147
|
+
user.soft_deleted? # => true (alias)
|
|
148
|
+
user.is_soft_deleted? # => true (alias)
|
|
140
149
|
|
|
141
|
-
user.restore!
|
|
142
|
-
user.deleted?
|
|
150
|
+
user.restore! # Restore the user (sets deleted_at to nil)
|
|
151
|
+
user.deleted? # => false
|
|
143
152
|
```
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
#### Permanently delete
|
|
155
|
+
```ruby
|
|
156
|
+
user.really_delete! # Hard delete the record from DB
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Soft delete/hard delete all records
|
|
160
|
+
```ruby
|
|
161
|
+
User.destroy_all # Soft delete all users (sets deleted_at)
|
|
162
|
+
User.really_destroy_all # Hard delete ALL users (removes from DB)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Callbacks (Hooks)
|
|
166
|
+
You can use the following hooks to run logic before/after soft delete or restore:
|
|
167
|
+
```ruby
|
|
168
|
+
class User < ApplicationRecord
|
|
169
|
+
include ConcernsOnRails::SoftDeletable
|
|
170
|
+
|
|
171
|
+
def before_soft_delete
|
|
172
|
+
# Code to run before soft delete
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def after_soft_delete
|
|
176
|
+
# Code to run after soft delete
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def before_restore
|
|
180
|
+
# Code to run before restore
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def after_restore
|
|
184
|
+
# Code to run after restore
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Notes
|
|
190
|
+
- Default field is `deleted_at`, can be changed with `soft_deletable_by :your_field`
|
|
191
|
+
- `touch: false` to skip updating updated_at when soft deleting/restoring
|
|
152
192
|
- Aliases for `deleted?`: `soft_deleted?`, `is_soft_deleted?`
|
|
193
|
+
- All scopes and methods work seamlessly with ActiveRecord
|
|
153
194
|
|
|
154
195
|
---
|
|
155
196
|
|
|
@@ -4,28 +4,22 @@ module ConcernsOnRails
|
|
|
4
4
|
module Publishable
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
6
|
|
|
7
|
-
# instance methods
|
|
8
7
|
included do
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
class_attribute :publishable_field, instance_accessor: false, default: :published_at
|
|
9
|
+
|
|
10
|
+
scope :published, -> { where(arel_table[publishable_field].lteq(Time.zone.now)) }
|
|
11
|
+
scope :unpublished, -> {
|
|
12
|
+
where(arel_table[publishable_field].eq(nil).or(arel_table[publishable_field].gt(Time.zone.now)))
|
|
13
|
+
}
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
# class methods
|
|
15
16
|
class_methods do
|
|
16
|
-
# Define publishable field
|
|
17
|
-
# Example:
|
|
18
|
-
# publishable_by :published_at
|
|
19
17
|
def publishable_by(field = nil)
|
|
20
18
|
self.publishable_field = field || :published_at
|
|
21
19
|
|
|
22
|
-
# validate publishable_field exists in database
|
|
23
20
|
unless column_names.include?(publishable_field.to_s)
|
|
24
21
|
raise ArgumentError, "ConcernsOnRails::Publishable: publishable_field '#{publishable_field}' does not exist in the database"
|
|
25
22
|
end
|
|
26
|
-
|
|
27
|
-
scope :published, -> { where(arel_table[publishable_field].not_eq(nil)) }
|
|
28
|
-
scope :unpublished, -> { where(arel_table[publishable_field].eq(nil)) }
|
|
29
23
|
end
|
|
30
24
|
end
|
|
31
25
|
|
|
@@ -48,7 +42,9 @@ module ConcernsOnRails
|
|
|
48
42
|
# Example:
|
|
49
43
|
# record.published?
|
|
50
44
|
def published?
|
|
51
|
-
self[self.class.publishable_field]
|
|
45
|
+
value = self[self.class.publishable_field]
|
|
46
|
+
return false unless value.present?
|
|
47
|
+
value.respond_to?(:<=) ? value <= Time.zone.now : true
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
# Check if the record is unpublished
|
|
@@ -51,13 +51,8 @@ module ConcernsOnRails
|
|
|
51
51
|
# Example:
|
|
52
52
|
# record.slug_source
|
|
53
53
|
def slug_source
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
elsif respond_to?(:title)
|
|
57
|
-
title
|
|
58
|
-
else
|
|
59
|
-
to_s
|
|
60
|
-
end
|
|
54
|
+
field = self.class.sluggable_field
|
|
55
|
+
respond_to?(field) ? send(field) : to_s
|
|
61
56
|
end
|
|
62
57
|
end
|
|
63
58
|
end
|
|
@@ -16,9 +16,6 @@ module ConcernsOnRails
|
|
|
16
16
|
# Optionally, uncomment to hide deleted by default:
|
|
17
17
|
default_scope { without_deleted }
|
|
18
18
|
|
|
19
|
-
# define callbacks
|
|
20
|
-
define_model_callbacks :soft_delete
|
|
21
|
-
define_model_callbacks :restore
|
|
22
19
|
end
|
|
23
20
|
|
|
24
21
|
class_methods do
|
|
@@ -33,6 +30,16 @@ module ConcernsOnRails
|
|
|
33
30
|
raise ArgumentError, "ConcernsOnRails::SoftDeletable: soft_delete_field '#{soft_delete_field}' does not exist in the database"
|
|
34
31
|
end
|
|
35
32
|
end
|
|
33
|
+
|
|
34
|
+
# Override destroy_all to perform soft delete on all records
|
|
35
|
+
def destroy_all
|
|
36
|
+
all.each(&:soft_delete!)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Provide really_destroy_all to hard delete all records
|
|
40
|
+
def really_destroy_all
|
|
41
|
+
unscoped.delete_all
|
|
42
|
+
end
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
# Soft delete hooks
|
|
@@ -41,43 +48,34 @@ module ConcernsOnRails
|
|
|
41
48
|
def before_restore; end
|
|
42
49
|
def after_restore; end
|
|
43
50
|
|
|
44
|
-
# add soft delete methods
|
|
45
51
|
def soft_delete!
|
|
46
52
|
return true if deleted?
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
after_soft_delete if result
|
|
53
|
-
end
|
|
54
|
-
else
|
|
55
|
-
update_column(self.class.soft_delete_field, Time.zone.now).tap do |result|
|
|
56
|
-
after_soft_delete if result
|
|
57
|
-
end
|
|
58
|
-
end
|
|
53
|
+
before_soft_delete
|
|
54
|
+
result = if self.class.soft_delete_touch
|
|
55
|
+
update(self.class.soft_delete_field => Time.zone.now)
|
|
56
|
+
else
|
|
57
|
+
update_column(self.class.soft_delete_field, Time.zone.now)
|
|
59
58
|
end
|
|
59
|
+
after_soft_delete if result
|
|
60
|
+
result
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
# really delete the record
|
|
63
|
-
def really_delete!
|
|
64
|
-
destroy
|
|
65
|
-
end
|
|
66
|
-
|
|
67
63
|
def restore!
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
end
|
|
75
|
-
else
|
|
76
|
-
update_column(self.class.soft_delete_field, nil).tap do |result|
|
|
77
|
-
after_restore if result
|
|
78
|
-
end
|
|
79
|
-
end
|
|
64
|
+
return true unless deleted?
|
|
65
|
+
before_restore
|
|
66
|
+
result = if self.class.soft_delete_touch
|
|
67
|
+
update(self.class.soft_delete_field => nil)
|
|
68
|
+
else
|
|
69
|
+
update_column(self.class.soft_delete_field, nil)
|
|
80
70
|
end
|
|
71
|
+
after_restore if result
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# bypasses AR callbacks and validations — use when you want a true hard delete
|
|
76
|
+
def really_delete!
|
|
77
|
+
self.class.unscoped.where(self.class.primary_key => id).delete_all
|
|
78
|
+
freeze
|
|
81
79
|
end
|
|
82
80
|
|
|
83
81
|
def deleted?
|
|
@@ -89,9 +87,8 @@ module ConcernsOnRails
|
|
|
89
87
|
alias_method :is_soft_deleted?, :deleted?
|
|
90
88
|
alias_method :soft_deleted?, :deleted?
|
|
91
89
|
|
|
92
|
-
# Is really deleted?
|
|
93
90
|
def is_really_deleted?
|
|
94
|
-
!self.class.exists?(id)
|
|
91
|
+
!self.class.unscoped.exists?(id)
|
|
95
92
|
end
|
|
96
93
|
end
|
|
97
94
|
end
|
|
@@ -22,7 +22,12 @@ module ConcernsOnRails
|
|
|
22
22
|
self.sortable_direction ||= :asc
|
|
23
23
|
|
|
24
24
|
# we cannot use acts_as_list here
|
|
25
|
-
default_scope
|
|
25
|
+
default_scope do
|
|
26
|
+
unless column_names.include?(sortable_field.to_s)
|
|
27
|
+
raise ArgumentError, "#{name}: '#{sortable_field}' column not found. Call `sortable_by :your_column` to configure the sort field."
|
|
28
|
+
end
|
|
29
|
+
order(sortable_field => sortable_direction)
|
|
30
|
+
end
|
|
26
31
|
end
|
|
27
32
|
|
|
28
33
|
# class methods
|
|
@@ -48,12 +53,7 @@ module ConcernsOnRails
|
|
|
48
53
|
|
|
49
54
|
validate_sortable_field!
|
|
50
55
|
|
|
51
|
-
# add acts_as_list and default scope
|
|
52
|
-
# Setup sorting behaviors
|
|
53
56
|
acts_as_list column: sortable_field if use_acts_as_list
|
|
54
|
-
|
|
55
|
-
# add default scope: position => asc
|
|
56
|
-
default_scope { order(sortable_field => sortable_direction) }
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
private
|
metadata
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: concerns_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ethan Nguyen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-05-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '5.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '5.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: acts_as_list
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
91
97
|
- !ruby/object:Gem::Version
|
|
92
98
|
version: '0'
|
|
93
99
|
requirements: []
|
|
94
|
-
rubygems_version: 3.
|
|
100
|
+
rubygems_version: 3.4.19
|
|
95
101
|
signing_key:
|
|
96
102
|
specification_version: 4
|
|
97
103
|
summary: Reusable Rails concerns like Sortable, Publishable, and Sluggable
|