standardapi 7.1.0 → 7.1.2

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: 88dd56c94d20d8649c5f755509be1aee880f7dff0133602a4fe46c0408c4e65b
4
- data.tar.gz: 3f14301f672ac171f9e2a39620de9f1913300572164978857ea12fcb1bb008de
3
+ metadata.gz: cb9fe722c9847c5c2aa696b36fdbebbc4783f78d92036683b03cde0929f7b78a
4
+ data.tar.gz: 6723597440138d500614b84f34106279778addc18bd40419d854b0e7bbb628cc
5
5
  SHA512:
6
- metadata.gz: e3c282a6b8898d1c1451e381dea4cf57c3cd58b65bccfba6a0b5f9d302242287bb233dd43b4a078c51f1711a32a449d47c157626a92cc752939d8040ef704534
7
- data.tar.gz: 56d90b5dc3d5d04924b7bd5e5fe6a98cfe9cf4f6fa79d30c933abff3e5c6f6b69a6718a699bb5199183f05372f99d8038f99dc7e5ff1b011dca4da978ee4d5ae
6
+ metadata.gz: 8aa742b8fda2329a7c17b314ac2b40c8ae46b139fa482516166e7c19a96e851c8438062a89b9de114a5425eaa429c69327faf1fb01b5b4e7e78f1415646a5669
7
+ data.tar.gz: 8f17ce9c37b5948c726e8cc13166acf8f5be4b7b63103d8c7eb55f308abfe65a6688000ac8b58a062bd30d8d385225a17ae9e8e80d34e8c757634084cab4776a
@@ -1,9 +1,9 @@
1
1
  module StandardAPI
2
2
  module Helpers
3
-
3
+
4
4
  def serialize_attribute(json, record, name, type)
5
5
  value = record.send(name)
6
-
6
+
7
7
  json.set! name, type == :binary ? value&.unpack1('H*') : value
8
8
  end
9
9
 
@@ -17,31 +17,25 @@ module StandardAPI
17
17
  preloads[key] = value
18
18
  when Hash, ActiveSupport::HashWithIndifferentAccess
19
19
  if !value.keys.any? { |x| ['when', 'where', 'limit', 'offset', 'order', 'distinct'].include?(x) }
20
- if !reflection.polymorphic?
21
- preloads[key] = preloadables_hash(reflection.klass, value)
22
- end
20
+ preloads[key.to_sym] = preloadables_hash(value)
23
21
  end
24
22
  end
25
23
  end
26
24
  end
27
25
 
28
- preloads.empty? ? record : record.preload(preloads)
26
+ preloads.present? ? record.preload(preloads) : record
29
27
  end
30
28
 
31
- def preloadables_hash(klass, iclds)
29
+ def preloadables_hash(iclds)
32
30
  preloads = {}
33
31
 
34
32
  iclds.each do |key, value|
35
- if reflection = klass.reflections[key]
36
- case value
37
- when true
38
- preloads[key] = value
39
- when Hash, ActiveSupport::HashWithIndifferentAccess
40
- if !value.keys.any? { |x| ['when', 'where', 'limit', 'offset', 'order', 'distinct'].include?(x) }
41
- if !reflection.polymorphic?
42
- preloads[key] = preloadables_hash(reflection.klass, value)
43
- end
44
- end
33
+ case value
34
+ when true
35
+ preloads[key] = value
36
+ when Hash, ActiveSupport::HashWithIndifferentAccess
37
+ if !value.keys.any? { |x| [ 'when', 'where', 'limit', 'offset', 'order', 'distinct' ].include?(x) }
38
+ preloads[key] = preloadables_hash(value)
45
39
  end
46
40
  end
47
41
  end
@@ -1,3 +1,3 @@
1
1
  module StandardAPI
2
- VERSION = '7.1.0'
2
+ VERSION = '7.1.2'
3
3
  end
@@ -31,11 +31,21 @@ includes.each do |inc, subinc|
31
31
  end
32
32
  else
33
33
  can_cache = can_cache_relation?(record, inc, subinc)
34
- if association.is_a?(ActiveRecord::Reflection::BelongsToReflection)
35
- can_cache = can_cache && !record.send(association.foreign_key).nil?
34
+ cache_key = nil
35
+
36
+ if can_cache
37
+ if association.is_a?(ActiveRecord::Reflection::BelongsToReflection)
38
+ can_cache = can_cache && !record.send(association.foreign_key).nil?
39
+ end
40
+
41
+ if can_cache
42
+ cache_key = association_cache_key(record, inc, subinc)
43
+ can_cache = cache_key.present?
44
+ end
36
45
  end
46
+
37
47
  json.set! inc do
38
- json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
48
+ json.cache_if!(can_cache, can_cache ? cache_key : nil) do
39
49
  value = record.send(inc)
40
50
  if value.nil?
41
51
  json.null!
@@ -69,7 +69,17 @@ else
69
69
  # TODO: it would be nice if rails responded with a true or false here
70
70
  # instead of the function itself
71
71
  json.set! 'auto_populated', !!column.auto_populated? if column.respond_to?(:auto_populated?)
72
- end
72
+
73
+ json.set! 'readonly', (if controller.respond_to?("#{ model.model_name.singular }_attributes")
74
+ !controller.send("#{ model.model_name.singular }_attributes").include?(column.name)
75
+ else
76
+ model.readonly_attribute?(column.name)
77
+ end)
78
+
79
+ visitor = StandardAPI::Visitors::Validator.new
80
+ validations = model.validators.select { |v| v.attributes.include?(column.name.to_sym) }.map { |v| visitor.accept(v, {}) }
81
+ json.set! 'validations', validations
82
+ end
73
83
  end
74
84
  end
75
85
 
@@ -79,6 +79,16 @@ else
79
79
  # TODO: it would be nice if rails responded with a true or false here
80
80
  # instead of the function itself
81
81
  json.set! 'auto_populated', !!column.auto_populated? if column.respond_to?(:auto_populated?)
82
+
83
+ json.set! 'readonly', (if controller.respond_to?("#{ model.model_name.singular }_attributes")
84
+ !controller.send("#{ model.model_name.singular }_attributes").include?(column.name)
85
+ else
86
+ true
87
+ end)
88
+
89
+ visitor = StandardAPI::Visitors::Validator.new
90
+ validations = model.validators.select { |v| v.attributes.include?(column.name.to_sym) }.map { |v| visitor.accept(v, {}) }
91
+ json.set! 'validations', validations
82
92
  end
83
93
  end
84
94
  end
@@ -0,0 +1,60 @@
1
+ module StandardAPI
2
+ module Visitors
3
+
4
+ class Validator < Arel::Visitors::Visitor
5
+
6
+ def visit_ActiveRecord_Validations_AbsenceValidator(o, col)
7
+ visit_validator(:absence, o.options)
8
+ end
9
+
10
+ def visit_ActiveRecord_Validations_AcceptanceValidator(o, col)
11
+ visit_validator(:acceptance, o.options)
12
+ end
13
+
14
+ def visit_ActiveRecord_Validations_ComparisonValidator(o, col)
15
+ visit_validator(:comparison, o.options)
16
+ end
17
+
18
+ def visit_ActiveRecord_Validations_ConfirmationValidator(o, col)
19
+ visit_validator(:confirmation, o.options)
20
+ end
21
+
22
+ def visit_ActiveRecord_Validations_ExclusionValidator(o, col)
23
+ visit_validator(:exclusion, o.options)
24
+ end
25
+
26
+ def visit_ActiveRecord_Validations_FormatValidator(o, col)
27
+ visit_validator(:format, o.options)
28
+ end
29
+
30
+ def visit_ActiveRecord_Validations_InclusionValidator(o, col)
31
+ visit_validator(:inclusion, o.options)
32
+ end
33
+
34
+ def visit_ActiveRecord_Validations_LengthValidator(o, col)
35
+ visit_validator(:length, o.options)
36
+ end
37
+
38
+ def visit_ActiveRecord_Validations_NumericalityValidator(o, col)
39
+ visit_validator(:numericality, o.options)
40
+ end
41
+
42
+ def visit_ActiveRecord_Validations_PresenceValidator(o, col)
43
+ visit_validator(:presence, o.options)
44
+ end
45
+
46
+ def visit_ActiveRecord_Validations_WithValidator(o, col)
47
+ visit_validator(:with, o.options)
48
+ end
49
+
50
+ private
51
+
52
+ def visit_validator(name, options)
53
+ { name => options.empty? ? true : options.as_json }
54
+ end
55
+
56
+
57
+ end
58
+
59
+ end
60
+ end
data/lib/standard_api.rb CHANGED
@@ -16,6 +16,7 @@ require 'standard_api/helpers'
16
16
  require 'standard_api/route_helpers'
17
17
  require 'standard_api/active_record/connection_adapters/postgresql/schema_statements'
18
18
  require 'standard_api/railtie'
19
+ require 'standard_api/visitors/validations'
19
20
 
20
21
  module StandardAPI
21
22
  autoload :AccessControlList, 'standard_api/access_control_list'
@@ -112,7 +112,7 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
112
112
  @controller.params = {}
113
113
  assert_equal 'SELECT "references".* FROM "references" WHERE "references"."subject_id" = 1', @controller.send(:resources).to_sql
114
114
  end
115
-
115
+
116
116
  test "Auto includes on a controller without a model" do
117
117
  @controller = SessionsController.new
118
118
  assert_nil @controller.send(:model)
@@ -154,13 +154,34 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
154
154
  else
155
155
  assert_nil schema.dig('models', model.name, 'attributes', column.name, 'comment')
156
156
  end
157
-
157
+
158
158
  if column.respond_to?(:auto_populated?)
159
159
  assert_equal !!column.auto_populated?, schema.dig('models', model.name, 'attributes', column.name, 'auto_populated')
160
160
  end
161
161
  end
162
162
  end
163
163
 
164
+ assert_equal true, schema['models']['Account']['attributes']['id']['readonly']
165
+ assert_equal false, schema['models']['Account']['attributes']['name']['readonly']
166
+
167
+ assert_equal [
168
+ { "presence" => true }
169
+ ], schema['models']['Property']['attributes']['name']['validations']
170
+
171
+ assert_equal [
172
+ { "numericality" => {
173
+ "greater_than" => 1,
174
+ "greater_than_or_equal_to" => 2,
175
+ "equal_to" => 2,
176
+ "less_than_or_equal_to" => 2,
177
+ "less_than" => 3,
178
+ "other_than" => 0,
179
+ "even" => true,
180
+ "in" => "1..3"
181
+ }
182
+ }
183
+ ], schema['models']['Property']['attributes']['numericality']['validations']
184
+
164
185
  assert_equal 'test comment', schema['comment']
165
186
  end
166
187
 
@@ -263,7 +284,7 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
263
284
  assert JSON(response.body).has_key?('document')
264
285
  assert_nil JSON(response.body)['document']
265
286
  end
266
-
287
+
267
288
  test 'rendering serialize_attribute' do
268
289
  property = create(:property, description: 'This text will magically change')
269
290
  get property_path(property, format: 'json'), params: { id: property.id, magic: true }
@@ -274,15 +295,15 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
274
295
 
275
296
  test 'rendering an enum' do
276
297
  public_document = create(:document, level: 'public')
277
-
298
+
278
299
  get documents_path(format: 'json'), params: { limit: 1 }
279
300
  assert_equal JSON(response.body)[0]['level'], 'public'
280
-
301
+
281
302
  secret_document = create(:document, level: 'secret')
282
303
  get document_path(secret_document, format: 'json')
283
304
  assert_equal JSON(response.body)['level'], 'secret'
284
305
  end
285
-
306
+
286
307
  test '#index.json uses overridden partial' do
