From 611d31e6afb05a49f988ffce3e4fc681425cd271 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sun, 26 Feb 2017 22:34:42 +0200 Subject: [PATCH 1/3] Nested exposures --- .rubocop_todo.yml | 25 ++++---- lib/grape-swagger/entity/parser.rb | 43 ++++++++++++-- .../entities/response_model_spec.rb | 59 ++++++++++++++++++- 3 files changed, 110 insertions(+), 17 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 59024da..7c6d4d8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,43 +1,44 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-02-02 15:42:44 +0100 using RuboCop version 0.47.1. +# on 2017-02-26 22:17:25 +0200 using RuboCop version 0.47.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 +# Offense count: 2 Metrics/AbcSize: - Max: 45 + Max: 35 -# Offense count: 8 -# Configuration parameters: CountComments, ExcludedMethods. -Metrics/BlockLength: - Max: 27 +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 126 # Offense count: 1 Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 11 +# Offense count: 14 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 120 -# Offense count: 2 +# Offense count: 3 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 30 + Max: 27 -# Offense count: 1 +# Offense count: 2 Metrics/PerceivedComplexity: - Max: 16 + Max: 15 # Offense count: 2 Style/Documentation: Exclude: - 'spec/**/*' + - 'test/**/*' - 'lib/grape-swagger/entity.rb' - 'lib/grape-swagger/entity/parser.rb' diff --git a/lib/grape-swagger/entity/parser.rb b/lib/grape-swagger/entity/parser.rb index 8bff44f..69bcd47 100644 --- a/lib/grape-swagger/entity/parser.rb +++ b/lib/grape-swagger/entity/parser.rb @@ -25,7 +25,7 @@ def call private - def parse_grape_entity_params(params) + def parse_grape_entity_params(params, parent_model = nil) return unless params params.each_with_object({}) do |(entity_name, entity_options), memo| @@ -33,11 +33,13 @@ def parse_grape_entity_params(params) entity_name = entity_options[:as] if entity_options[:as] documentation = entity_options[:documentation] - model = model_from(entity_options) + entity_model = model_from(entity_options) - if model - name = endpoint.nil? ? model.to_s.demodulize : endpoint.send(:expose_params_from_model, model) + if entity_model + name = endpoint.nil? ? entity_model.to_s.demodulize : endpoint.send(:expose_params_from_model, entity_model) memo[entity_name] = entity_model_type(name, entity_options) + elsif entity_options[:nesting] + memo[entity_name] = parse_nested(entity_name, entity_options, parent_model) else memo[entity_name] = data_type_from(entity_options) next unless documentation @@ -70,6 +72,39 @@ def model_from(entity_options) model end + def parse_nested(entity_name, entity_options, parent_model = nil) + nested_entity = if parent_model.nil? + model.root_exposures.find_by(entity_name) + else + parent_model.nested_exposures.find_by(entity_name) + end + + params = nested_entity.nested_exposures.each_with_object({}) do |value, memo| + memo[value.attribute] = value.send(:options) + end + + properties = parse_grape_entity_params(params, nested_entity) + is_a_collection = entity_options[:documentation].is_a?(Hash) && + entity_options[:documentation][:type].to_s.casecmp('array').zero? + + if is_a_collection + { + type: :array, + items: { + type: :object, + properties: properties + }, + description: entity_options[:desc] || '' + } + else + { + type: :object, + properties: properties, + description: entity_options[:desc] || '' + } + end + end + def could_it_be_a_model?(value) return false if value.nil? direct_model_type?(value[:type]) || ambiguous_model_type?(value[:type]) diff --git a/spec/grape-swagger/entities/response_model_spec.rb b/spec/grape-swagger/entities/response_model_spec.rb index e147fc2..b64053c 100644 --- a/spec/grape-swagger/entities/response_model_spec.rb +++ b/spec/grape-swagger/entities/response_model_spec.rb @@ -96,6 +96,23 @@ class Tag < Grape::Entity expose :name, documentation: { type: 'string', desc: 'Name' } end + class Nested < Grape::Entity + expose :nested, documentation: { type: 'Object', desc: 'Nested entity' } do + expose :some1, documentation: { type: 'String', desc: 'Nested some 1' } + expose :some2, documentation: { type: 'String', desc: 'Nested some 2' } + end + expose :deep_nested, documentation: { type: 'Object', desc: 'Deep nested entity' } do + expose :level_1, documentation: { type: 'Object', desc: 'More deepest nested entity' } do + expose :level_2, documentation: { type: 'String', desc: 'Level 2' } + end + end + + expose :nested_array, documentation: { type: 'Array', desc: 'Nested array' } do + expose :id, documentation: { type: 'Integer', desc: 'Collection element id' } + expose :name, documentation: { type: 'String', desc: 'Collection element name' } + end + end + class SomeEntity < Grape::Entity expose :text, documentation: { type: 'string', desc: 'Content of something.' } expose :kind, using: Kind, documentation: { type: 'TheseApi::Kind', desc: 'The kind of this something.' } @@ -104,6 +121,7 @@ class SomeEntity < Grape::Entity expose :tags, using: TheseApi::Entities::Tag, documentation: { desc: 'Tags.', is_array: true } expose :relation, using: TheseApi::Entities::Relation, documentation: { type: 'TheseApi::Relation', desc: 'A related model.' } expose :values, using: TheseApi::Entities::Values, documentation: { desc: 'Tertiary kind.' } + expose :nested, using: TheseApi::Entities::Nested, documentation: { desc: 'Nested object.' } end end @@ -151,6 +169,44 @@ def app expect(subject['Relation']).to eql( 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string', 'description' => 'Name' } } ) + expect(subject['Nested']).to eq( + 'properties' => { + 'nested' => { + 'type' => 'object', + 'properties' => { + 'some1' => { 'type' => 'string', 'description' => 'Nested some 1' }, + 'some2' => { 'type' => 'string', 'description' => 'Nested some 2' } + }, + 'description' => 'Nested entity' + }, + 'deep_nested' => { + 'type' => 'object', + 'properties' => { + 'level_1' => { + 'type' => 'object', + 'properties' => { + 'level_2' => { 'type' => 'string', 'description' => 'Level 2' } + }, + 'description' => 'More deepest nested entity' + } + }, + 'description' => 'Deep nested entity' + }, + 'nested_array' => { + 'type' => 'array', + 'items' => { + 'type' => 'object', + 'properties' => { + 'id' => { 'type' => 'integer', 'format' => 'int32', 'description' => 'Collection element id' }, + 'name' => { 'type' => 'string', 'description' => 'Collection element name' } + } + }, + 'description' => 'Nested array' + } + }, + 'type' => 'object' + ) + expect(subject['SomeEntity']).to eql( 'type' => 'object', 'properties' => { @@ -160,7 +216,8 @@ def app 'kind3' => { '$ref' => '#/definitions/Kind', 'description' => 'Tertiary kind.' }, 'tags' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/Tag' }, 'description' => 'Tags.' }, 'relation' => { '$ref' => '#/definitions/Relation', 'description' => 'A related model.' }, - 'values' => { '$ref' => '#/definitions/Values', 'description' => 'Tertiary kind.' } + 'values' => { '$ref' => '#/definitions/Values', 'description' => 'Tertiary kind.' }, + 'nested' => { '$ref' => '#/definitions/Nested', 'description' => 'Nested object.' } }, 'description' => 'This returns something' ) From 1d88a25d96e7ecd665245b2323e5828024100cef Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Sun, 26 Feb 2017 22:40:55 +0200 Subject: [PATCH 2/3] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36af5b8..9b5a499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Features +* [#22](https://github.com/ruby-grape/grape-swagger-entity/pull/22): Nested exposures - [@Bugagazavr](https://github.com/Bugagazavr). * Your contribution here. #### Fixes From 13e4bf81d42fd7880418f017cbb9824c1263d640 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Mon, 27 Feb 2017 00:55:12 +0200 Subject: [PATCH 3/3] Review notes --- .rubocop_todo.yml | 1 - spec/grape-swagger/entities/response_model_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7c6d4d8..abbdc9f 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -38,7 +38,6 @@ Metrics/PerceivedComplexity: Style/Documentation: Exclude: - 'spec/**/*' - - 'test/**/*' - 'lib/grape-swagger/entity.rb' - 'lib/grape-swagger/entity/parser.rb' diff --git a/spec/grape-swagger/entities/response_model_spec.rb b/spec/grape-swagger/entities/response_model_spec.rb index b64053c..fb4a7ed 100644 --- a/spec/grape-swagger/entities/response_model_spec.rb +++ b/spec/grape-swagger/entities/response_model_spec.rb @@ -97,7 +97,7 @@ class Tag < Grape::Entity end class Nested < Grape::Entity - expose :nested, documentation: { type: 'Object', desc: 'Nested entity' } do + expose :nested, documentation: { type: Hash, desc: 'Nested entity' } do expose :some1, documentation: { type: 'String', desc: 'Nested some 1' } expose :some2, documentation: { type: 'String', desc: 'Nested some 2' } end