gourami 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: f4786ed7265bcf16aca2b9fb9e4a695e84b6d7e31ebe5264870e8674f71a1115
4
- data.tar.gz: dcd57424cf0af46cda99949e77051c70beb4204c4b85f82c8d4300ed229fb09b
3
+ metadata.gz: 0cc34cc23b4323b885cb63b0c82cd98921e0467fac34a97fcc584550f85de29d
4
+ data.tar.gz: '0737019b3dcb2cc40567287a94f4a3bd9dc13ef3b32a28e0e140b0745cc5a29e'
5
5
  SHA512:
6
- metadata.gz: 6a1811ded432ac060c4461dc790e2cb08f9b7779157f6533cb8d32c9e189f1a716c0824dcc6ca8764d8bf21a2c8bbd67d1822860d11fb5f3d37979a75a03451a
7
- data.tar.gz: 7158387f0e394fe67f165df9a8e63aa2b8889d13e26e867d74048a68c9655cded3a181e731bddc4d73d6d087e60a5b83deea899ce7a5a5cbbee5311344a35a33
6
+ metadata.gz: fb5e34109e4c24fd1394e8888acee9d2a0ff99542fb9aadd9072f6e81ca9c7cb65f05d65ba7dfb505ea3ba520f803a9eceeaebd2b0fc2260bae740ce7ea60246
7
+ data.tar.gz: 55a3dffab397480607dfa1bb0edbee3b5d17a52c859e855423aec56b349a1a15542e47238b45c3edb5740d3c3680f0e363767da40e0edf7f93835c9f884f1b72
@@ -2,6 +2,107 @@ module Gourami
2
2
  module Extensions
3
3
  module Resources
4
4
 
5
+ # yield to the given block for each resource in the given namespace.
6
+ #
7
+ # @param resource_namespace [Symbol] The namespace of the resource (e.g. :users, :payments)
8
+ #
9
+ # @option offset [Integer] The offset of the resource (e.g. 0, 1, 2) for example in an update form, there may be existing items that already exist, however only the new items are sent to the form.
10
+ #
11
+ # @yield The block to execute, each time with a resource active.
12
+ #
13
+ # @example
14
+ # def validate
15
+ # with_each_resource(:social_broadcasts) do
16
+ # validate_presence(:title) # validates `attributes[:social_broadcasts][<EACH>][:title]`
17
+ # end
18
+ # end
19
+ #
20
+ # @example
21
+ # def validate
22
+ # with_each_resource(:items, offset: existing_items.count) do |item, key, index|
23
+ # # Standard validation methods are available, and will validate the attributes on the resource:
24
+ # validate_decimal_places(:price, max: 2)
25
+ #
26
+ # # You may reference the resource object directly to perform custom validation logic inline:
27
+ # append_error(:password_confirmation, :doesnt_match) if item["password"] != item["password_confirmation"]
28
+ #
29
+ # # If `items` is a Hash, `key` is the hash key of the resource.
30
+ # # If `items` is an Array, `key` is the index of the resource (`+ offset`, if an offset is given).
31
+ # validate_length(:name, max: 255) if key % 2 == 0
32
+ #
33
+ # # If `items` is a Hash, `index` is the hash key of the resource.
34
+ # # If `items` is an Array, `index` is the index of the resource (offset is NOT applied to index).
35
+ # append_error(:id, :is_invalid) if index > 500
36
+ # end
37
+ # end
38
+ def with_each_resource(resource_namespace, offset: nil, &_block)
39
+ resources = send(resource_namespace)
40
+ if resources.is_a?(Hash)
41
+ return resources.each_with_index do |(key, resource), index|
42
+ with_resource(resource_namespace, key, offset: offset) do
43
+ yield(resource, key, index)
44
+ end
45
+ end
46
+ end
47
+
48
+ send(resource_namespace).each_with_index do |resource, index|
49
+ with_resource(resource_namespace, index, offset: offset) do
50
+ yield(resource, offset ? index + offset : index, index)
51
+ end
52
+ end
53
+ end
54
+
55
+ # For the duration of the given block, all validations will be done on the given resource.
56
+ #
57
+ # @param resource_namespace [Symbol] The namespace of the resource (e.g. :users, :payments)
58
+ # @param resource_uid [String|Number] The uid of the resource (e.g. 0, 1, "123")
59
+ #
60
+ # @option offset [Integer] The offset of the resource (e.g. 0, 1, 2) for example in an update form, there may be existing items that already exist, however only the new items are sent to the form.
61
+ #
62
+ # @yield The block to execute with the resource active.
63
+ #
64
+ # @example
65
+ # def validate
66
+ # validate_presence(:title) # validates `attributes[:title]` of the form
67
+ #
68
+ # with_resource(:social_broadcasts, "facebook_page-41") do
69
+ # # Within this block all validations will be done on the resource.
70
+ # validate_presence(:title) # validates `attributes[:social_broadcasts]["facebook_page-41"][:title]`
71
+ # validate_presence(:trim_start_time) # validates `attributes[:social_broadcasts]["facebook_page-41"][:trim_start_time]`
72
+ # validate_presence(:trim_end_time) # validates `attributes[:social_broadcasts]["facebook_page-41"][:trim_end_time]`
73
+ # end
74
+ # end
75
+ def with_resource(resource_namespace, resource_uid, offset: nil, &_block)
76
+ @resource_namespace = resource_namespace
77
+ @resource_uid = resource_uid
78
+ @offset = offset
79
+ yield
80
+ ensure
81
+ @resource_namespace = nil
82
+ @resource_uid = nil
83
+ @offset = nil
84
+ end
85
+
86
+ # If a resource namespace is active (within with_resource block), find the resource using the namespace and uid.
87
+ # Otherwise, return the form object.
88
+ def current_resource
89
+ if @resource_namespace
90
+ send(@resource_namespace)[@resource_uid]
91
+ else
92
+ super
93
+ end
94
+ end
95
+
96
+ # If a resource namespace is active (within with_resource block), append the error to the resource.
97
+ # Otherwise, append the error to the form object.
98
+ def append_error(attribute_name, message)
99
+ if @resource_namespace
100
+ append_resource_error(@resource_namespace, @offset ? @resource_uid + @offset : @resource_uid, attribute_name, message)
101
+ else
102
+ super
103
+ end
104
+ end
105
+
5
106
  # Return a deeply nested Hash which allows you to identify errors by resource.