287
308
  create(:property, photos: [create(:photo)])
288
309
  get properties_path(format: 'json'), params: { limit: 100, include: [{:photos => { order: :id }}] }
@@ -726,4 +747,36 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
726
747
  assert_equal [1], JSON(response.body)
727
748
  end
728
749
 
750
+ test 'preloading polymorphic associations' do
751
+ p1 = create(:property)
752
+ p2 = create(:property)
753
+ c1 = create(:camera)
754
+ c2 = create(:camera)
755
+ a1 = create(:account, subject: p1, subject_cached_at: Time.now)
756
+ a2 = create(:account, subject: p2, subject_cached_at: Time.now)
757
+ a3 = create(:account, subject: c1, subject_cached_at: Time.now)
758
+ a4 = create(:account, subject: c2, subject_cached_at: Time.now)
759
+ a5 = create(:account, subject: c2, subject_cached_at: Time.now)
760
+
761
+ assert_sql(
762
+ 'SELECT "properties".* FROM "properties" WHERE "properties"."id" IN ($1, $2)',
763
+ 'SELECT "cameras".* FROM "cameras" WHERE "cameras"."id" IN ($1, $2)'
764
+ ) do
765
+ assert_no_sql("SELECT \"properties\".* FROM \"properties\" WHERE \"properties\".\"id\" = $1 LIMIT $2") do
766
+ get accounts_path(limit: 10, include: { subject: { landlord: { when: { subject_type: 'Property' } } } }, format: 'json')
767
+
768
+ assert_equal p1.id, a1.subject_id
769
+ assert_equal p2.id, a2.subject_id
770
+ assert_equal c1.id, a3.subject_id
771
+ assert_equal p1.id, JSON(response.body).dig(0, 'subject', 'id')
772
+ assert_equal p2.id, JSON(response.body).dig(1, 'subject', 'id')
773
+ assert_equal c1.id, JSON(response.body).dig(2, 'subject', 'id')
774
+ assert_equal c2.id, JSON(response.body).dig(3, 'subject', 'id')
775
+ assert_equal c2.id, JSON(response.body).dig(4, 'subject', 'id')
776
+ end
777
+ end
778
+ end
779
+
780
+
781
+
729
782
  end
@@ -9,7 +9,11 @@ module AccountACL
9
9
  end
10
10
 
11
11
  def includes
12
- [ "photos", "subject", "property" ]
12
+ {
13
+ photos: true,
14
+ subject: [ 'landlord' ],
15
+ property: true
16
+ }
13
17
  end
14
18
 
15
19
  end
@@ -7,7 +7,8 @@ module PropertyACL
7
7
  :description,
8
8
  :constructed,
9
9
  :size,
10
- :active
10
+ :active,
11
+ :numericality
11
12
  # :photos_attributes,
12
13
  # { photos_attributes: [ :id, :account_id, :property_id, :format] }
13
14
  ]
@@ -15,7 +16,17 @@ module PropertyACL
15
16
 
16
17
  # Orderings allowed
17
18
  def orders
18
- ["id", "name", "aliases", "description", "constructed", "size", "created_at", "active"]
19
+ [
20
+ "id",
21
+ "name",
22
+ "aliases",
23
+ "description",
24
+ "constructed",
25
+ "size",
26
+ "created_at",
27
+ "active",
28
+ "numericality"
29
+ ]
19
30
  end
20
31
 
21
32
  # Sub resources allowed to be included in the response
@@ -14,7 +14,7 @@ end
14
14
 
15
15
  class Document < ActiveRecord::Base
16
16
  attr_accessor :file
17
-
17
+
18
18
  enum level: { public: 0, secret: 1 }, _suffix: true
19
19
  enum rating: { poor: 0, ok: 1, good: 2 }
20
20
  end
@@ -29,13 +29,25 @@ class Property < ActiveRecord::Base
29
29
  has_one :document_attachments, class_name: "Attachment", as: :record, inverse_of: :record
