statusable 0.3.0.pre → 0.4.1

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: '08042127a22da0aacac6b596858eadf3dbbcbf4e0afdd3ff8713807a579f0960'
4
- data.tar.gz: cca9c44afb9772931d2a8b3e6b1c06ce9b765674762d0c47423e61b87ac1a634
3
+ metadata.gz: 47b494f50bfb5227bdc81b7b94b6401aacfbca8b9feb76a9ab05778aee68eccd
4
+ data.tar.gz: 53dad8c7f78793022eeeb65319178bf77214ad681cb10db7fcdb92ca34ac1a13
5
5
  SHA512:
6
- metadata.gz: df801462f17e8287a0885a001d97c71906a579c296851e113698f0c6ae3c2df02d7d249cb7dd8ef74906ea07c2e451d51c186580cce9af954e5846358d79629e
7
- data.tar.gz: 0e7b0fcaaf55e968d4d97bdcaadf4f3d8ca820498d737146f4b08fb739657cde63033567a336d1342dab38b50f56d731ed653b5b7d5134e442a897472aa6d742
6
+ metadata.gz: 514eaadc11e7e6556f1baab28a92fff17b060c701d61f83d82bdab267f4dec27d1038b8620d1fdcf488bbe80daf7641dfd96a4d0a44988c656e2721a7fcebb4d
7
+ data.tar.gz: f20b7697fe30031dfb688d08f0a2c7b1031c03be687d5183d593013b7ba49099d165d5b15e912834afeeb9815842ddb984ed91e7e6611270fccc74c6ce99bd24
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,9 +246,28 @@ 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
 
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
+
248
268
  ### Releases
249
269
 
250
- To release a new version of Statusable to RubyGems:
270
+ To release a new version of this gem to RubyGems:
251
271
 
252
272
  1. Update the version number in `version.rb`
253
273
  2. Update the release date, etc. in `CHANGELOG.md`
@@ -10,9 +10,15 @@ module Statusable::HasStatuses
10
10
  extend ActiveSupport::Concern
11
11
 
12
12
  class_methods do # rubocop:disable Metrics/BlockLength
13
+ # :reek:TooManyStatements
14
+ # :reek:LongParameterList
15
+ # :reek:BooleanParameter
16
+ # :reek:ControlParameter
17
+ # :reek:DuplicateMethodCall
13
18
  # rubocop:disable Metrics/AbcSize
14
19
  # rubocop:disable Metrics/MethodLength
15
20
  # rubocop:disable Naming/PredicateName
21
+
16
22
  def has_statuses(
17
23
  *args,
18
24
  col_name: "status",
@@ -28,126 +34,289 @@ module Statusable::HasStatuses
28
34
  last_word_connector: ", or ").
29
35
  freeze
30
36
 
31
- if validate_presence
32
- validates(
33
- col_name,
34
- presence: true)
37
+ # VALIDATIONS
38
+
39
+ # Define a presence validation on the given `col_name`, if requested.
40
+ if validate_presence # rubocop:disable Style/IfUnlessModifier
41
+ validates(col_name, presence: true)
35
42
  end
36
43
 
44
+ # Define an inclusion validation on the given `col_name` based on the
45
+ # givien list of `statuses`, if requested.
37
46
  if validate_inclusion
47
+ message = "must be one of #{humanized_statuses_list}"
38
48
  validates(
39
49
  col_name,
40
- inclusion: {
41
- in: statuses,
42
- allow_blank: true,
43
- message: "must be one of #{humanized_statuses_list}",
44
- })
50
+ inclusion: { in: statuses, allow_blank: true, message: message })
45
51
  end