6
107
  #
7
108
  # @return [Hash<Symbol>]
@@ -56,7 +157,7 @@ module Gourami
56
157
 
57
158
  # TODO: YARD
58
159
  def resource_has_errors?(resource_namespace, resource_uid)
59
- resource_errors[resource_namespace, resource_uid.to_s].values.map(&:flatten).any?
160
+ resource_errors[resource_namespace][resource_uid.to_s].values.map(&:flatten).any?
60
161
  end
61
162
 
62
163
  # TODO: YARD
@@ -85,16 +85,45 @@ module Gourami
85
85
  # @param attribute_name [Symbol, nil] nil for base
86
86
  # @param error [Symbol, String]
87
87
  # The error identifier.
88
- def append_error(attribute_name, error)
88
+ def append_root_error(attribute_name, error)
89
89
  errors[attribute_name] << error
90
90
  end
91
91
 
92
+ # NOTE: The following resource methods are to support the validate_* methods
93
+ # to cooperate with resource when using Extensions::Resources.
94
+
95
+ # Overridden and super invoked from Extensions::Resources
96
+ def current_resource
97
+ self
98
+ end
99
+
100
+ # Overridden and super invoked from Extensions::Resources
101
+ def append_error(attribute_name, message)
102
+ append_root_error(attribute_name, message)
103
+ end
104
+
105
+ def get_current_resource_attribute_value(attribute_name)
106
+ resource = current_resource
107
+ # If resource responds to the attribute, return the value. Otherwise, check if it's a hash and return the value for the attribute.
108
+ if resource.respond_to?(attribute_name)
109
+ resource.send(attribute_name)
110
+ elsif resource.respond_to?(:[])
111
+ if resource.key?(attribute_name.to_sym)
112
+ resource[attribute_name.to_sym]
113
+ elsif resource.key?(attribute_name.to_s)
114
+ resource[attribute_name.to_s]
115
+ end
116
+ else
117
+ nil
118
+ end
119
+ end
120
+
92
121
  # Validate the presence of the attribute value. If the value is nil or
93
122
  # false append the :cant_be_empty error to the attribute.
94
123
  #
95
124
  # @param attribute_name [Symbol]
96
125
  def validate_presence(attribute_name, message = nil)
97
- value = send(attribute_name)
126
+ value = get_current_resource_attribute_value(attribute_name)
98
127
  if !value || value.to_s.strip.empty?
99
128
  append_error(attribute_name, message || :cant_be_empty)
100
129
  end
@@ -109,7 +138,7 @@ module Gourami
109
138
  # A block to determine if a given value is unique or not. It receives
110
139
  # the value and returns true if the value is unique.
111
140
  def validate_uniqueness(attribute_name, message = nil, &block)
112
- value = send(attribute_name)
141
+ value = get_current_resource_attribute_value(attribute_name)
113
142
  unless block.call(value)
114
143
  append_error(attribute_name, message || :is_duplicated)
115
144
  end
@@ -137,7 +166,7 @@ module Gourami
137
166
  # @param attribute_name [Symbol]
