concerns_on_rails 1.0.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: '0679c4f78651169ae85fbf3f27d0a301d6ca33c148eb3e01c766252145c5be4c'
4
- data.tar.gz: cec6963e3ba583e9072b03de751adbd1362dc100b041d5addf8cb37ebacd1d41
3
+ metadata.gz: 603d571510be78d62d3d71f071b703dbf70bf505e6463e1766e4a99c4c039012
4
+ data.tar.gz: eefa6a75c74a0ff0db01e129cc85c73af7ae3b16fb38c2ceab757ff5fc1e2656
5
5
  SHA512:
6
- metadata.gz: c2ffaa1b86bd6c7952857c88e326009a85699bb77364e0b42e6cd8bfa11614917c3cf2fbae94af08bf2262210968878b65c37b22856bb8bac95345d81533fa57
7
- data.tar.gz: e387756b366d70efff292dd40b1a8cf34dfeb04321140c2e4e03833c754975afb1686df499fd6ddefc4e83e7c7347851e62c1bc2a8f7d1f9e51d0c7f3cae50ca
6
+ metadata.gz: 01b37a0a698df95131be947ebc0874c970dc1f4cadf591aca2de84b1bc4da8075967cc5d469a45713165d07c763e55c7296b34a621248bffa82e7af127c5b5b0
7
+ data.tar.gz: e0978b66b4208ef653563602e9b153f10f56867cd2ad821ae8323f12fad8eff9c917127f64ae567a0ffa62945b562c5244aed72e743419e47a22a1de8f121ad1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  <!-- CHANGELOG.md -->
2
2
 
3
+ ## 1.1.0 (2025-04-17)
4
+
5
+ ### Added
6
+ - SoftDeletable: Add soft delete concern with configurable field, scopes, callbacks, and default_scope support
7
+
3
8
  ## 1.0.0 (2025-04-12)
4
9
 
5
10
  ### Added
data/README.md CHANGED
@@ -1,18 +1,19 @@
1
- # ConcernsOnRails
1
+ # ๐Ÿงฉ ConcernsOnRails
2
2
 
3
- **Note: Hoร ng Sa and Trฦฐแปng Sa belong to Viแป‡t Nam.** ๐Ÿ‡ป๐Ÿ‡ณ
3
+ **๐Ÿ‡ป๐Ÿ‡ณ Note: Hoร ng Sa and Trฦฐแปng Sa belong to Viแป‡t Nam.**
4
4
 
5
5
  A simple collection of reusable Rails concerns to keep your models clean and DRY.
6
6
 
7
- ## Features
7
+ ## โœจ Features
8
8
 
9
9
  - โœ… `Sluggable`: Generate friendly slugs from a specified field
10
- - โœ… `Sortable`: Sort records based on a field using `acts_as_list`, with flexible sorting field and direction
11
- - โœ… `Publishable`: Easily manage published/unpublished records using a simple `published_at` field
10
+ - ๐Ÿ”ข `Sortable`: Sort records based on a field using `acts_as_list`, with flexible sorting field and direction
11
+ - ๐Ÿ“ค `Publishable`: Easily manage published/unpublished records using a simple `published_at` field
12
+ - โŒ `SoftDeletable`: Soft delete records using a configurable timestamp field (e.g., `deleted_at`) with automatic scoping
12
13
 
13
14
  ---
14
15
 
15
- ## Installation
16
+ ## ๐Ÿ“ฆ Installation
16
17
 
17
18
  Add this line to your application's Gemfile:
18
19
 
@@ -28,9 +29,9 @@ bundle install
28
29
 
29
30
  ---
30
31
 
31
- ## Usage
32
+ ## ๐Ÿš€ Usage
32
33
 
33
- ### 1. Sluggable
34
+ ### 1. ๐Ÿ“ Sluggable
34
35
 
35
36
  Add slugs based on a specified attribute.
36
37
 
@@ -49,7 +50,7 @@ If the slug source is changed, the slug will auto-update.
49
50
 
50
51
  ---
51
52
 
52
- ### 2. Sortable
53
+ ### 2. ๐Ÿ”ข Sortable
53
54
 
54
55
  Use for models that need ordering.
55
56
 
