statusable 0.2.0.pre → 0.4.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: 6abf9f62c9ce28ec0003fe73731760adc77dd2e6fad64f05165279d2cb575684
4
- data.tar.gz: db4e4a6a8dd888ee50dc489defe0e44c6b225a68a07ec1c41e02b4194eb6d4f8
3
+ metadata.gz: b529e80d12f7c2f085b89f9ed98fc9abd70e47f2a823c814005bad5243c0bed6
4
+ data.tar.gz: 503ce3bedca223203c854a936ff4b98ca04a2efe60a8a61d58b530dbe66c56d9
5
5
  SHA512:
6
- metadata.gz: db814ddca7cffb1c14facd97dadc88d341616a4357e32822f254a9b0d6196103bc17022549955f58cd4db71544295a409381fcc08e022a5a366586d8110cd793
7
- data.tar.gz: aa2482d72ee291e07f8b40f3e5e4c9d26b518508c0214974695b51687d8735f3d92a7d991372219dd88c1b87bc9be4432eb4bc8c17c0a504ad16734d58f7ffbd
6
+ metadata.gz: aebb2e90c390cc59bde1e77de2c4468d35bc5acfe339092ac598b5f4f547dca04d04d6b8d5dd7f78ad7828baabc24d8e3842349c8e94a7fc11e4b417beb54cf3
7
+ data.tar.gz: 4d7f1525ffc1ff3a80be61fac81095649aaf9eecd2b43c48f11edd96034be506c2c39cf801117f3a82b1ea2f348b68f4d845e28f40fbedbcb10b8ef47d4ff957
data/README.md CHANGED
@@ -142,6 +142,7 @@ job.validate; job.errors[:status]
142
142
  The column name is customizable, as is whether or not to validate on presence or inclusion. Validating on inclusion means: ensuring that the current status value is included in the list of possible status values.
143
143
 
144
144
  Defaults:
145
+
145
146
  - `col_name = "status"`
146
147
  - `validate_presence = false`
147
148
  - `validate_inclusion = true`
@@ -245,7 +246,34 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
245
246
 
246
247
  To install this gem onto your local machine, run `bundle exec rake install`.
247
248
 