138
167
  # @param format [Regexp]
139
168
  def validate_format(attribute_name, format, message = nil)
140
- value = send(attribute_name)
169
+ value = get_current_resource_attribute_value(attribute_name)
141
170
  if value && !(format =~ value)
142
171
  append_error(attribute_name, message || :is_invalid)
143
172
  end
@@ -157,7 +186,7 @@ module Gourami
157
186
 
158
187
  min = options.fetch(:min, nil)
159
188
  max = options.fetch(:max, nil)
160
- value = send(attribute_name)
189
+ value = get_current_resource_attribute_value(attribute_name)
161
190
 
162
191
  return if options[:allow_blank] && value.blank?
163
192
 
@@ -185,7 +214,7 @@ module Gourami
185
214
  # @param attribute_name [Symbol]
186
215
  # @param list [Array]
187
216
  def validate_inclusion(attribute_name, list, message = nil)
188
- value = send(attribute_name)
217
+ value = get_current_resource_attribute_value(attribute_name)
189
218
  if value && !list.include?(value)
190
219
  append_error(attribute_name, message || :isnt_listed)
191
220
  end
@@ -194,7 +223,7 @@ module Gourami
194
223
  # Validate the presence of each object in attribute name within list. If the object
195
224
  # is not included in the list, append the :not_listed error to the attribute.
196
225
  def validate_inclusion_of_each(attribute_name, list, message = nil)
197
- value = send(attribute_name)
226
+ value = get_current_resource_attribute_value(attribute_name)
198
227
  value && value.each do |obj|
199
228
  unless list.include?(obj)
200
229
  append_error(attribute_name, message || "#{obj} isn't listed")
@@ -208,7 +237,7 @@ module Gourami
208
237
  #
209
238
  # @param attribute_name [Symbol]
210
239
  def validate_any(attribute_name, message = nil)
211
- value = send(attribute_name)
240
+ value = get_current_resource_attribute_value(attribute_name)
212
241
  if value && value.empty?
213
242
  append_error(attribute_name, message || :cant_be_empty)
214
243
  end
@@ -220,7 +249,7 @@ module Gourami
220
249
  # @param attribute_name [Symbol]
221
250
  # @param filetypes [Array<String>]
222
251
  def validate_filetype(attribute_name, filetypes, message = nil)
223
- value = send(attribute_name)
252
+ value = get_current_resource_attribute_value(attribute_name)
224
253
  if value && !filetypes.include?(value[:type].to_s.split("/").first)
225
254
  append_error(attribute_name, message || :is_invalid)
226
255
  end
@@ -237,7 +266,7 @@ module Gourami
237
266
  # @option options [Integer] :max (nil)
238
267
  # The maximum value the attribute can take, if nil, no validation is made.
239
268
  def validate_range(attribute_name, options = {})
240
- value = send(attribute_name)
269
+ value = get_current_resource_attribute_value(attribute_name)
241
270
 
242
271
  return unless value
243
272
 
@@ -247,5 +276,35 @@ module Gourami
247
276
  append_error(attribute_name, options.fetch(:max_message, nil) || :greater_than_max) if max && value > max
248
277
  end
249
278
 
279
+ # Ensure the provided numeric attribute has the correct number of decimal places within the given range.
280
+ #
281
+ # @param attribute_name [Symbol]
282
+ # @option options [Integer] :max (nil)
283
+ # The maximum number of decimal places the attribute can have.
284
+ # @option options [Integer] :min (0)
285
+ # The minimum number of decimal places the attribute can have.
286
+ # @option options [String] :max_message (nil)
287
+ # The error message to append if the attribute has too many decimal places.
288
+ # @option options [String] :min_message (nil)
289
+ # The error message to append if the attribute has too few decimal places.
290
+ #
291
+ # @example
292
+ # validate_decimal_places(:price, max: 2)
293
+ # validate_decimal_places(:price, min: 2, min_message: "Price must have at least 2 decimal places.")
294
+ def validate_decimal_places(attribute_name, max:, min: 0, max_message: nil, min_message: nil)
295
+ value = get_current_resource_attribute_value(attribute_name)&.to_s
296
+ return unless value
297
+
298
+ decimal_places = value.split(".", 2).last&.length || 0
299
+
300
+ if max && max > 0 && decimal_places > max
301
+ append_error(attribute_name, max_message || :too_many_decimal_places)
302
+ end
303
+
304
+ if min && min > 0 && decimal_places < min
305
+ append_error(attribute_name, min_message || :too_few_decimal_places)
306
+ end
307
+ end
308
+
250
309
  end
251
310
  end
@@ -1,3 +1,3 @@
1
1
  module Gourami
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: gourami
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
  - TSMMark
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-09 00:00:00.000000000 Z
11
+ date: 2025-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport