standardapi 7.1.0 → 7.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|