standardapi 7.1.0 → 7.1.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 +4 -4
- data/lib/standard_api/helpers.rb +11 -17
- data/lib/standard_api/version.rb +1 -1
- data/lib/standard_api/views/application/_record.json.jbuilder +13 -3
- data/test/standard_api/standard_api_test.rb +36 -6
- data/test/standard_api/test_app/app/controllers/acl/account_acl.rb +5 -1
- data/test/standard_api/test_app.rb +1 -0
- data/test/standard_api/test_helper.rb +91 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf0f6e2d86659b79df8b56eb7b998118541facdc0fd516ad878f4afb0c531299
|
4
|
+
data.tar.gz: dcfebcfd01bd96dbaa8a4a2aef53ea1549c0820b7879845a2e812311efbd3556
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7b0189b209ce50457ba90a8f2c89653557f1f7ff61b0a955f479c40beefc8666f47e9dfe9c2e6b17193fc197510305ff442ea49c0bf31ab482011d729b7b09e
|
7
|
+
data.tar.gz: a31458fac536e7d0e074a3c4c5efee56123f264771316503df1ff69b8a4539e782e29c109d5c9f89a789d5f54b576300fde5de91852dc4a67a526b665bc793ff
|
data/lib/standard_api/helpers.rb
CHANGED
@@ -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
|
-
|
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.
|
26
|
+
preloads.present? ? record.preload(preloads) : record
|
29
27
|
end
|
30
28
|
|
31
|
-
def preloadables_hash(
|
29
|
+
def preloadables_hash(iclds)
|
32
30
|
preloads = {}
|
33
31
|
|
34
32
|
iclds.each do |key, value|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
when
|
40
|
-
|
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
|
data/lib/standard_api/version.rb
CHANGED
@@ -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
|
-
|
35
|
-
|
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 ?
|
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!
|
@@ -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,7 +154,7 @@ 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
|
@@ -263,7 +263,7 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
263
263
|
assert JSON(response.body).has_key?('document')
|
264
264
|
assert_nil JSON(response.body)['document']
|
265
265
|
end
|
266
|
-
|
266
|
+
|
267
267
|
test 'rendering serialize_attribute' do
|
268
268
|
property = create(:property, description: 'This text will magically change')
|
269
269
|
get property_path(property, format: 'json'), params: { id: property.id, magic: true }
|
@@ -274,15 +274,15 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
274
274
|
|
275
275
|
test 'rendering an enum' do
|
276
276
|
public_document = create(:document, level: 'public')
|
277
|
-
|
277
|
+
|
278
278
|
get documents_path(format: 'json'), params: { limit: 1 }
|
279
279
|
assert_equal JSON(response.body)[0]['level'], 'public'
|
280
|
-
|
280
|
+
|
281
281
|
secret_document = create(:document, level: 'secret')
|
282
282
|
get document_path(secret_document, format: 'json')
|
283
283
|
assert_equal JSON(response.body)['level'], 'secret'
|
284
284
|
end
|
285
|
-
|
285
|
+
|
286
286
|
test '#index.json uses overridden partial' do
|
287
287
|
create(:property, photos: [create(:photo)])
|
288
288
|
get properties_path(format: 'json'), params: { limit: 100, include: [{:photos => { order: :id }}] }
|
@@ -726,4 +726,34 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
726
726
|
assert_equal [1], JSON(response.body)
|
727
727
|
end
|
728
728
|
|
729
|
+
test 'preloading polymorphic associations' do
|
730
|
+
p1 = create(:property)
|
731
|
+
p2 = create(:property)
|
732
|
+
c1 = create(:camera)
|
733
|
+
c2 = create(:camera)
|
734
|
+
a1 = create(:account, subject: p1, subject_cached_at: Time.now)
|
735
|
+
a2 = create(:account, subject: p2, subject_cached_at: Time.now)
|
736
|
+
a3 = create(:account, subject: c1, subject_cached_at: Time.now)
|
737
|
+
a4 = create(:account, subject: c2, subject_cached_at: Time.now)
|
738
|
+
a5 = create(:account, subject: c2, subject_cached_at: Time.now)
|
739
|
+
|
740
|
+
assert_sql(
|
741
|
+
'SELECT "properties".* FROM "properties" WHERE "properties"."id" IN ($1, $2)',
|
742
|
+
'SELECT "cameras".* FROM "cameras" WHERE "cameras"."id" IN ($1, $2)'
|
743
|
+
) do
|
744
|
+
assert_no_sql("SELECT \"properties\".* FROM \"properties\" WHERE \"properties\".\"id\" = $1 LIMIT $2") do
|
745
|
+
get accounts_path(limit: 10, include: { subject: { landlord: { when: { subject_type: 'Property' } } } }, format: 'json')
|
746
|
+
|
747
|
+
assert_equal p1.id, a1.subject_id
|
748
|
+
assert_equal p2.id, a2.subject_id
|
749
|
+
assert_equal c1.id, a3.subject_id
|
750
|
+
assert_equal p1.id, JSON(response.body).dig(0, 'subject', 'id')
|
751
|
+
assert_equal p2.id, JSON(response.body).dig(1, 'subject', 'id')
|
752
|
+
assert_equal c1.id, JSON(response.body).dig(2, 'subject', 'id')
|
753
|
+
assert_equal c2.id, JSON(response.body).dig(3, 'subject', 'id')
|
754
|
+
assert_equal c2.id, JSON(response.body).dig(4, 'subject', 'id')
|
755
|
+
end
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
729
759
|
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(
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
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
|
-
|
97
|
-
|
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.
|
4
|
+
version: 7.1.1
|
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-
|
11
|
+
date: 2024-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|