standardapi 6.0.0.15 → 6.0.0.29
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/README.md +15 -6
- data/lib/standard_api.rb +2 -0
- data/lib/standard_api/active_record/connection_adapters/postgresql/schema_statements.rb +21 -0
- data/lib/standard_api/controller.rb +77 -66
- data/lib/standard_api/errors.rb +13 -0
- data/lib/standard_api/helpers.rb +67 -15
- data/lib/standard_api/includes.rb +22 -12
- data/lib/standard_api/orders.rb +4 -6
- data/lib/standard_api/railtie.rb +1 -1
- data/lib/standard_api/route_helpers.rb +4 -0
- data/lib/standard_api/test_case/calculate_tests.rb +5 -3
- data/lib/standard_api/test_case/create_tests.rb +0 -3
- data/lib/standard_api/test_case/schema_tests.rb +19 -3
- data/lib/standard_api/version.rb +1 -1
- data/lib/standard_api/views/application/_record.json.jbuilder +11 -10
- data/lib/standard_api/views/application/_record.streamer +11 -10
- data/lib/standard_api/views/application/_schema.json.jbuilder +68 -0
- data/lib/standard_api/views/application/_schema.streamer +78 -0
- data/lib/standard_api/views/application/index.json.jbuilder +9 -16
- data/lib/standard_api/views/application/index.streamer +9 -16
- data/lib/standard_api/views/application/schema.json.jbuilder +1 -12
- data/lib/standard_api/views/application/schema.streamer +1 -16
- data/lib/standard_api/views/application/show.json.jbuilder +8 -1
- data/lib/standard_api/views/application/show.streamer +8 -1
- data/test/standard_api/test_app.rb +55 -0
- data/test/standard_api/test_app/config/database.yml +4 -0
- data/test/standard_api/test_app/controllers.rb +107 -0
- data/test/standard_api/test_app/models.rb +94 -0
- data/test/standard_api/test_app/test/factories.rb +50 -0
- data/test/standard_api/test_app/test/fixtures/photo.png +0 -0
- data/test/standard_api/test_app/views/photos/_photo.json.jbuilder +15 -0
- data/test/standard_api/test_app/views/photos/_photo.streamer +17 -0
- data/test/standard_api/test_app/views/photos/_schema.json.jbuilder +1 -0
- data/test/standard_api/test_app/views/photos/_schema.streamer +3 -0
- data/test/standard_api/test_app/views/photos/schema.json.jbuilder +1 -0
- data/test/standard_api/test_app/views/photos/schema.streamer +1 -0
- data/test/standard_api/test_app/views/properties/edit.html.erb +1 -0
- data/test/standard_api/test_app/views/sessions/new.html.erb +0 -0
- metadata +27 -8
@@ -17,17 +17,29 @@ module StandardAPI
|
|
17
17
|
includes.flatten.compact.each { |v| normalized.merge!(normalize(v)) }
|
18
18
|
when Hash, ActionController::Parameters
|
19
19
|
includes.each_pair do |k, v|
|
20
|
-
|
21
|
-
|
20
|
+
normalized[k] = case k.to_s
|
21
|
+
when 'when', 'where', 'order'
|
22
|
+
case v
|
22
23
|
when Hash then v.to_h
|
23
24
|
when ActionController::Parameters then v.to_unsafe_h
|
24
25
|
end
|
25
|
-
|
26
|
-
|
26
|
+
when 'limit'
|
27
|
+
case v
|
28
|
+
when String then v.to_i
|
29
|
+
when Integer then v
|
30
|
+
end
|
31
|
+
when 'distinct'
|
32
|
+
case v
|
33
|
+
when 'true' then true
|
34
|
+
when 'false' then false
|
35
|
+
end
|
36
|
+
when 'distinct_on'
|
37
|
+
case v
|
27
38
|
when String then v
|
39
|
+
when Array then v
|
28
40
|
end
|
29
41
|
else
|
30
|
-
|
42
|
+
normalize(v)
|
31
43
|
end
|
32
44
|
end
|
33
45
|
when nil
|
@@ -58,14 +70,12 @@ module StandardAPI
|
|
58
70
|
|
59
71
|
permit = normalize(permit.with_indifferent_access)
|
60
72
|
includes.each do |k, v|
|
61
|
-
if permit.has_key?(k)
|
62
|
-
|
73
|
+
permitted[k] = if permit.has_key?(k)
|
74
|
+
sanitize(v, permit[k] || {}, true)
|
75
|
+
elsif ['limit', 'when', 'where', 'order', 'distinct', 'distinct_on'].include?(k.to_s)
|
76
|
+
v
|
63
77
|
else
|
64
|
-
|
65
|
-
raise ActionController::UnpermittedParameters.new([k])
|
66
|
-
else
|
67
|
-
Rails.logger.try(:warn, "Invalid Include: #{k}")
|
68
|
-
end
|
78
|
+
raise StandardAPI::UnpermittedParameters.new([k])
|
69
79
|
end
|
70
80
|
end
|
71
81
|
|
data/lib/standard_api/orders.rb
CHANGED
@@ -15,11 +15,11 @@ module StandardAPI
|
|
15
15
|
key2, key3 = *key.to_s.split('.')
|
16
16
|
permitted << sanitize({key2.to_sym => { key3.to_sym => value } }, permit)
|
17
17
|
elsif permit.include?(key.to_s)
|
18
|
-
case value
|
18
|
+
value = case value
|
19
19
|
when Hash
|
20
20
|
value
|
21
21
|
when ActionController::Parameters
|
22
|
-
value.
|
22
|
+
value.permit([:asc, :desc]).to_h
|
23
23
|
else
|
24
24
|
value
|
25
25
|
end
|
@@ -29,7 +29,7 @@ module StandardAPI
|
|
29
29
|
sanitized_value = sanitize(value, subpermit)
|
30
30
|
permitted << { key.to_sym => sanitized_value }
|
31
31
|
else
|
32
|
-
raise(
|
32
|
+
raise(StandardAPI::UnpermittedParameters.new([orders]))
|
33
33
|
end
|
34
34
|
end
|
35
35
|
when Array
|
@@ -48,7 +48,7 @@ module StandardAPI
|
|
48
48
|
elsif permit.include?(orders.to_s)
|
49
49
|
permitted = orders
|
50
50
|
else
|
51
|
-
raise(
|
51
|
+
raise(StandardAPI::UnpermittedParameters.new([orders]))
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
@@ -57,8 +57,6 @@ module StandardAPI
|
|
57
57
|
else
|
58
58
|
permitted
|
59
59
|
end
|
60
|
-
|
61
|
-
# permitted
|
62
60
|
end
|
63
61
|
|
64
62
|
end
|
data/lib/standard_api/railtie.rb
CHANGED
@@ -24,6 +24,8 @@ module StandardAPI
|
|
24
24
|
resources(*resources, options) do
|
25
25
|
get :schema, on: :collection
|
26
26
|
get :calculate, on: :collection
|
27
|
+
delete ':relationship/:resource_id' => :remove_resource, on: :member
|
28
|
+
post ':relationship/:resource_id' => :add_resource, on: :member
|
27
29
|
block.call if block
|
28
30
|
end
|
29
31
|
end
|
@@ -51,6 +53,8 @@ module StandardAPI
|
|
51
53
|
resource(*resource, options) do
|
52
54
|
get :schema, on: :collection
|
53
55
|
get :calculate, on: :collection
|
56
|
+
delete ':relationship/:resource_id' => :remove_resource, on: :member
|
57
|
+
post ':relationship/:resource_id' => :add_resource, on: :member
|
54
58
|
block.call if block
|
55
59
|
end
|
56
60
|
end
|
@@ -12,7 +12,7 @@ module StandardAPI
|
|
12
12
|
create_model
|
13
13
|
|
14
14
|
math_column = model.columns.find { |x| CALCULATE_COLUMN_TYPES.include?(x.sql_type) }
|
15
|
-
|
15
|
+
|
16
16
|
if math_column
|
17
17
|
column = math_column
|
18
18
|
selects = [{ count: column.name }, { maximum: column.name }, { minimum: column.name }, { average: column.name }]
|
@@ -24,7 +24,9 @@ module StandardAPI
|
|
24
24
|
get resource_path(:calculate, select: selects, format: :json)
|
25
25
|
assert_response :ok
|
26
26
|
calculations = @controller.instance_variable_get('@calculations')
|
27
|
-
|
27
|
+
expectations = selects.map { |s| model.send(s.keys.first, column.name) }
|
28
|
+
expectations = [expectations] if expectations.length > 1
|
29
|
+
assert_equal expectations,
|
28
30
|
calculations
|
29
31
|
end
|
30
32
|
|
@@ -33,7 +35,7 @@ module StandardAPI
|
|
33
35
|
create_model
|
34
36
|
|
35
37
|
math_column = model.columns.find { |x| CALCULATE_COLUMN_TYPES.include?(x.sql_type) }
|
36
|
-
|
38
|
+
|
37
39
|
if math_column
|
38
40
|
column = math_column
|
39
41
|
selects = [{ count: column.name}, { maximum: column.name }, { minimum: column.name }, { average: column.name }]
|
@@ -91,9 +91,6 @@ module StandardAPI
|
|
91
91
|
mask.each { |k, v| attrs[k] = v }
|
92
92
|
create_webmocks(attrs)
|
93
93
|
|
94
|
-
file_upload = attrs.any? { |k, v| v.is_a?(Rack::Test::UploadedFile) }
|
95
|
-
as = file_upload ? nil : :json
|
96
|
-
|
97
94
|
assert_difference("#{model.name}.count") do
|
98
95
|
post resource_path(:create), params: { singular_name => attrs }, as: :html
|
99
96
|
assert_response :redirect
|
@@ -9,13 +9,29 @@ module StandardAPI
|
|
9
9
|
get resource_path(:schema, format: :json)
|
10
10
|
assert_response :ok
|
11
11
|
json = JSON(@response.body)
|
12
|
-
assert json['
|
12
|
+
assert json['attributes']
|
13
|
+
|
13
14
|
model.columns.map do |column|
|
14
|
-
|
15
|
+
actual_column = json['attributes'][column.name]
|
16
|
+
assert_not_nil actual_column['type'], "Missing `type` for \"#{model}\" attribute \"#{column.name}\""
|
17
|
+
assert_equal_or_nil model.primary_key == column.name, actual_column['primary_key']
|
18
|
+
assert_equal_or_nil column.null, actual_column['null']
|
19
|
+
assert_equal_or_nil column.array, actual_column['array']
|
20
|
+
assert_equal_or_nil column.comment, actual_column['comment']
|
21
|
+
assert_equal_or_nil (column.default || column.default_function), actual_column['default']
|
15
22
|
end
|
23
|
+
|
16
24
|
assert json['limit']
|
25
|
+
assert_equal_or_nil model.connection.table_comment(model.table_name), json['comment']
|
17
26
|
end
|
18
27
|
|
28
|
+
def assert_equal_or_nil(expected, actual, msg=nil)
|
29
|
+
if expected.nil?
|
30
|
+
assert_nil actual, msg
|
31
|
+
else
|
32
|
+
assert_equal expected, actual, msg
|
33
|
+
end
|
34
|
+
end
|
19
35
|
end
|
20
36
|
end
|
21
|
-
end
|
37
|
+
end
|
data/lib/standard_api/version.rb
CHANGED
@@ -5,7 +5,7 @@ record.attributes.each do |name, value|
|
|
5
5
|
end
|
6
6
|
|
7
7
|
includes.each do |inc, subinc|
|
8
|
-
next if ["limit", "offset", "order", "when", "where"].include?(inc)
|
8
|
+
next if ["limit", "offset", "order", "when", "where", "distinct", "distinct_on"].include?(inc)
|
9
9
|
|
10
10
|
case association = record.class.reflect_on_association(inc)
|
11
11
|
when ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection, ActiveRecord::Reflection::ThroughReflection
|
@@ -14,15 +14,16 @@ includes.each do |inc, subinc|
|
|
14
14
|
partial = model_partial(association.klass)
|
15
15
|
json.set! inc do
|
16
16
|
# TODO limit causes preloaded assocations to reload
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
sub_records = record.send(inc)
|
18
|
+
|
19
|
+
sub_records = sub_records.limit(subinc['limit']) if subinc['limit']
|
20
|
+
sub_records = sub_records.offset(subinc['offset']) if subinc['offset']
|
21
|
+
sub_records = sub_records.order(subinc['order']) if subinc['order']
|
22
|
+
sub_records = sub_records.filter(subinc['where']) if subinc['where']
|
23
|
+
sub_records = sub_records.distinct if subinc['distinct']
|
24
|
+
sub_records = sub_records.distinct_on(subinc['distinct_on']) if subinc['distinct_on']
|
25
|
+
|
26
|
+
json.array! sub_records, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
|
26
27
|
end
|
27
28
|
end
|
28
29
|
when ActiveRecord::Reflection::BelongsToReflection, ActiveRecord::Reflection::HasOneReflection
|
@@ -7,7 +7,7 @@ json.object! do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
includes.each do |inc, subinc|
|
10
|
-
next if ["limit", "offset", "order", "when", "where"].include?(inc)
|
10
|
+
next if ["limit", "offset", "order", "when", "where", "distinct", "distinct_on"].include?(inc)
|
11
11
|
|
12
12
|
case association = record.class.reflect_on_association(inc)
|
13
13
|
when ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection, ActiveRecord::Reflection::ThroughReflection
|
@@ -16,15 +16,16 @@ json.object! do
|
|
16
16
|
partial = model_partial(association.klass)
|
17
17
|
json.set! inc do
|
18
18
|
# TODO limit causes preloaded assocations to reload
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
sub_records = record.send(inc)
|
20
|
+
|
21
|
+
sub_records = sub_records.limit(subinc['limit']) if subinc['limit']
|
22
|
+
sub_records = sub_records.offset(subinc['offset']) if subinc['offset']
|
23
|
+
sub_records = sub_records.order(subinc['order']) if subinc['order']
|
24
|
+
sub_records = sub_records.filter(subinc['where']) if subinc['where']
|
25
|
+
sub_records = sub_records.distinct if subinc['distinct']
|
26
|
+
sub_records = sub_records.distinct_on(subinc['distinct_on']) if subinc['distinct_on']
|
27
|
+
|
28
|
+
json.array! sub_records, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
|
28
29
|
end
|
29
30
|
end
|
30
31
|
when ActiveRecord::Reflection::BelongsToReflection, ActiveRecord::Reflection::HasOneReflection
|
@@ -0,0 +1,68 @@
|
|
1
|
+
if model.nil? && controller_name == "application"
|
2
|
+
routes = Rails.application.routes.routes.reject(&:internal).collect do |route|
|
3
|
+
{ name: route.name,
|
4
|
+
verb: route.verb,
|
5
|
+
path: route.path.spec.to_s.gsub(/\(\.format\)\Z/, ''),
|
6
|
+
controller: route.requirements[:controller],
|
7
|
+
action: route.requirements[:action],
|
8
|
+
array: ['index'].include?(route.requirements[:action]) }
|
9
|
+
end
|
10
|
+
|
11
|
+
json.set! 'comment', ActiveRecord::Base.connection.database_comment
|
12
|
+
|
13
|
+
json.set! 'routes' do
|
14
|
+
json.array!(routes) do |route|
|
15
|
+
controller = if controller_name = route[:controller]
|
16
|
+
begin
|
17
|
+
controller_param = controller_name.underscore
|
18
|
+
const_name = "#{controller_param.camelize}Controller"
|
19
|
+
const = ActiveSupport::Dependencies.constantize(const_name)
|
20
|
+
if const.ancestors.include?(StandardAPI::Controller)
|
21
|
+
const
|
22
|
+
else
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
rescue NameError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
next if controller.nil?
|
30
|
+
|
31
|
+
resource_limit = controller.resource_limit if controller.respond_to?(:resource_limit)
|
32
|
+
|
33
|
+
json.set! 'path', route[:path]
|
34
|
+
json.set! 'method', route[:verb]
|
35
|
+
json.set! 'model', controller.model&.name
|
36
|
+
json.set! 'array', route[:array]
|
37
|
+
json.set! 'limit', resource_limit
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
json.set! 'models' do
|
42
|
+
models.each do |model|
|
43
|
+
json.set! model.name do
|
44
|
+
json.partial! partial: schema_partial(model), model: model
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
else
|
50
|
+
|
51
|
+
json.set! 'attributes' do
|
52
|
+
model.columns.each do |column|
|
53
|
+
json.set! column.name, {
|
54
|
+
type: json_column_type(column.sql_type),
|
55
|
+
default: column.default || column.default_function,
|
56
|
+
primary_key: column.name == model.primary_key,
|
57
|
+
null: column.null,
|
58
|
+
array: column.array,
|
59
|
+
comment: column.comment
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
json.set! 'limit', resource_limit # This should be removed?
|
65
|
+
json.set! 'comment', model.connection.table_comment(model.table_name)
|
66
|
+
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
if model.nil? && controller_name == "application"
|
2
|
+
routes = Rails.application.routes.routes.reject(&:internal).collect do |route|
|
3
|
+
{ name: route.name,
|
4
|
+
verb: route.verb,
|
5
|
+
path: route.path.spec.to_s.gsub(/\(\.:format\)\Z/, ''),
|
6
|
+
controller: route.requirements[:controller],
|
7
|
+
action: route.requirements[:action],
|
8
|
+
array: ['index'].include?(route.requirements[:action]) }
|
9
|
+
end
|
10
|
+
|
11
|
+
json.object! do
|
12
|
+
json.set! 'comment', ActiveRecord::Base.connection.database_comment
|
13
|
+
|
14
|
+
json.set! 'routes' do
|
15
|
+
json.array!(routes) do |route|
|
16
|
+
controller = if controller_name = route[:controller]
|
17
|
+
begin
|
18
|
+
controller_param = controller_name.underscore
|
19
|
+
const_name = "#{controller_param.camelize}Controller"
|
20
|
+
const = ActiveSupport::Dependencies.constantize(const_name)
|
21
|
+
if const.ancestors.include?(StandardAPI::Controller)
|
22
|
+
const
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
rescue NameError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
next if controller.nil?
|
31
|
+
|
32
|
+
resource_limit = controller.resource_limit if controller.respond_to?(:resource_limit)
|
33
|
+
|
34
|
+
json.object! do
|
35
|
+
json.set! 'path', route[:path]
|
36
|
+
json.set! 'method', route[:verb]
|
37
|
+
json.set! 'model', controller.model&.name
|
38
|
+
json.set! 'array', route[:array]
|
39
|
+
json.set! 'limit', resource_limit
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
json.set! 'models' do
|
46
|
+
json.object! do
|
47
|
+
models.each do |model|
|
48
|
+
json.set! model.name do
|
49
|
+
json.partial!(schema_partial(model), model: model)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
else
|
57
|
+
|
58
|
+
json.object! do
|
59
|
+
json.set! 'attributes' do
|
60
|
+
json.object! do
|
61
|
+
model.columns.each do |column|
|
62
|
+
json.set! column.name, {
|
63
|
+
type: json_column_type(column.sql_type),
|
64
|
+
default: column.default || column.default_function,
|
65
|
+
primary_key: column.name == model.primary_key,
|
66
|
+
null: column.null,
|
67
|
+
array: column.array,
|
68
|
+
comment: column.comment
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
json.set! 'limit', resource_limit
|
75
|
+
json.set! 'comment', model.connection.table_comment(model.table_name)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -1,19 +1,16 @@
|
|
1
|
-
if !
|
2
|
-
|
1
|
+
if !defined?(records)
|
2
|
+
records = instance_variable_get("@#{model.model_name.plural}")
|
3
3
|
end
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
record_name = partial.split('/').last.to_sym
|
5
|
+
partial = model_partial(model)
|
6
|
+
partial_record_name = partial.split('/').last.to_sym
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
json.partial!
|
8
|
+
if !includes.empty? && can_cache?(model, includes)
|
9
|
+
json.cache_collection! records, key: proc { |record| cache_key(record, includes) } do |record|
|
10
|
+
json.partial!(partial, includes: includes, partial_record_name => record)
|
12
11
|
end
|
13
12
|
else
|
14
|
-
|
15
|
-
record_name = partial.split('/').last.to_sym
|
16
|
-
json.array!(instance_variable_get("@#{model.model_name.plural}")) do |record|
|
13
|
+
json.array!(records) do |record|
|
17
14
|
sub_includes = includes.select do |key, value|
|
18
15
|
case value
|
19
16
|
when Hash, ActionController::Parameters
|
@@ -27,10 +24,6 @@ else
|
|
27
24
|
end
|
28
25
|
end
|
29
26
|
|
30
|
-
json.partial!
|
31
|
-
record: record,
|
32
|
-
record_name => record,
|
33
|
-
includes: sub_includes
|
34
|
-
}
|
27
|
+
json.partial!(partial, includes: sub_includes, partial_record_name => record)
|
35
28
|
end
|
36
29
|
end
|