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.
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