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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -6
  3. data/lib/standard_api.rb +2 -0
  4. data/lib/standard_api/active_record/connection_adapters/postgresql/schema_statements.rb +21 -0
  5. data/lib/standard_api/controller.rb +77 -66
  6. data/lib/standard_api/errors.rb +13 -0
  7. data/lib/standard_api/helpers.rb +67 -15
  8. data/lib/standard_api/includes.rb +22 -12
  9. data/lib/standard_api/orders.rb +4 -6
  10. data/lib/standard_api/railtie.rb +1 -1
  11. data/lib/standard_api/route_helpers.rb +4 -0
  12. data/lib/standard_api/test_case/calculate_tests.rb +5 -3
  13. data/lib/standard_api/test_case/create_tests.rb +0 -3
  14. data/lib/standard_api/test_case/schema_tests.rb +19 -3
  15. data/lib/standard_api/version.rb +1 -1
  16. data/lib/standard_api/views/application/_record.json.jbuilder +11 -10
  17. data/lib/standard_api/views/application/_record.streamer +11 -10
  18. data/lib/standard_api/views/application/_schema.json.jbuilder +68 -0
  19. data/lib/standard_api/views/application/_schema.streamer +78 -0
  20. data/lib/standard_api/views/application/index.json.jbuilder +9 -16
  21. data/lib/standard_api/views/application/index.streamer +9 -16
  22. data/lib/standard_api/views/application/schema.json.jbuilder +1 -12
  23. data/lib/standard_api/views/application/schema.streamer +1 -16
  24. data/lib/standard_api/views/application/show.json.jbuilder +8 -1
  25. data/lib/standard_api/views/application/show.streamer +8 -1
  26. data/test/standard_api/test_app.rb +55 -0
  27. data/test/standard_api/test_app/config/database.yml +4 -0
  28. data/test/standard_api/test_app/controllers.rb +107 -0
  29. data/test/standard_api/test_app/models.rb +94 -0
  30. data/test/standard_api/test_app/test/factories.rb +50 -0
  31. data/test/standard_api/test_app/test/fixtures/photo.png +0 -0
  32. data/test/standard_api/test_app/views/photos/_photo.json.jbuilder +15 -0
  33. data/test/standard_api/test_app/views/photos/_photo.streamer +17 -0
  34. data/test/standard_api/test_app/views/photos/_schema.json.jbuilder +1 -0
  35. data/test/standard_api/test_app/views/photos/_schema.streamer +3 -0
  36. data/test/standard_api/test_app/views/photos/schema.json.jbuilder +1 -0
  37. data/test/standard_api/test_app/views/photos/schema.streamer +1 -0
  38. data/test/standard_api/test_app/views/properties/edit.html.erb +1 -0
  39. data/test/standard_api/test_app/views/sessions/new.html.erb +0 -0
  40. 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
- if ['limit', 'when', 'where', 'order'].include?(k.to_s) # Where and order are not normalized (sanitation happens in activerecord-filter)
21
- normalized[k] = case v
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
- elsif k.to_s == 'distinct'
26
- normalized[k] = case v
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
- normalized[k] = normalize(v)
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) || ['limit', 'when', 'where', 'order', 'distinct'].include?(k.to_s)
62
- permitted[k] = sanitize(v, permit[k] || {}, true)
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
- if [:raise, nil].include?(Rails.configuration.try(:action_on_unpermitted_includes))
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
 
@@ -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.to_unsafe_hash
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(ActionController::UnpermittedParameters.new([orders]))
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(ActionController::UnpermittedParameters.new([orders]))
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
@@ -9,4 +9,4 @@ module StandardAPI
9
9
  end
10
10
 
11
11
  end
12
- end
12
+ end
@@ -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
- assert_equal [selects.map { |s| model.send(s.keys.first, column.name) }],
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['columns']
12
+ assert json['attributes']
13
+
13
14
  model.columns.map do |column|
14
- assert json['columns'][column.name]['type'], "Missing `type` for \"#{model}\" attribute \"#{column.name}\""
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
@@ -1,3 +1,3 @@
1
1
  module StandardAPI
2
- VERSION = '6.0.0.15'
2
+ VERSION = '6.0.0.29'
3
3
  end
@@ -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
- if subinc.keys.any? { |x| ["limit", "offset", "order", "when", "where"].include?(x) }
18
- if subinc['distinct']
19
- json.array! record.send(inc).filter(subinc['where']).limit(subinc['limit']).sort(subinc['order']).distinct_on(subinc['distinct']), partial: partial, as: partial.split('/').last, locals: { includes: subinc }
20
- else
21
- json.array! record.send(inc).filter(subinc['where']).limit(subinc['limit']).sort(subinc['order']).distinct, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
22
- end
23
- else
24
- json.array! record.send(inc), partial: partial, as: partial.split('/').last, locals: { includes: subinc }
25
- end
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
- if subinc.keys.any? { |x| ["limit", "offset", "order", "when", "where"].include?(x) }
20
- if subinc['distinct']
21
- json.array! record.send(inc).filter(subinc['where']).limit(subinc['limit']).sort(subinc['order']).distinct_on(subinc['distinct']), partial: partial, as: partial.split('/').last, locals: { includes: subinc }
22
- else
23
- json.array! record.send(inc).filter(subinc['where']).limit(subinc['limit']).sort(subinc['order']).distinct, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
24
- end
25
- else
26
- json.array! record.send(inc), partial: partial, as: partial.split('/').last, locals: { includes: subinc }
27
- end
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 !includes.empty?
2
- instance_variable_set("@#{model.model_name.plural}", preloadables(instance_variable_get("@#{model.model_name.plural}"), includes))
1
+ if !defined?(records)
2
+ records = instance_variable_get("@#{model.model_name.plural}")
3
3
  end
4
4
 
5
- if !includes.empty? && can_cache?(model, includes)
6
- partial = model_partial(model)
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
- json.cache_collection! instance_variable_get("@#{model.model_name.plural}"), key: proc { |record| cache_key(record, includes) } do |record|
10
- locals = { record: record, record_name => record, :includes => includes }
11
- json.partial! partial, locals
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
- partial = model_partial(model)
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! 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