248
- To release a new version, update the version number in `version.rb` and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
249
+ ### Testing
250
+
251
+ To test this gem:
252
+
253
+ ```bash
254
+ rake
255
+ ```
256
+
257
+ #### Linters
258
+
259
+ ```bash
260
+ rubocop
261
+
262
+ reek
263
+
264
+ npx prettier . --check
265
+ npx prettier . --write
266
+ ```
267
+
268
+ ### Releases
269
+
270
+ To release a new version of this gem to RubyGems:
271
+
272
+ 1. Update the version number in `version.rb`
273
+ 2. Update the release date, etc. in `CHANGELOG.md`
274
+ 3. Run `bundle` to update Gemfile.lock with the latest version info
275
+ 4. Commit the changes. e.g. `Bump to vX.Y.Z`
276
+ 5. Run `rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
249
277
 
250
278
  ## License
251
279
 
@@ -28,125 +28,289 @@ module Statusable::HasStatuses
28
28
  last_word_connector: ", or ").
29
29
  freeze
30
30
 
31
- if validate_presence
32
- validates(
33
- col_name,
34
- presence: true)
31
+ # VALIDATIONS
32
+
33
+ # Define a presence validation on the given `col_name`, if requested.
34
+ if validate_presence # rubocop:disable Style/IfUnlessModifier
35
+ validates(col_name, presence: true)
35
36
  end
36
37
 
38
+ # Define an inclusion validation on the given `col_name` based on the
39
+ # givien list of `statuses`, if requested.
37
40
  if validate_inclusion
41
+ message = "must be one of #{humanized_statuses_list}"
38
42
  validates(
39
43
  col_name,
40
- inclusion: {
41
- in: statuses,
42
- allow_blank: true,
43
- message: "must be one of #{humanized_statuses_list}",
44
- })
44
+ inclusion: { in: statuses, allow_blank: true, message: message })
45
45
  end
46
46
 
47
- # .for_status(<status>)
48
- scope "for_#{col_name}",
49
- ->(status) {
50
- where(
51
- if status.is_a?(Array)
52
- arel_column.in(status)
53
- else
54
- arel_column.eq(status)
55
- end)
56
- }
57
-
58
- # .not_for_status(<status>)
59
- scope "not_for_#{col_name}",
60
- ->(status) {
61
- where(
62
- if status.is_a?(Array)
63
- arel_column.not_in(status)
64
- else
65
- arel_column.not_eq(status)
66
- end)
67
- }
68
-
69
- # .by_status_asc
70
- scope "by_#{col_name}_asc",
71
- -> { order(arel_column) }
72
-
73
- # .by_status_desc
74
- scope "by_#{col_name}_desc",
75
- -> { order(arel_column.desc) }
76
-
77
- # .group_by_status
78
- scope "group_by_#{col_name}",
79
- -> { group(arel_column) }
80
-
81
- # Class method to get a humanized list of statuses.
82
- # .humanized_statuses_list
83
- define_singleton_method(:"humanized_#{pluralized_column_name}_list") do
84
- humanized_statuses_list
85
- end
47
+ # NAMED SCOPES
86
48
 
87
- # Instance method to get a humanized list of statuses.
88
- # #humanized_statuses_list
89
- define_method(:"humanized_#{pluralized_column_name}_list") do
90
- humanized_statuses_list
91
- end
49
+ # Define a named scope that filters on `col_name` records.
50
+ #
51
+ # @attr status [String, Array[String]]
52
+ #
53
+ # @example Default `col_name`
54
+ # .for_status(<a_status_name>)
55
+ #
56
+ # @example Custom `col_name`
57
+ # .for_custom_col_name(<a_status_name>)
58
+ scope :"for_#{col_name}", ->(status) {
59
+ method_name = status.is_a?(Array) ? :in : :eq
60
+ where(arel_column.public_send(method_name, status))
61
+ }
62
+
63
+ # Define a named scope that filters out `col_name` records.
64
+ #
65
+ # @attr status [String, Array[String]]
66
+ #
67
+ # @example Default `col_name`
68
+ # .not_for_status(<a_status_name>)
69
+ #
70
+ # @example Custom `col_name`
71
+ # .not_for_custom_col_name(<a_status_name>)
72
+ scope :"not_for_#{col_name}", ->(status) {
73
+ method_name = status.is_a?(Array) ? :not_in : :not_eq
74
+ where(arel_column.public_send(method_name, status))
75
+ }
76
+
77
+ # Define a named scope that orders by `col_name`, ascending.
78
+ #
79
+ # @example Default `col_name`
80
+ # .by_status_asc
81
+ #
82
+ # @example Custom `col_name`
83
+ # .by_custom_col_name_asc
84
+ scope :"by_#{col_name}_asc", -> { order(arel_column) }
85
+
86
+ # Define a named scope that orders by `col_name`, descending.
87
+ #
88
+ # @example Default `col_name`
89
+ # .by_status_desc
90
+ #
91
+ # @example Custom `col_name`
92
+ # .by_custom_col_name_desc
93
+ scope :"by_#{col_name}_desc", -> { order(arel_column.desc) }
94
+
95
+ # Define a named scope that groups by `col_name`, ascending.
96
+ #
97
+ # @example Default `col_name`
98
+ # .group_by_status
99
+ #
100
+ # @example Custom `col_name`
101
+ # .group_by_custom_col_name
102
+ scope :"group_by_#{col_name}", -> { group(arel_column) }
103
+
104
+ # CLASS METHODS
92
105
 
93
- # Class method to get all options for <col_name>.
94
- # .status_options
106
+ # Define a class method that returns the `statuses` Array.
107
+ #
108
+ # @return [Array[String]]
109
+ #
110
+ # @example Default `col_name`
111
+ # .status_options # => ["Status 1", "Status 2"]
112
+ #
113
+ # @example Custom `col_name`
114
+ # .custom_col_name_options # => ["Custom 1", "Custom 2"]
95
115
  define_singleton_method(:"#{col_name}_options") do
96
116
  statuses
97
117
  end
98
118
 
99
- # Instance method to get all options for <col_name>.
100
- # #status_options
119
+ # Define a class method that returns a humanized list of `statuses`.
120
+ #
121
+ # @return [String]
122
+ #
123
+ # @example Default `col_name`
124
+ # .humanized_statuses_list # => "Status 1, Status 2, or Status 3"
125
+ #
126
+ # @example Custom `col_name`
127
+ # .humanized_custom_col_name_list # => "Custom 1, Custom 2, or Custom 3"
128
+ define_singleton_method(:"humanized_#{pluralized_column_name}_list") do
129
+ humanized_statuses_list
130
+ end
131
+
132
+ # INSTANCE METHODS
133
+
134
+ # Define an instance method that returns the `statuses` Array.
135
+ #
136
+ # @return [Array[String]]
137
+ #
138
+ # @example Default `col_name`
139
+ # #status_options # => ["Status 1", "Status 2"]
140
+ #
141
+ # @example Custom `col_name`
142
+ # #custom_col_name_options # => ["Custom 1", "Custom 2"]
101
143
  define_method(:"#{col_name}_options") do
102
144
  statuses
103
145
  end
104
146
 
105
- # #status?("Ready")
147
+ # Define an instance method that returns a humanized list of `statuses`.
148
+ #
149
+ # @return [String]
150
+ #
151
+ # @example Default `col_name`
152
+ # #humanized_statuses_list # => "Status 1, Status 2, or Status 3"
153
+ #
154
+ # @example Custom `col_name`
155
+ # #humanized_custom_col_name_list # => "Custom 1, Custom 2, or Custom 3"
156
+ define_method(:"humanized_#{pluralized_column_name}_list") do
157
+ humanized_statuses_list
158
+ end
159
+
160
+ # Define an instance method that checks whether:
161
+ # - The current `status` is the same as the given status
162
+ # - The current `status` is the same as any of the given statuses
163
+ #
164
+ # @return [Boolean]
165
+ #
166
+ # @example Default `col_name`
167
+ # #status?("Known Status") # => true
168
+ # #status?("Unknown Status") # => false
169
+ # #status?(["Known Status 1", "Unknown Status 1"]) # => true
170
+ # #status?(["Unknown Status 1", "Unknown Status 2"]) # => false
171
+ #
172
+ # @example Custom `col_name`
173
+ # #custom_col_name?("Known Custom") # => true
174
+ # #custom_col_name?("Unknown Custom") # => false
175
+ # #custom_col_name?(["Known Custom 1", "Unknown Custom 1"]) # => true
176
+ # #custom_col_name?(["Unknown Custom 1", "Unknown Custom 2"]) # => false
106
177
  define_method(:"#{col_name}?") do |a_status|
107
- public_send(col_name) == a_status
178
+ Array(a_status).any?(public_send(col_name))
108
179
  end
109
180
 
110
- # #not_status?("Ready")
181
+ # rubocop:disable Layout/LineLength
182
+
183
+ # Define an instance method that checks whether:
184
+ # - The current `status` is not the same as given status
185
+ # - The current `status` is not the same as any of the given statuses
186
+ #
187
+ # @return [Boolean]
188
+ #
189
+ # @example Default `col_name`
190
+ # #not_status?("Known Status") # => false
191
+ # #not_status?("Unknown Status") # => true
192
+ # #not_status?(["Known Status 1", "Unknown Status 1"]) # => false
193
+ # #not_status?(["Unknown Status 1", "Unknown Status 2"]) # => true
194
+ #
195
+ # @example Custom `col_name`
196
+ # #not_custom_col_name?("Known Custom") # => false
197
+ # #not_custom_col_name?("Unknown Custom") # => true
198
+ # #not_custom_col_name?(["Known Custom 1", "Unknown Custom 1"]) # => false
199
+ # #not_custom_col_name?(["Unknown Custom 1", "Unknown Custom 2"]) # => true
111
200
  define_method(:"not_#{col_name}?") do |a_status|
112
- public_send(col_name) != a_status
201
+ Array(a_status).none?(public_send(col_name))
113
202
  end
114
203
 
204
+ # rubocop:enable Layout/LineLength
205
+
206
+ # INDIVIDUAL STATUS-SPECIFIC SCOPES/METHODS
207
+
115
208
  statuses.each do |status| # rubocop:disable Metrics/BlockLength
116
209
  status_name = status.parameterize.underscore
117
210
 
118
- # .for_status_ready
119
- scope "for_#{col_name}_#{status_name}",
211
+ # NAMED SCOPES
212
+
213
+ # Define a named scope that filters on `col_name` records that match
214
+ # this `status`.
215
+ #
216
+ # @example Default `col_name`
217
+ # .for_status_ready
218
+ # .for_status_not_ready
219
+ #
220
+ # @example Custom `col_name`
221
+ # .for_custom_col_name_custom_1
222
+ # .for_custom_col_name_custom_2
223
+ scope :"for_#{col_name}_#{status_name}",
120
224
  -> { where(arel_column.eq(status)) }
121
225
 
122
- # .not_for_status_ready
123
- scope "not_for_#{col_name}_#{status_name}",
226
+ # Define a named scope that filters on `col_name` records that do not
227
+ # match this `status`.
228
+ #
229
+ # @example Default `col_name`
230
+ # .not_for_status_ready
231
+ # .not_for_status_not_ready
232
+ #
233
+ # @example Custom `col_name`
234
+ # .not_for_custom_col_name_custom_1
235
+ # .not_for_custom_col_name_custom_2
236
+ scope :"not_for_#{col_name}_#{status_name}",
124
237
  -> { where(arel_column.not_eq(status)) }
125
238
 
126
- # Class method to get <status_name> value. Obviates the need for
127
- # defining a constant.
128
- # .status_ready # => "Ready"
239
+ # CLASS METHODS
240
+
241
+ # Define a class method that returns this `status`'s value. This
242
+ # obviates the need to define a constant on the including Class.
243
+ #
244
+ # @return [String]
245
+ #
246
+ # @example Default `col_name`
247
+ # .status_ready # => "Ready"
248
+ # .status_not_ready # => "Not Ready"
249
+ #
250
+ # @example Custom `col_name`
251
+ # .custom_col_name_custom_1 # => "Custom 1"
252
+ # .custom_col_name_custom_2 # => "Custom 2"
129
253
  define_singleton_method(:"#{col_name}_#{status_name}") do
130
254
  status
131
255
  end
132
256
 
133
- # Instance method to get <status_name> value. Obviates the need for
134
- # defining a constant.
135
- # #status_ready # => "Ready"
257
+ # Define an instance method that returns this `status`'s value. This
258
+ # obviates the need to call e.g.
259
+ # - `self.class::STATUS_READY`
260
+ # - `self.class.status_ready`
261
+ # on the including Object.
262
+ #
263
+ # @return [String]
264
+ #
265
+ # @example Default `col_name`
266
+ # #status_ready # => "Ready"
267
+ # #status_not_ready # => "Not Ready"
268
+ #
269
+ # @example Custom `col_name`
270
+ # #custom_col_name_custom_1 # => "Custom 1"
271
+ # #custom_col_name_custom_2 # => "Custom 2"
136
272
  define_method(:"#{col_name}_#{status_name}") do
137
273
  status
138
274
  end
139
275
 
276
+ # Define an instance method that sets this `status`'s value. This
277
+ # obviates the need to call e.g. `self.status = status_not_ready` on the
278
+ # including Object.
279
+ #
140
280
  # @return [self]
141
- # #set_status_ready
281
+ #
282
+ # @example Default `col_name`
283
+ # #set_status_ready.status # => "Ready"
284
+ # #set_status_not_ready.status # => "Not Ready"
285
+ #
286
+ # @example Custom `col_name`
287
+ # #set_custom_col_name_custom_1.custom_col_name # => "Custom 1"
288
+ # #set_custom_col_name_custom_2.custom_col_name # => "Custom 2"
142
289
  define_method(:"set_#{col_name}_#{status_name}") do
143
290
  public_send(:"#{col_name}=", status)
144
291
 
145
292
  self
146
293
  end
147
294
 
148
- # #set_status_ready!
295
+ # Define an instance method that sets this `status`'s value, and then
296
+ # calls ActiveRecord::Persistence#save!. This obviates the need to call
297
+ # e.g. `self.status = status_not_ready; save!` on the including Object.
298
+ #
149
299
  # @return [self]
300
+ #
301
+ # @example Default `col_name`
302
+ # #set_status_ready!.status # => "Ready"
303
+ # #changes # => {}
304
+ #
305
+ # #set_status_not_ready!.status # => "Not Ready"
306
+ # #changes # => {}
307
+ #
308
+ # @example Custom `col_name`
309
+ # #set_custom_col_name_custom_1!.custom_col_name # => "Custom 1"
310
+ # #changes # => {}
311
+ #
312
+ # #set_custom_col_name_custom_2!.custom_col_name # => "Custom 2"
313
+ # #changes # => {}
150
314
  define_method(:"set_#{col_name}_#{status_name}!") do
151
315
  public_send(:"set_#{col_name}_#{status_name}")
152
316
  save!
@@ -154,12 +318,37 @@ module Statusable::HasStatuses
154
318
  self
155
319
  end
156
320
 
157
- # #status_ready?
321
+ # Define an instance method that checks whether the current `status` is
322
+ # the same as the status named by this method name. This obviates the
323
+ # need to call e.g. `status == status_ready` on the including Object.
324
+ #
325
+ # @return [Boolean]
326
+ #
327
+ # @example Default `col_name`
328
+ # #status_ready? # => true
329
+ # #status_not_ready? # => false
330
+ #
331
+ # @example Custom `col_name`
332
+ # #custom_col_name_custom_1? # => true
333
+ # #custom_col_name_custom_2? # => false
158
334
  define_method(:"#{col_name}_#{status_name}?") do
159
335
  public_send(col_name) == status
160
336
  end
161
337
 
162
- # #not_status_ready?
338
+ # Define an instance method that checks whether the current `status` is
339
+ # not the same as the status named by this method name. This obviates
340
+ # the need to call e.g. `status != status_ready` on the including
341
+ # Object.
342
+ #
343
+ # @return [Boolean]
344
+ #
345
+ # @example Default `col_name`
346
+ # #not_status_ready? # => false
347
+ # #not_status_not_ready? # => true
348
+ #
349
+ # @example Custom `col_name`
350
+ # #not_custom_col_name_custom_1? # => false
351
+ # #not_custom_col_name_custom_2? # => true
163
352
  define_method(:"not_#{col_name}_#{status_name}?") do
164
353
  public_send(col_name) != status
165
354
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statusable
4
- VERSION = "0.2.0.pre"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statusable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.pre
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul DobbinSchmaltz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-16 00:00:00.000000000 Z
11
+ date: 2024-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -98,14 +98,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - ">="
100
100
  - !ruby/object:Gem::Version
101
- version: '2.7'
101
+ version: '3.1'
102
102
  required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  requirements:
104
104
  - - ">="
105
105
  - !ruby/object:Gem::Version
106
106
  version: '0'
107
107
  requirements: []
108
- rubygems_version: 3.5.17
108
+ rubygems_version: 3.3.27
109
109
  signing_key:
110
110
  specification_version: 4
111
111
  summary: Adds a `has_statuses` macro for defining common status-related ActiveRecord