standardapi 6.0.0.32 → 6.1.0
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.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
|