46
52
 
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
53
+ # NAMED SCOPES
86
54
 
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
55
+ # Define a named scope that filters on `col_name` records.
56
+ #
57
+ # @attr status [String, Array[String]]
58
+ #
59
+ # @example Default `col_name`
60
+ # .for_status(<a_status_name>)
61
+ #
62
+ # @example Custom `col_name`
63
+ # .for_custom_col_name(<a_status_name>)
64
+ scope :"for_#{col_name}", ->(status) {
65
+ method_name = status.is_a?(Array) ? :in : :eq
66
+ where(arel_column.public_send(method_name, status))
67
+ }
68
+
69
+ # Define a named scope that filters out `col_name` records.
70
+ #
71
+ # @attr status [String, Array[String]]
72
+ #
73
+ # @example Default `col_name`
74
+ # .not_for_status(<a_status_name>)
75
+ #
76
+ # @example Custom `col_name`
77
+ # .not_for_custom_col_name(<a_status_name>)
78
+ scope :"not_for_#{col_name}", ->(status) {
79
+ method_name = status.is_a?(Array) ? :not_in : :not_eq
80
+ where(arel_column.public_send(method_name, status))
81
+ }
82
+
83
+ # Define a named scope that orders by `col_name`, ascending.
84
+ #
85
+ # @example Default `col_name`
86
+ # .by_status_asc
87
+ #
88
+ # @example Custom `col_name`
89
+ # .by_custom_col_name_asc
90
+ scope :"by_#{col_name}_asc", -> { order(arel_column) }
91
+
92
+ # Define a named scope that orders by `col_name`, descending.
93
+ #
94
+ # @example Default `col_name`
95
+ # .by_status_desc
96
+ #
97
+ # @example Custom `col_name`
98
+ # .by_custom_col_name_desc
99
+ scope :"by_#{col_name}_desc", -> { order(arel_column.desc) }
100
+
101
+ # Define a named scope that groups by `col_name`, ascending.
102
+ #
103
+ # @example Default `col_name`
104
+ # .group_by_status
105
+ #
106
+ # @example Custom `col_name`
107
+ # .group_by_custom_col_name
108
+ scope :"group_by_#{col_name}", -> { group(arel_column) }
109
+
110
+ # CLASS METHODS
92
111
 
93
- # Class method to get all options for <col_name>.
94
- # .status_options
112
+ # Define a class method that returns the `statuses` Array.
113
+ #
114
+ # @return [Array[String]]
115
+ #
116
+ # @example Default `col_name`
117
+ # .status_options # => ["Status 1", "Status 2"]
118
+ #
119
+ # @example Custom `col_name`
120
+ # .custom_col_name_options # => ["Custom 1", "Custom 2"]
95
121
  define_singleton_method(:"#{col_name}_options") do
96
122
  statuses
97
123
  end
98
124
 
99
- # Instance method to get all options for <col_name>.
100
- # #status_options
125
+ # Define a class method that returns a humanized list of `statuses`.
126
+ #
127
+ # @return [String]
128
+ #
129
+ # @example Default `col_name`
130
+ # .humanized_statuses_list # => "Status 1, Status 2, or Status 3"
131
+ #
132
+ # @example Custom `col_name`
133
+ # .humanized_custom_col_name_list # => "Custom 1, Custom 2, or Custom 3"
134
+ define_singleton_method(:"humanized_#{pluralized_column_name}_list") do
135
+ humanized_statuses_list
136
+ end
137
+
138
+ # INSTANCE METHODS
139
+
140
+ # Define an instance method that returns the `statuses` Array.
141
+ #
142
+ # @return [Array[String]]
143
+ #
144
+ # @example Default `col_name`
145
+ # #status_options # => ["Status 1", "Status 2"]
146
+ #
147
+ # @example Custom `col_name`
148
+ # #custom_col_name_options # => ["Custom 1", "Custom 2"]
101
149
  define_method(:"#{col_name}_options") do
102
150
  statuses
103
151
  end
104
152
 
105
- # #status?("Ready")
106
- # #status?(["Ready", "Not Ready"])
153
+ # Define an instance method that returns a humanized list of `statuses`.
154
+ #
155
+ # @return [String]
156
+ #
157
+ # @example Default `col_name`
158
+ # #humanized_statuses_list # => "Status 1, Status 2, or Status 3"
159
+ #
160
+ # @example Custom `col_name`
161
+ # #humanized_custom_col_name_list # => "Custom 1, Custom 2, or Custom 3"
162
+ define_method(:"humanized_#{pluralized_column_name}_list") do
163
+ humanized_statuses_list
164
+ end
165
+
166
+ # Define an instance method that checks whether:
167
+ # - The current `status` is the same as the given status
168
+ # - The current `status` is the same as any of the given statuses
169
+ #
170
+ # @return [Boolean]
171
+ #
172
+ # @example Default `col_name`
173
+ # #status?("Known Status") # => true
174
+ # #status?("Unknown Status") # => false
175
+ # #status?(["Known Status 1", "Unknown Status 1"]) # => true
176
+ # #status?(["Unknown Status 1", "Unknown Status 2"]) # => false
177
+ #
178
+ # @example Custom `col_name`
179
+ # #custom_col_name?("Known Custom") # => true
180
+ # #custom_col_name?("Unknown Custom") # => false
181
+ # #custom_col_name?(["Known Custom 1", "Unknown Custom 1"]) # => true
182
+ # #custom_col_name?(["Unknown Custom 1", "Unknown Custom 2"]) # => false
107
183
  define_method(:"#{col_name}?") do |a_status|
