gl_command 1.4.0 → 2.0.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: affffd85a3150ca92bd45b28822125b8a64117bb41d2f8bffa9fd2861712c9d5
4
- data.tar.gz: 45e62be391f6958720e3e8aec0a7f04fbaf0d9fe4a5b9b382fc2ae4b91246689
3
+ metadata.gz: 5c1d353b0428b6aa32adf96b5962256b415dfacc45116d3e4a21ea8ca345de8e
4
+ data.tar.gz: f8056f5711ef8ffdfafd3678fd25c05fa1879f2893f1d9acecaa9a3880565d7a
5
5
  SHA512:
6
- metadata.gz: 9c2ac5fcedfc9156231e3d146a221945deca5e3f687b2413fe7db9fecd02e9ba3c8f0b94223d0e414fae1946b1ce52eb574118cfaa873a2524cea1f9b8a7812a
7
- data.tar.gz: 31aee586028c0cf073f22f267ee3ad095f48041aace47df51fe9b4c48ed6a6487f2a1d138ad26e786f75e314adc087e87b573032663d9bf363adfceb6a8ac790
6
+ metadata.gz: 8229ae95a48c6b5fc83fbeb647d604666be82cc2e35b08d294c6c8c4c7e44ec4c52d40f520ead9005d38edeee0aab9fc03f0db9e637271b2fd1dbd98619efd21
7
+ data.tar.gz: 678f19f03349988f5a033eeb1f5cff6acb846e0c2b5eea744b7493e0419232d7e0e56f2b4684c9857f0d5e78d7119b9147bef86cf0b5ac642056f9b67365cd8d
data/README.md CHANGED
@@ -18,6 +18,7 @@ Calling a command returns a `GLCommand::Context` which has these properties:
18
18
  - [Displaying errors (use `full_error_message`)](#displaying-errors-use-full_error_message)
19
19
  - [stop_and_fail!](#stop_and_fail)
20
20
  - [Validations](#validations)
21
+ - [Best practices for error handling](#best-practices-for-error-handling)
21
22
  - [GLExceptionNotifier](#glexceptionnotifier)
22
23
  - [Chainable](#chainable)
23
24
  - [Testing `GLCommand`s](#testing-glcommands)
@@ -146,12 +147,41 @@ stop_and_fail!('An error message', no_notify: true) # GLExceptionNotifier is *no
146
147
 
147
148
  ### Validations
148
149
 
149
- You can add validations to `GLCommand::Callable` and `GLCommand::Chainable`.
150
+ You can add validations to `GLCommand::Callable` and `GLCommand::Chainable`. They include `ActiveModel::Validations`, so you can use [Rails active record validations](https://guides.rubyonrails.org/active_record_validations.html).
150
151
 
151
- If the validations fail, the command returns `success: false` without executing.
152
+ If the validations fail, the command returns `success: false` without executing and if validations fail, `GLExceptionNotifier` is **not** called.
152
153
 
153
- If validations fail, `GLExceptionNotifier` is not called
154
+ ```ruby
155
+ class ExampleCommand < GLCommand::Callable
156
+ validates :name, presence: true
157
+ validate :name_must_start_with_cool
158
+
159
+ def name_must_start_with_cool
160
+ return true unless name.start_with?('cool')
161
+
162
+ errors.add(:name, "Doesn't start with 'cool'")
163
+ end
164
+ end
165
+ ```
166
+
167
+ ### Best practices for error handling
168
+
169
+ #### Only add validation errors in validations
170
+
171
+ i.e. don't use `errors.add` in the `call` method. Use `stop_and_fail!` instead.
172
+
173
+ #### Prefer raising the original error
174
+
175
+ For example, if you want to raise a custom error message, don't rescue and then `stop_and_fail!('Some special error message')`. Do this instead:
176
+
177
+ ```ruby
178
+ rescue StandardError => e
179
+ context.full_error_message = "Some special error message"
180
+ raise e
181
+ end
182
+ ```
154
183
 
184
+ This will preserve the original error and stack trace, which makes it easier to debug and track down issues.
155
185
 
156
186
  ## GLExceptionNotifier
157
187
 
data/gl_command.gemspec CHANGED
@@ -19,7 +19,5 @@ Gem::Specification.new do |spec|
19
19
  spec.add_dependency 'activerecord', '>= 3.2.0'
20
20
  spec.add_dependency 'gl_exception_notifier', '>= 1.0.2'
21
21
 
22
- spec.add_development_dependency 'rspec', '~> 3.0'
23
-
24
22
  spec.metadata['rubygems_mfa_required'] = 'true'
25
23
  end
@@ -214,12 +214,15 @@ module GLCommand
214
214
  raise ArgumentError, "unknown #{error_keys_str(unknown)}" if unknown.any?
215
215
 
216
216
  # strong_attributes type checking
217
+ # type can be a class (e.g. String), a symbol naming a predicate method
218
+ # the value must answer truthily (e.g. :acts_as_syncable?), or an array of
219
+ # either (e.g. [User, AdminUser]), in which case the value may match any
217
220
  self.class.requires.merge(self.class.allows).each do |arg, type|
218
- next if type.nil? || args[arg].is_a?(type)
221
+ next if type.nil? || Array(type).any? { |t| value_matches_type?(args[arg], t) }
219
222
  # Validation skipped if allows and nil (but not if blank)
220
223
  next if args[arg].nil? && self.class.allows.include?(arg)
221
224
 
222
- raise GLCommand::ArgumentTypeError, ":#{arg} is not a #{type}"
225
+ raise GLCommand::ArgumentTypeError, ":#{arg} is not #{type_error_str(type)}"
223
226
  end
224
227
  end
225
228
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -227,5 +230,21 @@ module GLCommand
227
230
  def error_keys_str(keys)
228
231
  "keyword#{keys.count > 1 ? 's' : ''}: #{keys.map { |k| ":#{k}" }.join(', ')}"
229
232
  end
233
+
234
+ # A type can be a class (checked with is_a?) or a symbol naming a predicate
235
+ # method the value must respond to and return truthy from
236
+ def value_matches_type?(value, type)
237
+ if type.is_a?(Symbol)
238
+ value.respond_to?(type) && value.public_send(type)
239
+ else
240
+ value.is_a?(type)
241
+ end
242
+ end
243
+
244
+ def type_error_str(type)
245
+ return "one of #{type.join(', ')}" if type.is_a?(Array)
246
+
247
+ type.is_a?(Symbol) ? type.to_s : "a #{type}"
248
+ end
230
249
  end
231
250
  end
@@ -27,7 +27,7 @@ module GLCommand
27
27
 
28
28
  # If someone calls #errors, they expect to get the errors! Include the non-validation error, if it exists
29
29
  def errors
30
- current_errors&.add(:base, "Command Error: #{full_error_message}") if add_command_error?
30
+ current_errors&.add(:base, full_error_message) if add_command_error?
31
31
  current_errors
32
32
  end
33
33
 
@@ -126,7 +126,10 @@ module GLCommand
126
126
  private
127
127
 
128
128
  def current_errors
129
- @callable&.errors
129
+ return @callable.errors if defined?(@callable)
130
+
131
+ # @standalone_errors only is instantiated when you use build_context
132
+ (@standalone_errors ||= ActiveModel::Errors.new(self))
130
133
  end
131
134
 
132
135
  def add_command_error?
@@ -136,7 +139,7 @@ module GLCommand
136
139
 
137
140
  # Add command error unless the existing error is a validation error or there's already a command error
138
141
  @error&.class != ActiveRecord::RecordInvalid &&
139
- current_errors.full_messages.none? { |err| err.start_with?('Command Error: ') }
142
+ current_errors.full_messages.none? { |err| err == @error.message }
140
143
  end
141
144
 
142
145
  def exception?(passed_error)
@@ -46,15 +46,15 @@ module GLCommand
46
46
  class RequireArgumentMatcher < CommandArgumentMatcher
47
47
  private
48
48
 
49
- def scope; :requires; end
50
- def action; 'require'; end
49
+ def scope = :requires
50
+ def action = 'require'
51
51
  end
52
52
 
53
53
  class AllowArgumentMatcher < CommandArgumentMatcher
54
54
  private
55
55
 
56
- def scope; :allows; end
57
- def action; 'allow'; end
56
+ def scope = :allows
57
+ def action = 'allow'
58
58
  end
59
59
 
60
60
  # Specific matcher for `returns`, which only stores an Array of keys.
@@ -69,6 +69,7 @@ module GLCommand
69
69
  self
70
70
  end
71
71
 
72
+ # rubocop:disable Layout/LineLength
72
73
  def matches?(command_class)
73
74
  @command_class = command_class.is_a?(Class) ? command_class : command_class.class
74
75
 
@@ -79,6 +80,7 @@ module GLCommand
79
80
 
80
81
  @command_class.returns.include?(@attribute)
81
82
  end
83
+ # rubocop:enable Layout/LineLength
82
84
 
83
85
  def description
84
86
  "return attribute `#{@attribute}`"
@@ -13,7 +13,7 @@ module GLCommand
13
13
  include ActiveModel::Validations
14
14
 
15
15
  class_methods do
16
- def i18n_scope
16
+ define_method(:i18n_scope) do
17
17
  :activerecord
18
18
  end
19
19
  end
@@ -1,3 +1,3 @@
1
1
  module GLCommand
2
- VERSION = '1.4.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gl_command
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Give Lively
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-11 00:00:00.000000000 Z
11
+ date: 2026-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.0.2
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.0'
55
41
  description:
56
42
  email:
57
43
  executables: []
@@ -92,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
78
  - !ruby/object:Gem::Version
93
79
  version: '0'
94
80
  requirements: []
95
- rubygems_version: 3.4.19
81
+ rubygems_version: 3.5.22
96
82
  signing_key:
97
83
  specification_version: 4
98
84
  summary: Give Lively Commands