30
30
  has_one :document, through: "document_attachments"
31
31
 
32
-
33
32
  validates :name, presence: true
34
33
  accepts_nested_attributes_for :photos
35
34
 
36
35
  def english_name
37
36
  'A Name'
38
37
  end
38
+
39
+ # Numericality Validation
40
+ validates :numericality, numericality: {
41
+ greater_than: 1,
42
+ greater_than_or_equal_to: 2,
43
+ equal_to: 2,
44
+ less_than_or_equal_to: 2,
45
+ less_than: 3,
46
+ other_than: 0,
47
+ even: true,
48
+ in: 1..3
49
+ }
50
+
39
51
  end
40
52
 
41
53
  class LSNType < ActiveRecord::Type::Value
@@ -129,6 +141,7 @@ class CreateModelTables < ActiveRecord::Migration[6.0]
129
141
  t.decimal "size"
130
142
  t.datetime "created_at", null: false
131
143
  t.boolean "active", default: false
144
+ t.integer "numericality", default: 2
132
145
  end
133
146
 
134
147
  create_table "references", force: :cascade do |t|
@@ -167,12 +180,12 @@ class CreateModelTables < ActiveRecord::Migration[6.0]
167
180
  t.integer 'record_id'
168
181
  t.integer 'document_id'
169
182
  end
170
-
183
+
171
184
  create_table "uuid_models", id: :uuid, force: :cascade do |t|
172
185
  t.string 'title', default: 'recruit'
173
186
  t.string 'name', default: -> { 'round(random() * 1000)' }
174
187
  end
175
-
188
+
176
189
  end
177
190
 
178
191
  end
@@ -53,6 +53,7 @@ Rails.application.routes.draw do
53
53
  end
54
54
 
55
55
  standard_resource :account
56
+ standard_resources :accounts, only: :index
56
57
  # standard_resources :photos, only: [ :index, :show ]
57
58
 
58
59
  end
@@ -85,16 +85,58 @@ class ActiveSupport::TestCase
85
85
  { :controller => controller_path, :action => action }.merge(options)
86
86
  end
87
87
 
88
- def assert_sql(sql, &block)
89
- queries = []
90
- callback = -> (*, payload) do
91
- queries << payload[:sql]
88
+ def assert_sql(*expected)
89
+ return_value = nil
90
+
91
+ queries_ran = if block_given?
92
+ queries_ran = SQLLogger.log.size
93
+ return_value = yield if block_given?
94
+ SQLLogger.log[queries_ran...]
95
+ else
96
+ [expected.pop]
97
+ end
98
+
99
+ failed_patterns = []
100
+ expected.each do |pattern|
101
+ failed_patterns << pattern unless queries_ran.any?{ |sql| sql_equal(pattern, sql) }
92
102
  end
93
103
 
94
- ActiveSupport::Notifications.subscribed(callback, "sql.active_record", &block)
104
+ assert failed_patterns.empty?, <<~MSG
105
+ Query pattern(s) not found:
106
+ - #{failed_patterns.map{|l| l.gsub(/\n\s*/, " ")}.join('\n - ')}
107
+ Queries Ran (queries_ran.size):
108
+ - #{queries_ran.map{|l| l.gsub(/\n\s*/, "\n ")}.join("\n - ")}
109
+ MSG
95
110
 