108
184
  Array(a_status).any?(public_send(col_name))
109
185
  end
110
186
 
111
- # #not_status?("Ready")
187
+ # rubocop:disable Layout/LineLength
188
+
189
+ # Define an instance method that checks whether:
190
+ # - The current `status` is not the same as given status
191
+ # - The current `status` is not the same as any of the given statuses
192
+ #
193
+ # @return [Boolean]
194
+ #
195
+ # @example Default `col_name`
196
+ # #not_status?("Known Status") # => false
197
+ # #not_status?("Unknown Status") # => true
198
+ # #not_status?(["Known Status 1", "Unknown Status 1"]) # => false
199
+ # #not_status?(["Unknown Status 1", "Unknown Status 2"]) # => true
200
+ #
201
+ # @example Custom `col_name`
202
+ # #not_custom_col_name?("Known Custom") # => false
203
+ # #not_custom_col_name?("Unknown Custom") # => true
204
+ # #not_custom_col_name?(["Known Custom 1", "Unknown Custom 1"]) # => false
205
+ # #not_custom_col_name?(["Unknown Custom 1", "Unknown Custom 2"]) # => true
112
206
  define_method(:"not_#{col_name}?") do |a_status|
113
- public_send(col_name) != a_status
207
+ Array(a_status).none?(public_send(col_name))
114
208
  end
115
209
 
210
+ # rubocop:enable Layout/LineLength
211
+
212
+ # INDIVIDUAL STATUS-SPECIFIC SCOPES/METHODS
213
+
116
214
  statuses.each do |status| # rubocop:disable Metrics/BlockLength
117
215
  status_name = status.parameterize.underscore
118
216
 
119
- # .for_status_ready
120
- scope "for_#{col_name}_#{status_name}",
217
+ # NAMED SCOPES
218
+
219
+ # Define a named scope that filters on `col_name` records that match
220
+ # this `status`.
221
+ #
222
+ # @example Default `col_name`
223
+ # .for_status_ready
224
+ # .for_status_not_ready
225
+ #
226
+ # @example Custom `col_name`
227
+ # .for_custom_col_name_custom_1
228
+ # .for_custom_col_name_custom_2
229
+ scope :"for_#{col_name}_#{status_name}",
121
230
  -> { where(arel_column.eq(status)) }
122
231
 
123
- # .not_for_status_ready
124
- scope "not_for_#{col_name}_#{status_name}",
232
+ # Define a named scope that filters on `col_name` records that do not
233
+ # match this `status`.
234
+ #
235
+ # @example Default `col_name`
236
+ # .not_for_status_ready
237
+ # .not_for_status_not_ready
238
+ #
239
+ # @example Custom `col_name`
240
+ # .not_for_custom_col_name_custom_1
241
+ # .not_for_custom_col_name_custom_2
242
+ scope :"not_for_#{col_name}_#{status_name}",
125
243
  -> { where(arel_column.not_eq(status)) }
126
244
 
127
- # Class method to get <status_name> value. Obviates the need for
128
- # defining a constant.
129
- # .status_ready # => "Ready"
245
+ # CLASS METHODS
246
+
247
+ # Define a class method that returns this `status`'s value. This
248
+ # obviates the need to define a constant on the including Class.
249
+ #
250
+ # @return [String]
251
+ #
252
+ # @example Default `col_name`
253
+ # .status_ready # => "Ready"
254
+ # .status_not_ready # => "Not Ready"
255
+ #
256
+ # @example Custom `col_name`
257
+ # .custom_col_name_custom_1 # => "Custom 1"
258
+ # .custom_col_name_custom_2 # => "Custom 2"
130
259
  define_singleton_method(:"#{col_name}_#{status_name}") do
131
260
  status
132
261
  end
133
262
 
134
- # Instance method to get <status_name> value. Obviates the need for
135
- # defining a constant.
136
- # #status_ready # => "Ready"
263
+ # Define an instance method that returns this `status`'s value. This
264
+ # obviates the need to call e.g.
265
+ # - `self.class::STATUS_READY`
266
+ # - `self.class.status_ready`
267
+ # on the including Object.
268
+ #
269
+ # @return [String]
270
+ #
271
+ # @example Default `col_name`
272
+ # #status_ready # => "Ready"
273
+ # #status_not_ready # => "Not Ready"
274
+ #
275
+ # @example Custom `col_name`
276
+ # #custom_col_name_custom_1 # => "Custom 1"
277
+ # #custom_col_name_custom_2 # => "Custom 2"
137
278
  define_method(:"#{col_name}_#{status_name}") do