@@ -76,16 +77,16 @@ end
76
77
  ```
77
78
 
78
79
  Additional features:
79
- - Automatically sets `acts_as_list` on the configured column
80
- - Adds default sorting scope to your model
81
- - Supports custom direction: `:asc` or `:desc`
82
- - Validates that the sortable field exists in the table schema
83
- - Compatible with scopes and ActiveRecord queries
84
- - Can be reconfigured dynamically within the model using `sortable_by`
80
+ - ๐Ÿ“Œ Automatically sets `acts_as_list` on the configured column
81
+ - ๐Ÿ“‹ Adds default sorting scope to your model
82
+ - โ†•๏ธ Supports custom direction: `:asc` or `:desc`
83
+ - ๐Ÿ” Validates that the sortable field exists in the table schema
84
+ - ๐Ÿง  Compatible with scopes and ActiveRecord queries
85
+ - ๐Ÿ”„ Can be reconfigured dynamically within the model using `sortable_by`
85
86
 
86
87
  ---
87
88
 
88
- ### 3. Publishable
89
+ ### 3. ๐Ÿ“ค Publishable
89
90
 
90
91
  Manage published/unpublished records using a `published_at` field.
91
92
 
@@ -108,17 +109,92 @@ article.published? # => false
108
109
  ```
109
110
 
110
111
  Additional features:
111
- - `published?` returns true if `published_at` is present and in the past
112
- - `publish!` sets `published_at` to current time
113
- - `unpublish!` sets `published_at` to `nil`
114
- - Add scopes: `.published`, `.unpublished`, and a default scope (optional)
115
- - Ideal for blog posts, articles, or any content that toggles visibility
116
- - Lightweight and non-invasive
117
- - Easy to test and override in custom implementations
112
+ - โœ… `published?` returns true if `published_at` is present and in the past
113
+ - ๐Ÿ•’ `publish!` sets `published_at` to current time
114
+ - ๐Ÿšซ `unpublish!` sets `published_at` to `nil`
115
+ - ๐Ÿ”Ž Add scopes: `.published`, `.unpublished`, and a default scope (optional)
116
+ - ๐Ÿ“ฐ Ideal for blog posts, articles, or any content that toggles visibility
117
+ - ๐Ÿงฉ Lightweight and non-invasive
118
+ - ๐Ÿงช Easy to test and override in custom implementations
118
119
 
119
120
  ---
120
121
 
121
- ## Development
122
+ ### 4. โŒ SoftDeletable
123
+
124
+ Soft delete records using a timestamp field (default: `deleted_at`).
125
+
126
+ ```ruby
127
+ class User < ApplicationRecord
128
+ include ConcernsOnRails::SoftDeletable
129
+
130
+ # Optional: customize field and touch behavior
131
+ soft_deletable_by :deleted_at, touch: true
132
+ end
133
+ ```
134
+
135
+ #### Scopes
136
+ ```ruby
137
+ User.without_deleted # => returns only active users
138
+ User.soft_deleted # => returns soft-deleted users
139
+ User.active # => same as without_deleted
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)
149
+
150
+ user.restore! # Restore the user (sets deleted_at to nil)
151
+ user.deleted? # => false
152
+ ```
153
+
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
192
+ - Aliases for `deleted?`: `soft_deleted?`, `is_soft_deleted?`
193
+ - All scopes and methods work seamlessly with ActiveRecord
194
+
195
+ ---
196
+
197
+ ## ๐Ÿ› ๏ธ Development
122
198
 
123
199
  To build the gem:
124
200
 
@@ -134,13 +210,13 @@ gem install ./concerns_on_rails-1.0.0.gem
134
210
 
135
211
  ---
136
212
 
137
- ## Contributing
213
+ ## ๐Ÿค Contributing
138
214
 
139
215
  Bug reports and pull requests are welcome!
140
216
 
141
217
  ---
142
218
 
143
- ## License
219
+ ## ๐Ÿ“„ License
144
220
 
145
221
  This project is licensed under the MIT License.
146
222
 
@@ -150,11 +226,11 @@ This project is licensed under the MIT License.
150
226
 
151
227
  ---
152
228
 
153
- ### ๐Ÿ“ฆ Source Code
229
+ ### ๐Ÿ”— Source Code
154
230
 
155
231
  The source code is available on GitHub:
156
232
 
