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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8810e1d51d69ba3de04b06ec9f372aca86611a0ac555b0d0652284680b35d37a
4
- data.tar.gz: d6d8d480ffac223059af1942c0161ab7eff86b6af5320852838a2e5c0c9e3611
3
+ metadata.gz: 603d571510be78d62d3d71f071b703dbf70bf505e6463e1766e4a99c4c039012
4
+ data.tar.gz: eefa6a75c74a0ff0db01e129cc85c73af7ae3b16fb38c2ceab757ff5fc1e2656
5
5
  SHA512:
6
- metadata.gz: fdc35dc31371b5bef4f0897494250778dcb794598136c21db6d33db5b165a0fc95151c2939ee610312249e3a6012738e0041673ce592846515eaa38b0a8dd453
7
- data.tar.gz: b9704d4368ba22504f7b2bf6081832fd2e4a6014f74ea9a88abc73ab55dc0e4af07cc89d09cff6442d59357fa0907503eb19900e4165ef241e5183527e1b512f
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
- soft_deletable_by :deleted_at
130
+ # Optional: customize field and touch behavior
131
+ soft_deletable_by :deleted_at, touch: true
131
132
  end
133
+ ```
132
134
 
133
- user = User.create!(name: "Alice")
134
- user.soft_delete!
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? # => false
150
+ user.restore! # Restore the user (sets deleted_at to nil)
151
+ user.deleted? # => false
143
152
  ```
144
153
 
145
- Additional features:
146
- - Default field is `deleted_at`, can be configured
147
- - Automatically applies `default_scope` to hide soft-deleted records
148
- - Scopes: `without_deleted`, `soft_deleted`, `active`
149
- - Methods: `soft_delete!`, `restore!`, `deleted?`, `really_delete!`
150
- - Callbacks: `before_soft_delete`, `after_soft_delete`, `before_restore`, `after_restore`
151
- - Touch support when soft deleting or restoring (can be turned off)
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
- # declare class attributes and set default values
10
- class_attribute :publishable_field, instance_accessor: false
11
- self.publishable_field ||= :published_at
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].present?
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
- if self.class.sluggable_field.present? && respond_to?(self.class.sluggable_field)
55
- send(self.class.sluggable_field)
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
- run_callbacks(:soft_delete) do
48
- before_soft_delete
49
- if self.class.soft_delete_touch
50
- update(self.class.soft_delete_field => Time.zone.now).tap do |result|
51
- touch if respond_to?(:touch)
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
- run_callbacks(:restore) do
69
- before_restore
70
- if self.class.soft_delete_touch
71
- update(self.class.soft_delete_field => nil).tap do |result|
72
- touch if respond_to?(:touch)
73
- after_restore if result
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 { order(sortable_field => sortable_direction) }
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
@@ -1,3 +1,3 @@
1
1
  module ConcernsOnRails
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
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.1.0
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: 2025-04-16 00:00:00.000000000 Z
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.1.6
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