96
- assert_not_nil queries.map { |x| x.strip.gsub(/\s+/, ' ') }.
97
- find { |x| x == sql.strip.gsub(/\s+/, ' ') }
111
+ return_value
112
+ end
113
+
114
+ def assert_no_sql(*not_expected)
115
+ return_value = nil
116
+ queries_ran = block_given? ? SQLLogger.log.size : 0
117
+ return_value = yield if block_given?
118
+ ensure
119
+ failed_patterns = []
120
+ queries_ran = SQLLogger.log[queries_ran...]
121
+ not_expected.each do |pattern|
122
+ failed_patterns << pattern if queries_ran.any?{ |sql| sql_equal(pattern, sql) }
123
+ end
124
+ assert failed_patterns.empty?, <<~MSG
125
+ Unexpected Query pattern(s) found:
126
+ - #{failed_patterns.map(&:inspect).join('\n - ')}
127
+ Queries Ran (queries_ran.size):
128
+ - #{queries_ran.map{|l| l.gsub(/\n\s*/, "\n ")}.join("\n - ")}
129
+ MSG
130
+
131
+ return_value
132
+ end
133
+ def sql_equal(expected, sql)
134
+ sql = sql.strip.gsub(/"(\w+)"/, '\1').gsub(/\(\s+/, '(').gsub(/\s+\)/, ')').gsub(/\s+/, ' ')
135
+ if expected.is_a?(String)
136
+ expected = Regexp.new(Regexp.escape(expected.strip.gsub(/"(\w+)"/, '\1').gsub(/\(\s+/, '(').gsub(/\s+\)/, ')').gsub(/\s+/, ' ')), Regexp::IGNORECASE)
137
+ end
138
+
139
+ expected.match(sql)
98
140
  end
99
141
 
100
142
  def assert_rendered(options = {}, message = nil)
@@ -234,6 +276,48 @@ class ActiveSupport::TestCase
234
276
  end
235
277
  end
236
278
 
279
+ class SQLLogger
280
+ class << self
281
+ attr_accessor :ignored_sql, :log, :log_all
282
+ def clear_log; self.log = []; self.log_all = []; end
283
+ end
284
+
285
+ self.clear_log
286
+
287
+ self.ignored_sql = [/^PRAGMA/i, /^SELECT currval/i, /^SELECT CAST/i, /^SELECT @@IDENTITY/i, /^SELECT @@ROWCOUNT/i, /^SAVEPOINT/i, /^ROLLBACK TO SAVEPOINT/i, /^RELEASE SAVEPOINT/i, /^SHOW max_identifier_length/i, /^BEGIN/i, /^COMMIT/i]
288
+
289
+ # FIXME: this needs to be refactored so specific database can add their own
290
+ # ignored SQL, or better yet, use a different notification for the queries
291
+ # instead examining the SQL content.
292
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
293
+ mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
294
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
295
+ sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
296
+
297
+ [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
298
+ ignored_sql.concat db_ignored_sql
299
+ end
300
+
301
+ attr_reader :ignore
302
+
303
+ def initialize(ignore = Regexp.union(self.class.ignored_sql))
304
+ @ignore = ignore
305
+ end
306
+
307
+ def call(name, start, finish, message_id, values)
308
+ sql = values[:sql]
309
+
310
+ # FIXME: this seems bad. we should probably have a better way to indicate
311
+ # the query was cached
312
+ return if 'CACHE' == values[:name]
313
+
314
+ self.class.log_all << sql
315
+ # puts sql
316
+ self.class.log << sql unless ignore =~ sql
317
+ end
318
+ end
319
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLLogger.new)
320
+
237
321
  end
238
322
 
239
323
  class ActionController::TestCase
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standardapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.0
4
+ version: 7.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Bracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-30 00:00:00.000000000 Z
11
+ date: 2024-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -306,6 +306,7 @@ files:
306
306
  - lib/standard_api/views/application/schema.streamer
307
307
  - lib/standard_api/views/application/show.json.jbuilder
308
308
  - lib/standard_api/views/application/show.streamer
309
+ - lib/standard_api/visitors/validations.rb
309
310
  - test/standard_api/caching_test.rb
310
311
  - test/standard_api/controller/include_test.rb
311
312
  - test/standard_api/controller/subresource_test.rb
@@ -361,7 +362,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
361
362
  - !ruby/object:Gem::Version
362
363
  version: '0'
363
364
  requirements: []
364
- rubygems_version: 3.5.9
365
+ rubygems_version: 3.5.15
365
366
  signing_key:
366
367
  specification_version: 4
367
368
  summary: StandardAPI makes it easy to expose a query interface for your Rails models