standardapi 6.0.0.32 → 6.1.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 +4 -4
- data/lib/standard_api.rb +4 -0
- data/lib/standard_api/access_control_list.rb +114 -0
- data/lib/standard_api/controller.rb +32 -11
- data/lib/standard_api/helpers.rb +4 -3
- data/lib/standard_api/includes.rb +9 -0
- data/lib/standard_api/middleware/query_encoding.rb +3 -3
- data/lib/standard_api/railtie.rb +13 -2
- data/lib/standard_api/route_helpers.rb +5 -5
- data/lib/standard_api/test_case.rb +13 -3
- data/lib/standard_api/test_case/calculate_tests.rb +8 -2
- data/lib/standard_api/test_case/create_tests.rb +7 -9
- data/lib/standard_api/test_case/index_tests.rb +10 -0
- data/lib/standard_api/test_case/schema_tests.rb +7 -1
- data/lib/standard_api/test_case/show_tests.rb +1 -0
- data/lib/standard_api/test_case/update_tests.rb +8 -9
- data/lib/standard_api/version.rb +1 -1
- data/lib/standard_api/views/application/_record.json.jbuilder +14 -14
- data/lib/standard_api/views/application/_record.streamer +36 -34
- data/lib/standard_api/views/application/_schema.json.jbuilder +1 -1
- data/lib/standard_api/views/application/_schema.streamer +1 -1
- data/lib/standard_api/views/application/new.streamer +1 -1
- data/test/standard_api/caching_test.rb +14 -4
- data/test/standard_api/helpers_test.rb +25 -9
- data/test/standard_api/standard_api_test.rb +122 -14
- data/test/standard_api/test_app/app/controllers/acl/account_acl.rb +15 -0
- data/test/standard_api/test_app/app/controllers/acl/property_acl.rb +27 -0
- data/test/standard_api/test_app/app/controllers/acl/reference_acl.rb +7 -0
- data/test/standard_api/test_app/controllers.rb +13 -45
- data/test/standard_api/test_app/models.rb +17 -5
- data/test/standard_api/test_app/test/factories.rb +4 -3
- data/test/standard_api/test_app/views/photos/_photo.json.jbuilder +1 -0
- data/test/standard_api/test_app/views/photos/_photo.streamer +2 -1
- data/test/standard_api/test_helper.rb +12 -0
- metadata +19 -15
@@ -6,13 +6,13 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
6
6
|
|
7
7
|
self.includes = [ :photos, :landlord, :english_name ]
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
def normalizers
|
10
|
+
{
|
11
|
+
Property => {
|
12
|
+
"size" => lambda { |value| value.round(4).to_s }
|
13
|
+
}
|
14
|
+
}
|
15
|
+
end
|
16
16
|
|
17
17
|
# = Routing Tests
|
18
18
|
#
|
@@ -94,7 +94,8 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
94
94
|
|
95
95
|
test 'Controller#model_params defaults to []' do
|
96
96
|
@controller = ReferencesController.new
|
97
|
-
|
97
|
+
@controller.params = {}
|
98
|
+
assert_equal @controller.send(:model_params), ActionController::Parameters.new
|
98
99
|
end
|
99
100
|
|
100
101
|
test 'Controller#current_mask' do
|
@@ -123,8 +124,9 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
123
124
|
|
124
125
|
model.columns.each do |column|
|
125
126
|
assert_equal json_column_type(column.sql_type), schema.dig('models', model.name, 'attributes', column.name, 'type')
|
126
|
-
default = column.default
|
127
|
+
default = column.default
|
127
128
|
if default then
|
129
|
+
default = model.connection.lookup_cast_type_from_column(column).deserialize(default)
|
128
130
|
assert_equal default, schema.dig('models', model.name, 'attributes', column.name, 'default')
|
129
131
|
else
|
130
132
|
assert_nil schema.dig('models', model.name, 'attributes', column.name, 'default')
|
@@ -190,7 +192,7 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
190
192
|
property = create(:property, photos: [])
|
191
193
|
photo = create(:photo)
|
192
194
|
|
193
|
-
post "/properties/#{property.id}/photos/#{photo.id}
|
195
|
+
post "/properties/#{property.id}/photos/#{photo.id}"
|
194
196
|
assert_equal property.photos.reload.map(&:id), [photo.id]
|
195
197
|
assert_response :created
|
196
198
|
|
@@ -198,9 +200,18 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
198
200
|
assert_response :not_found
|
199
201
|
end
|
200
202
|
|
203
|
+
test 'Controller#add_resource with has_one' do
|
204
|
+
photo = create(:document)
|
205
|
+
property = create(:property)
|
206
|
+
post "/properties/#{property.id}/document/#{photo.id}"
|
207
|
+
assert_equal property.reload.document, photo
|
208
|
+
assert_response :created
|
209
|
+
end
|
210
|
+
|
201
211
|
test 'Controller#remove_resource' do
|
202
212
|
photo = create(:photo)
|
203
213
|
property = create(:property, photos: [photo])
|
214
|
+
assert_equal property.photos.reload, [photo]
|
204
215
|
delete "/properties/#{property.id}/photos/#{photo.id}"
|
205
216
|
assert_equal property.photos.reload, []
|
206
217
|
assert_response :no_content
|
@@ -209,6 +220,15 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
209
220
|
assert_response :not_found
|
210
221
|
end
|
211
222
|
|
223
|
+
test 'Controller#remove_resource with has_one' do
|
224
|
+
photo = create(:document)
|
225
|
+
property = create(:property, document: photo)
|
226
|
+
assert_equal property.document, photo
|
227
|
+
delete "/properties/#{property.id}/document/#{photo.id}"
|
228
|
+
assert_nil property.reload.document
|
229
|
+
assert_response :no_content
|
230
|
+
end
|
231
|
+
|
212
232
|
# = View Tests
|
213
233
|
|
214
234
|
test 'rendering tables' do
|
@@ -222,7 +242,7 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
222
242
|
test 'rendering null attribute' do
|
223
243
|
property = create(:property)
|
224
244
|
get property_path(property, format: 'json'), params: { id: property.id, include: [:landlord] }
|
225
|
-
|
245
|
+
assert JSON(response.body).has_key?('landlord')
|
226
246
|
assert_nil JSON(response.body)['landlord']
|
227
247
|
end
|
228
248
|
|
@@ -320,14 +340,22 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
320
340
|
test 'include with where key' do
|
321
341
|
photo_a = create(:photo)
|
322
342
|
photo_b = create(:photo)
|
343
|
+
photo_c = create(:photo)
|
323
344
|
|
324
|
-
property = create(:property, photos: [photo_b])
|
345
|
+
property = create(:property, photos: [photo_b, photo_c])
|
325
346
|
get property_path(property, include: { photos: { where: { id: photo_a.id } } }, format: :json)
|
326
347
|
assert_equal [], JSON(response.body)['photos']
|
327
348
|
|
328
349
|
property.photos << photo_a
|
329
350
|
get property_path(property, include: { photos: { where: { id: photo_a.id } } }, format: :json)
|
330
351
|
assert_equal [photo_a.id], JSON(response.body)['photos'].map { |x| x['id'] }
|
352
|
+
get property_path(property, include: { photos: { where: [
|
353
|
+
{ id: photo_a.id },
|
354
|
+
'OR',
|
355
|
+
{ id: photo_c.id}
|
356
|
+
] } }, format: :json)
|
357
|
+
assert_equal [photo_a.id, photo_c.id].sort,
|
358
|
+
JSON(response.body)['photos'].map { |x| x['id'] }.sort
|
331
359
|
end
|
332
360
|
|
333
361
|
test 'include with order key' do
|
@@ -338,6 +366,14 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
338
366
|
assert_equal photos.map(&:id).sort, JSON(response.body)['photos'].map { |x| x['id'] }
|
339
367
|
end
|
340
368
|
|
369
|
+
test 'include relation with default order using an order key' do
|
370
|
+
p1 = create(:photo)
|
371
|
+
p2 = create(:photo)
|
372
|
+
account = create(:account, photos: [ p1, p2 ])
|
373
|
+
get account_path(account, include: { photos: { order: { created_at: :desc } } }, format: 'json')
|
374
|
+
assert_equal [ p2.id, p1.id ], JSON(response.body)['photos'].map { |x| x["id"] }
|
375
|
+
end
|
376
|
+
|
341
377
|
test 'include with limit key' do
|
342
378
|
5.times { create(:property, photos: Array.new(5) { create(:photo) }) }
|
343
379
|
get properties_path(include: { photos: { limit: 1 } }, limit: 5, format: 'json')
|
@@ -380,8 +416,8 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
380
416
|
|
381
417
|
json = JSON(response.body)
|
382
418
|
|
383
|
-
assert_equal json.find { |x| x['id'] == account_reference.id }.dig('subject', 'photos', 0, 'id')
|
384
|
-
assert_equal json.find { |x| x['id'] == property_reference.id }.dig('subject', 'landlord', 'id')
|
419
|
+
assert_equal photo.id, json.find { |x| x['id'] == account_reference.id }.dig('subject', 'photos', 0, 'id')
|
420
|
+
assert_equal account.id, json.find { |x| x['id'] == property_reference.id }.dig('subject', 'landlord', 'id')
|
385
421
|
end
|
386
422
|
|
387
423
|
test 'include with distinct key' do
|
@@ -549,6 +585,19 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
549
585
|
assert_equal properties.map(&:id).sort.reverse, JSON(response.body).map { |x| x['id'] }
|
550
586
|
end
|
551
587
|
|
588
|
+
test 'ordering via nulls_first/last' do
|
589
|
+
p1 = create(:property, description: 'test')
|
590
|
+
p2 = create(:property, description: nil)
|
591
|
+
|
592
|
+
get properties_path(format: 'json'), params: { limit: 100, order: { description: { desc: 'nulls_last' } } }
|
593
|
+
properties = JSON(response.body)
|
594
|
+
assert_equal p1.id, properties.first['id']
|
595
|
+
|
596
|
+
get properties_path(format: 'json'), params: { limit: 100, order: { description: { asc: 'nulls_last' } } }
|
597
|
+
properties = JSON(response.body)
|
598
|
+
assert_equal p1.id, properties.first['id']
|
599
|
+
end
|
600
|
+
|
552
601
|
# Calculate Test
|
553
602
|
test 'calculate' do
|
554
603
|
create(:photo)
|
@@ -556,6 +605,65 @@ class PropertiesControllerTest < ActionDispatch::IntegrationTest
|
|
556
605
|
assert_equal [1], JSON(response.body)
|
557
606
|
end
|
558
607
|
|
608
|
+
test 'calculate distinct aggregation' do
|
609
|
+
assert_sql(<<-SQL) do
|
610
|
+
SELECT DISTINCT COUNT("properties"."id") FROM "properties"
|
611
|
+
SQL
|
612
|
+
create(:property)
|
613
|
+
create(:property)
|
614
|
+
get '/properties/calculate', params: {
|
615
|
+
select: { count: "id" },
|
616
|
+
distinct: true
|
617
|
+
}
|
618
|
+
assert_equal [2], JSON(response.body)
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
test 'calculate aggregation distinctly' do
|
623
|
+
assert_sql(<<-SQL) do
|
624
|
+
SELECT COUNT(DISTINCT "properties"."id") FROM "properties"
|
625
|
+
SQL
|
626
|
+
create(:property)
|
627
|
+
create(:property)
|
628
|
+
get '/properties/calculate', params: {
|
629
|
+
select: { count: { distinct: "id" } }
|
630
|
+
}
|
631
|
+
assert_equal [2], JSON(response.body)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
test 'calculate distinct aggregation distinctly' do
|
636
|
+
assert_sql(<<-SQL) do
|
637
|
+
SELECT DISTINCT COUNT(DISTINCT "properties"."id") FROM "properties"
|
638
|
+
SQL
|
639
|
+
create(:property)
|
640
|
+
create(:property)
|
641
|
+
get '/properties/calculate', params: {
|
642
|
+
select: { count: { distinct: "id" } },
|
643
|
+
distinct: true
|
644
|
+
}
|
645
|
+
assert_equal [2], JSON(response.body)
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
test 'calculate distinct count' do
|
650
|
+
p1 = create(:property)
|
651
|
+
p2 = create(:property)
|
652
|
+
a1 = create(:account, property: p1)
|
653
|
+
a2 = create(:account, property: p2)
|
654
|
+
get '/properties/calculate', params: {
|
655
|
+
select: { count: 'id' },
|
656
|
+
where: {
|
657
|
+
accounts: {
|
658
|
+
id: [a1.id, a2.id]
|
659
|
+
}
|
660
|
+
},
|
661
|
+
group_by: 'id',
|
662
|
+
distinct: true
|
663
|
+
}
|
664
|
+
assert_equal Hash[p1.id.to_s, 1, p2.id.to_s, 1], JSON(response.body)
|
665
|
+
end
|
666
|
+
|
559
667
|
test 'calculate group_by' do
|
560
668
|
create(:photo, format: 'jpg')
|
561
669
|
create(:photo, format: 'jpg')
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PropertyACL
|
2
|
+
|
3
|
+
def attributes
|
4
|
+
[ :name,
|
5
|
+
:aliases,
|
6
|
+
:description,
|
7
|
+
:constructed,
|
8
|
+
:size,
|
9
|
+
:active
|
10
|
+
# :photos_attributes,
|
11
|
+
# { photos_attributes: [ :id, :account_id, :property_id, :format] }
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
def orders
|
16
|
+
["id", "name", "aliases", "description", "constructed", "size", "created_at", "active"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def includes
|
20
|
+
[ :photos, :landlord, :english_name, :document ]
|
21
|
+
end
|
22
|
+
|
23
|
+
def nested
|
24
|
+
[ :photos ]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -1,77 +1,45 @@
|
|
1
1
|
class ApplicationController < ActionController::Base
|
2
2
|
include StandardAPI::Controller
|
3
|
+
include StandardAPI::AccessControlList
|
3
4
|
prepend_view_path File.join(File.dirname(__FILE__), 'views')
|
4
5
|
|
5
|
-
private
|
6
|
-
|
7
|
-
def account_params
|
8
|
-
[ "property_id", "name" ]
|
9
|
-
end
|
10
|
-
|
11
|
-
def account_orders
|
12
|
-
[ "id" ]
|
13
|
-
end
|
14
|
-
|
15
|
-
def account_includes
|
16
|
-
[ "photos" ]
|
17
|
-
end
|
18
|
-
|
19
|
-
def property_params
|
20
|
-
[ :name,
|
21
|
-
:aliases,
|
22
|
-
:description,
|
23
|
-
:constructed,
|
24
|
-
:size,
|
25
|
-
:active,
|
26
|
-
:photos_attributes,
|
27
|
-
{ photos_attributes: [ :id, :account_id, :property_id, :format] }
|
28
|
-
]
|
29
|
-
end
|
30
|
-
|
31
|
-
def property_orders
|
32
|
-
["id", "name", "aliases", "description", "constructed", "size", "created_at", "active"]
|
33
|
-
end
|
34
|
-
|
35
|
-
def property_includes
|
36
|
-
[:photos, :landlord, :english_name, :document]
|
37
|
-
end
|
38
|
-
|
39
|
-
def reference_includes
|
40
|
-
{ subject: [ :landlord, :photos ] }
|
41
|
-
end
|
42
|
-
|
43
6
|
end
|
44
7
|
|
45
8
|
class PropertiesController < ApplicationController
|
46
9
|
end
|
47
10
|
|
48
11
|
class AccountsController < ApplicationController
|
12
|
+
|
13
|
+
def show
|
14
|
+
@account = Account.last
|
15
|
+
end
|
16
|
+
|
49
17
|
end
|
50
18
|
|
51
19
|
class DocumentsController < ApplicationController
|
52
20
|
|
53
|
-
def
|
21
|
+
def document_attributes
|
54
22
|
[ :file, :type ]
|
55
23
|
end
|
56
|
-
|
24
|
+
|
57
25
|
def document_orders
|
58
|
-
[:id]
|
26
|
+
[ :id ]
|
59
27
|
end
|
60
28
|
|
61
29
|
end
|
62
30
|
|
63
31
|
class PhotosController < ApplicationController
|
64
32
|
|
65
|
-
def
|
33
|
+
def photo_attributes
|
66
34
|
[ :id, :account_id, :property_id, :format ]
|
67
35
|
end
|
68
36
|
|
69
37
|
def photo_orders
|
70
|
-
[:id]
|
38
|
+
[ :id ]
|
71
39
|
end
|
72
40
|
|
73
41
|
def photo_includes
|
74
|
-
[:account]
|
42
|
+
[ :account ]
|
75
43
|
end
|
76
44
|
|
77
45
|
end
|
@@ -104,4 +72,4 @@ class DefaultLimitController < ApplicationController
|
|
104
72
|
100
|
105
73
|
end
|
106
74
|
|
107
|
-
end
|
75
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# = Models
|
2
2
|
|
3
3
|
class Account < ActiveRecord::Base
|
4
|
-
has_many :photos
|
4
|
+
has_many :photos, -> { order(:created_at) }
|
5
5
|
belongs_to :property
|
6
|
+
belongs_to :subject, polymorphic: true
|
6
7
|
end
|
7
8
|
|
8
9
|
class Photo < ActiveRecord::Base
|
@@ -24,6 +25,7 @@ class Property < ActiveRecord::Base
|
|
24
25
|
has_one :document_attachments, class_name: "Attachment", as: :record, inverse_of: :record
|
25
26
|
has_one :document, through: "document_attachments"
|
26
27
|
|
28
|
+
|
27
29
|
validates :name, presence: true
|
28
30
|
accepts_nested_attributes_for :photos
|
29
31
|
|
@@ -59,13 +61,23 @@ class CreateModelTables < ActiveRecord::Migration[6.0]
|
|
59
61
|
create_table "accounts", force: :cascade do |t|
|
60
62
|
t.string 'name', limit: 255
|
61
63
|
t.integer 'property_id'
|
64
|
+
t.integer "subject_id"
|
65
|
+
t.string "subject_type"
|
66
|
+
t.datetime "property_cached_at"
|
67
|
+
t.datetime "subject_cached_at"
|
62
68
|
t.integer 'photos_count', null: false, default: 0
|
69
|
+
t.datetime "created_at", null: false
|
70
|
+
end
|
71
|
+
|
72
|
+
create_table "landlords", force: :cascade do |t|
|
73
|
+
t.string "name"
|
63
74
|
end
|
64
75
|
|
65
76
|
create_table "photos", force: :cascade do |t|
|
66
77
|
t.integer "account_id"
|
67
78
|
t.integer "property_id"
|
68
79
|
t.string "format", limit: 255
|
80
|
+
t.datetime "created_at", null: false
|
69
81
|
end
|
70
82
|
|
71
83
|
create_table "properties", force: :cascade do |t|
|
@@ -84,12 +96,12 @@ class CreateModelTables < ActiveRecord::Migration[6.0]
|
|
84
96
|
t.string "key"
|
85
97
|
t.string "value"
|
86
98
|
end
|
87
|
-
|
99
|
+
|
88
100
|
create_table "photos_properties", force: :cascade do |t|
|
89
101
|
t.integer "photo_id"
|
90
102
|
t.integer "property_id"
|
91
103
|
end
|
92
|
-
|
104
|
+
|
93
105
|
create_table "landlords_properties", force: :cascade do |t|
|
94
106
|
t.integer "landlord_id"
|
95
107
|
t.integer "property_id"
|
@@ -98,8 +110,8 @@ class CreateModelTables < ActiveRecord::Migration[6.0]
|
|
98
110
|
create_table "documents", force: :cascade do |t|
|
99
111
|
t.string 'type'
|
100
112
|
end
|
101
|
-
|
102
|
-
create_table "attachments", force: :cascade do |t|
|
113
|
+
|
114
|
+
create_table "attachments", force: :cascade do |t|
|
103
115
|
t.string 'record_type'
|
104
116
|
t.integer 'record_id'
|
105
117
|
t.integer 'document_id'
|
@@ -1,4 +1,5 @@
|
|
1
1
|
FactoryBot.define do
|
2
|
+
|
2
3
|
factory :account do
|
3
4
|
name { Faker::Name.name }
|
4
5
|
|
@@ -7,7 +8,7 @@ FactoryBot.define do
|
|
7
8
|
name { nil }
|
8
9
|
end
|
9
10
|
end
|
10
|
-
|
11
|
+
|
11
12
|
factory :landlord do
|
12
13
|
name { Faker::Name.name }
|
13
14
|
end
|
@@ -17,12 +18,12 @@ FactoryBot.define do
|
|
17
18
|
end
|
18
19
|
|
19
20
|
factory :document do
|
20
|
-
file {
|
21
|
+
file { Rack::Test::UploadedFile.new(File.join(Rails.root, 'test/fixtures/photo.png'), 'image/png') }
|
21
22
|
end
|
22
23
|
|
23
24
|
factory :pdf do
|
24
25
|
type { 'Pdf' }
|
25
|
-
file {
|
26
|
+
file { Rack::Test::UploadedFile.new(File.join(Rails.root, 'test/fixtures/photo.png'), 'image/png') }
|
26
27
|
end
|
27
28
|
|
28
29
|
factory :reference do
|