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