157
- ๐Ÿ”— [https://github.com/VSN2015/concerns_on_rails](https://github.com/VSN2015/concerns_on_rails)
233
+ [๐Ÿ‘‰ https://github.com/VSN2015/concerns_on_rails](https://github.com/VSN2015/concerns_on_rails)
158
234
 
159
235
  Feel free to star โญ๏ธ, fork ๐Ÿด, or contribute with issues and PRs.
160
236
 
@@ -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
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
@@ -8,7 +8,7 @@ module ConcernsOnRails
8
8
  # instance methods
9
9
  included do
10
10
  # declare class attributes and set default values
11
- class_attribute :sluggable_field
11
+ class_attribute :sluggable_field, instance_accessor: false
12
12
  self.sluggable_field ||= :name
13
13
 
14
14
  extend FriendlyId
@@ -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
@@ -0,0 +1,100 @@
1
+ require "active_support/concern"
2
+
3
+ module ConcernsOnRails
4
+ module SoftDeletable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # declare class attributes and set default values
9
+ class_attribute :soft_delete_field, instance_accessor: false, default: :deleted_at
10
+ class_attribute :soft_delete_touch, instance_accessor: false, default: true
11
+
12
+ # scopes
13
+ scope :active, -> { unscope(where: soft_delete_field).where(soft_delete_field => nil) }
14
+ scope :without_deleted, -> { unscope(where: soft_delete_field).where(soft_delete_field => nil) }
15
+ scope :soft_deleted, -> { unscope(where: soft_delete_field).where.not(soft_delete_field => nil) }
16
+ # Optionally, uncomment to hide deleted by default:
17
+ default_scope { without_deleted }
18
+
19
+ end
20
+
21
+ class_methods do
22
+ # Define soft delete field and options
23
+ # Example:
24
+ # soft_deletable_by :deleted_at, touch: false
25
+ def soft_deletable_by(field = nil, touch: true)
26
+ self.soft_delete_field = field || :deleted_at
27
+ self.soft_delete_touch = touch
28
+
29
+ unless column_names.include?(soft_delete_field.to_s)
30
+ raise ArgumentError, "ConcernsOnRails::SoftDeletable: soft_delete_field '#{soft_delete_field}' does not exist in the database"
31
+ end
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
43
+ end
44
+
45
+ # Soft delete hooks
46
+ def before_soft_delete; end
47
+ def after_soft_delete; end
48
+ def before_restore; end
49
+ def after_restore; end
50
+
51
+ def soft_delete!
52
+ return true if deleted?
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)
58
+ end
59
+ after_soft_delete if result
60
+ result
61
+ end
62
+
63
+ def restore!
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)
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
79
+ end
80
+
81
+ def deleted?
82
+ self[self.class.soft_delete_field].present?
83
+ end
84
+
85
+ # alias methods
86
+ # define here to avoid issue: undefined method `deleted?' for module `ConcernsOnRails::SoftDeletable'
87
+ alias_method :is_soft_deleted?, :deleted?
88
+ alias_method :soft_deleted?, :deleted?
89
+
90
+ def is_really_deleted?
91
+ !self.class.unscoped.exists?(id)
92
+ end
93
+ end
94
+ end
95
+
96
+ # Usage Example:
97
+ # class MyModel < ApplicationRecord
98
+ # include ConcernsOnRails::SoftDeletable
99
+ # soft_deletable_by :deleted_at
100
+ # end
@@ -14,15 +14,20 @@ module ConcernsOnRails
14
14
  # end
15
15
  included do
16
16
  # declare class attributes
17
- class_attribute :sortable_field
18
- class_attribute :sortable_direction
17
+ class_attribute :sortable_field, instance_accessor: false
18
+ class_attribute :sortable_direction, instance_accessor: false
19
19
 
20
20
  # set default values
21
21
  self.sortable_field ||= :position
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.0.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -3,6 +3,7 @@ require "concerns_on_rails/version"
3
3
  require "concerns_on_rails/sortable"
4
4
  require "concerns_on_rails/publishable"
5
5
  require "concerns_on_rails/sluggable"
6
+ require "concerns_on_rails/soft_deletable"
6
7
 
7
8
  module ConcernsOnRails
8
9
  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.0.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-12 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
@@ -66,6 +72,7 @@ files:
66
72
  - lib/concerns_on_rails.rb
67
73
  - lib/concerns_on_rails/publishable.rb
68
74
  - lib/concerns_on_rails/sluggable.rb
75
+ - lib/concerns_on_rails/soft_deletable.rb
69
76
  - lib/concerns_on_rails/sortable.rb
70
77
  - lib/concerns_on_rails/version.rb
71
78
  homepage: https://github.com/VSN2015/concerns_on_rails
@@ -90,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
97
  - !ruby/object:Gem::Version
91
98
  version: '0'
92
99
  requirements: []
93
- rubygems_version: 3.1.6
100
+ rubygems_version: 3.4.19
94
101
  signing_key:
95
102
  specification_version: 4
96
103
  summary: Reusable Rails concerns like Sortable, Publishable, and Sluggable