138
279
  status
139
280
  end
140
281
 
282
+ # Define an instance method that sets this `status`'s value. This
283
+ # obviates the need to call e.g. `self.status = status_not_ready` on the
284
+ # including Object.
285
+ #
141
286
  # @return [self]
142
- # #set_status_ready
287
+ #
288
+ # @example Default `col_name`
289
+ # #set_status_ready.status # => "Ready"
290
+ # #set_status_not_ready.status # => "Not Ready"
291
+ #
292
+ # @example Custom `col_name`
293
+ # #set_custom_col_name_custom_1.custom_col_name # => "Custom 1"
294
+ # #set_custom_col_name_custom_2.custom_col_name # => "Custom 2"
143
295
  define_method(:"set_#{col_name}_#{status_name}") do
144
296
  public_send(:"#{col_name}=", status)
145
297
 
146
298
  self
147
299
  end
148
300
 
149
- # #set_status_ready!
301
+ # Define an instance method that sets this `status`'s value, and then
302
+ # calls ActiveRecord::Persistence#save!. This obviates the need to call
303
+ # e.g. `self.status = status_not_ready; save!` on the including Object.
304
+ #
150
305
  # @return [self]
306
+ #
307
+ # @example Default `col_name`
308
+ # #set_status_ready!.status # => "Ready"
309
+ # #changes # => {}
310
+ #
311
+ # #set_status_not_ready!.status # => "Not Ready"
312
+ # #changes # => {}
313
+ #
314
+ # @example Custom `col_name`
315
+ # #set_custom_col_name_custom_1!.custom_col_name # => "Custom 1"
316
+ # #changes # => {}
317
+ #
318
+ # #set_custom_col_name_custom_2!.custom_col_name # => "Custom 2"
319
+ # #changes # => {}
151
320
  define_method(:"set_#{col_name}_#{status_name}!") do
152
321
  public_send(:"set_#{col_name}_#{status_name}")
153
322
  save!
@@ -155,17 +324,43 @@ module Statusable::HasStatuses
155
324
  self
156
325
  end
157
326
 
158
- # #status_ready?
327
+ # Define an instance method that checks whether the current `status` is
328
+ # the same as the status named by this method name. This obviates the
329
+ # need to call e.g. `status == status_ready` on the including Object.
330
+ #
331
+ # @return [Boolean]
332
+ #
333
+ # @example Default `col_name`
334
+ # #status_ready? # => true
335
+ # #status_not_ready? # => false
336
+ #
337
+ # @example Custom `col_name`
338
+ # #custom_col_name_custom_1? # => true
339
+ # #custom_col_name_custom_2? # => false
159
340
  define_method(:"#{col_name}_#{status_name}?") do
160
341
  public_send(col_name) == status
161
342
  end
162
343
 
163
- # #not_status_ready?
344
+ # Define an instance method that checks whether the current `status` is
345
+ # not the same as the status named by this method name. This obviates
346
+ # the need to call e.g. `status != status_ready` on the including
347
+ # Object.
348
+ #
349
+ # @return [Boolean]
350
+ #
351
+ # @example Default `col_name`
352
+ # #not_status_ready? # => false
353
+ # #not_status_not_ready? # => true
354
+ #
355
+ # @example Custom `col_name`
356
+ # #not_custom_col_name_custom_1? # => false
357
+ # #not_custom_col_name_custom_2? # => true
164
358
  define_method(:"not_#{col_name}_#{status_name}?") do
165
359
  public_send(col_name) != status
166
360
  end
167
361
  end
168
362
  end
363
+
169
364
  # rubocop:enable Naming/PredicateName
170
365
  # rubocop:enable Metrics/MethodLength
171
366
  # rubocop:enable Metrics/AbcSize
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :reek:IrresponsibleModule
4
+
3
5
  class Statusable::Railtie < Rails::Railtie
4
6
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Statusable
4
- VERSION = "0.3.0.pre"
4
+ VERSION = "0.4.1"
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.3.0.pre
4
+ version: 0.4.1
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-26 00:00:00.000000000 Z
11
+ date: 2024-11-22 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