diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 34cf5231e8..53bed7b532 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,52 +1,52 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2014-12-16 11:52:50 -0500 using RuboCop version 0.29.1. +# on 2015-06-03 09:20:43 -0400 using RuboCop version 0.29.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: 29 +# Offense count: 33 # Cop supports --auto-correct. Lint/UnusedBlockArgument: Enabled: false -# Offense count: 26 +# Offense count: 27 # Cop supports --auto-correct. Lint/UnusedMethodArgument: Enabled: false -# Offense count: 35 +# Offense count: 37 Metrics/AbcSize: - Max: 50 + Max: 48 -# Offense count: 1 +# Offense count: 2 Metrics/BlockNesting: Max: 4 # Offense count: 4 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 245 + Max: 246 -# Offense count: 15 +# Offense count: 23 Metrics/CyclomaticComplexity: Max: 20 -# Offense count: 545 +# Offense count: 676 # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Max: 198 -# Offense count: 42 +# Offense count: 44 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 35 -# Offense count: 13 +# Offense count: 17 Metrics/PerceivedComplexity: Max: 22 -# Offense count: 26 +# Offense count: 46 # Cop supports --auto-correct. Style/Blocks: Enabled: false @@ -57,7 +57,7 @@ Style/Blocks: Style/ClassCheck: Enabled: false -# Offense count: 157 +# Offense count: 174 Style/Documentation: Enabled: false @@ -73,7 +73,7 @@ Style/EachWithObject: Style/EmptyElse: Enabled: false -# Offense count: 14 +# Offense count: 15 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false @@ -89,7 +89,8 @@ Style/HashSyntax: Style/IndentArray: Enabled: false -# Offense count: 18 +# Offense count: 20 +# Cop supports --auto-correct. Style/Lambda: Enabled: false @@ -109,7 +110,7 @@ Style/PercentLiteralDelimiters: Style/PredicateName: Enabled: false -# Offense count: 9 +# Offense count: 13 # Configuration parameters: EnforcedStyle, SupportedStyles. Style/RaiseArgs: Enabled: false @@ -119,7 +120,7 @@ Style/RaiseArgs: Style/RegexpLiteral: Enabled: false -# Offense count: 4 +# Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/SpaceBeforeBlockBraces: diff --git a/CHANGELOG.md b/CHANGELOG.md index 891eade53a..e205aff36f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [#952](https://github.com/intridea/grape/pull/952): Status method now raises error when called with invalid status code - [@dabrorius](https://github.com/dabrorius). * [#957](https://github.com/intridea/grape/pull/957): Regexp validator now supports `allow_blank`, `nil` value behavior changed - [@calfzhou](https://giihub.com/calfzhou). * [#962](https://github.com/intridea/grape/pull/962): The `default` attribute with `false` value is documented now - [@ajvondrak](https://github.com/ajvondrak). +* [#1026](https://github.com/intridea/grape/pull/1026): Added `file` method, explicitly setting a file-like response object - [@dblock](https://github.com/dblock). #### Fixes diff --git a/README.md b/README.md index a1d26a6571..551afb998d 100644 --- a/README.md +++ b/README.md @@ -2009,7 +2009,7 @@ class API < Grape::API end ``` -You can also set the response body explicitly with `body`. +You can set the response body explicitly with `body`. ```ruby class API < Grape::API @@ -2023,6 +2023,28 @@ end Use `body false` to return `204 No Content` without any data or content-type. +You can also set the response to a file-like object with `file`. + +```ruby +class FileStreamer + def initialize(file_path) + @file_path = file_path + end + + def each(&blk) + File.open(@file_path, 'rb') do |file| + file.each(10, &blk) + end + end +end + +class API < Grape::API + get '/' do + file FileStreamer.new('file.bin') + end +end +``` + ## Authentication ### Basic and Digest Auth diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 08c0a1c123..9f2502637b 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -169,6 +169,22 @@ def body(value = nil) end end + # Allows you to define the response as a file-like object. + # + # @example + # get '/file' do + # file FileStreamer.new(...) + # end + # + # GET /file # => "contents of file" + def file(value = nil) + if value + @file = value + else + @file + end + end + # Allows you to make use of Grape Entities by setting # the response body to the serializable hash of the # entity provided in the `:with` option. This has the diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 977117cce4..35fc301aae 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -248,11 +248,13 @@ def run(env) run_filters after_validations - response_text = @block ? @block.call(self) : nil + response_object = @block ? @block.call(self) : nil run_filters afters cookies.write(header) - [status, header, [body || response_text]] + # The Body commonly is an Array of Strings, the application instance itself, or a File-like object. + response_object = file || [body || response_object] + [status, header, response_object] end def build_middleware diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index d31ea05266..e9e26bac58 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -29,9 +29,13 @@ def after api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env['api.format'] formatter = Grape::Formatter::Base.formatter_for api_format, options begin - bodymap = bodies.collect do |body| - formatter.call body, env - end + bodymap = if bodies.respond_to?(:collect) + bodies.collect do |body| + formatter.call body, env + end + else + bodies + end rescue Grape::Exceptions::InvalidFormatter => e throw :error, status: 500, message: e.message end diff --git a/spec/grape/dsl/inside_route_spec.rb b/spec/grape/dsl/inside_route_spec.rb index 63cfc6f77c..91357b2c93 100644 --- a/spec/grape/dsl/inside_route_spec.rb +++ b/spec/grape/dsl/inside_route_spec.rb @@ -195,6 +195,22 @@ def initialize end end + describe '#file' do + describe 'set' do + before do + subject.file 'file' + end + + it 'returns value' do + expect(subject.file).to eq 'file' + end + end + + it 'returns default' do + expect(subject.file).to be nil + end + end + describe '#route' do before do subject.env['rack.routing_args'] = {} diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 528a28b396..dfd303c13e 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -927,4 +927,18 @@ def memoized expect(last_response.status).to eq(406) end end + + context 'binary' do + before do + subject.get do + file FileStreamer.new(__FILE__) + end + end + + it 'suports stream objects in response' do + get '/' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq File.read(__FILE__) + end + end end diff --git a/spec/support/file_streamer.rb b/spec/support/file_streamer.rb new file mode 100644 index 0000000000..8a9f24d090 --- /dev/null +++ b/spec/support/file_streamer.rb @@ -0,0 +1,11 @@ +class FileStreamer + def initialize(file_path) + @file_path = file_path + end + + def each(&blk) + File.open(@file_path, 'rb') do |file| + file.each(10, &blk) + end + end +end