diff --git a/.rubocop.yml b/.rubocop.yml index a80d52c..01732de 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,32 +3,4 @@ AllCops: - vendor/** - .bundle -LineLength: - Enabled: false - -MethodLength: - Enabled: false - -ClassLength: - Enabled: false - -Documentation: - # don't require classes to be documented - Enabled: false - -Encoding: - # no need to always specify encoding - Enabled: false - -CollectionMethods: - # don't prefer map to collect, recuce to inject - Enabled: false - -RescueException: - Enabled: false - -DoubleNegation: - Enabled: false - -FileName: - Enabled: false +inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..eb3c70e --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,56 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2017-11-17 09:51:46 -0500 using RuboCop version 0.51.0. +# 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 +Lint/RescueException: + Exclude: + - 'spec/grape_rabl_spec.rb' + +# Offense count: 8 +# Configuration parameters: CountComments, ExcludedMethods. +Metrics/BlockLength: + Max: 169 + +# Offense count: 23 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 148 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 13 + +# Offense count: 1 +# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. +# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS +Naming/FileName: + Exclude: + - 'lib/grape-rabl.rb' + +# Offense count: 5 +Style/Documentation: + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'lib/grape-rabl.rb' + - 'lib/grape-rabl/configuration.rb' + - 'lib/grape-rabl/formatter.rb' + - 'lib/grape-rabl/render.rb' + - 'lib/grape/rabl.rb' + +# Offense count: 1 +Style/DoubleNegation: + Exclude: + - 'lib/grape-rabl/formatter.rb' + +# Offense count: 1 +Style/IfInsideElse: + Exclude: + - 'lib/grape-rabl/formatter.rb' diff --git a/.travis.yml b/.travis.yml index b492663..d052915 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,13 @@ +before_install: gem install bundler -v '1.15.0' rvm: - - 1.9.3 - - 2.0.0 - - 2.1.0 + - 2.2.0 + - 2.3.0 - ruby-head - - jruby-19mode - - jruby-head - - rbx-2.2.10 matrix: + include: + - rvm: 2.3.1 + script: + - bundle exec danger allow_failures: - - rvm: jruby-19mode - - rvm: jruby-head - rvm: ruby-head diff --git a/CHANGELOG.md b/CHANGELOG.md index 6724353..13526af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,62 +2,89 @@ * Your contribution here. +#### v0.5.0 + +* [#34](https://github.com/ruby-grape/grape-rabl/pulls/34): If no RABL template is specified, fallback to the default response format as determined by Grape - [@chrisbloom7](https://github.com/chrisbloom7). + +#### v0.4.3 + +* [#44](https://github.com/ruby-grape/grape-rabl/issues/44): Don't require unused hashie - [@tsuwatch](https://github.com/tsuwatch). +* [#44](https://github.com/ruby-grape/grape-rabl/issues/44): Support Ruby >= 2.2 - [@tsuwatch](https://github.com/tsuwatch). +* [#45](https://github.com/ruby-grape/grape-rabl/pull/45): Added danger, PR linter - [@dblock](https://github.com/dblock). + +#### v0.4.2 + +* [#43](https://github.com/ruby-grape/grape-rabl/pull/43): Fix template caching for multiple formats - [@kushkella](https://github.com/kushkella). + +#### v0.4.1 + +* [#39](https://github.com/ruby-grape/grape-rabl/issues/39): Automatically require 'grape/rabl' - [@martinezcoder](https://github.com/martinezcoder). + +#### v0.4.0 + +* [#37](https://github.com/ruby-grape/grape-rabl/issues/37): Make grape-rabl thread-safe - [@kushkella](https://github.com/kushkella). + +#### v0.3.1 + +* [#35](https://github.com/ruby-grape/grape-rabl/issues/35): The `render` method will no longer modify endpoint options at runtime - [@yesmeck](https://github.com/yesmeck). + #### v0.3.0 -* Enable using a layout template in Rabl. [@koko1000ban](https://github.com/koko1000ban) [view commit](https://github.com/LTe/grape-rabl/commit/1fbfbd58c3fb320be1b52b3247fda2a23cacc9fc) -* Implemented Rubocop, Ruby style linter. [@dblock](https://github.com/dblock) [view commit](https://github.com/LTe/grape-rabl/commit/1211056de22a5989c063d57b7b37ebb1f1977e83) -* Removed JRuby support. [@dblock](https://github.com/dblock) [view commit](https://github.com/LTe/grape-rabl/commit/59905c1b09670fe08501e09bad4ec8714839f2d3) -* Enable using locals with #render. [@hobofan](https://github.com/hobofan) [view commit](https://github.com/LTe/grape-rabl/commit/6c24130f6a670e52e6119c56904b8ed2e6f60b39) -* Enable support for template caching. [#28](https://github.com/LTe/grape-rabl/pull/28) [@kushkella](https://github.com/kushkella) [view commit](https://github.com/LTe/grape-rabl/commit/79b1e58d767c6286b510af669e718310c0ad25c2) +* [#22](https://github.com/ruby-grape/grape-rabl/pull/22): Enable using a layout template in Rabl - [@koko1000ban](https://github.com/koko1000ban). +* Implemented Rubocop, Ruby style linter - [@dblock](https://github.com/dblock). +* Removed JRuby support - [@dblock](https://github.com/dblock). +* Enable using locals with `.render` - [@hobofan](https://github.com/hobofan). +* Enable support for template caching - [@kushkella](https://github.com/kushkella). + #### v0.2.2 -* Relaxed dependency on a specific version of Grape. [#20](https://github.com/LTe/grape-rabl/pull/20) [@cheef](https://github.com/cheef) [view commit](https://github.com/LTe/grape-rabl/commit/56da0a5bcecb16501cdd93ac25f3b6ca6d7a86f0) +* [#20](https://github.com/ruby-grape/grape-rabl/pull/20): Relaxed dependency on a specific version of Grape - [@cheef](https://github.com/cheef). #### v0.2.1 -* Fix: render template according to request format. [#11](https://github.com/LTe/grape-rabl/pull/11) [@alovak](https://github.com/alovak) [view commit](http://github.com/LTe/grape-rabl/commit/f9658cf7a3026122afbb77e0da613731a5828338) +* [#11](https://github.com/ruby-grape/grape-rabl/pull/11): Fix: render template according to request format - [@alovak](https://github.com/alovak). #### v0.2.0 -* Allow to use partials in Grape. [#10](https://github.com/LTe/grape-rabl/pull/10) [@ichilton](https://github.com/ichilton) [view commit](http://github.com/LTe/grape-rabl/commit/72c96c5acc9d8000f56ee8400ae0229053fb3e7e) -* Stick to gem conventions. [@LTe](https://github.com/lte) [view commit](http://github.com/LTe/grape-rabl/commit/aabd0e2ad72f56a75427eebcc586deed57cf5f58) -* Update for Grape 0.3 compatibility. [@alovak](https://github.com/alovak) [view commit](http://github.com/LTe/grape-rabl/commit/78bfdceffbfe90b700868ff1e79ab87e8baded81) -* Format fix. [@LTe](https://github.com/lte) [view commit](http://github.com/LTe/grape-rabl/commit/13749cc18d332dcd0050bb32980cc233868a7992) +* [#10](https://github.com/ruby-grape/grape-rabl/pull/10): Allow to use partials in Grape - [@ichilton](https://github.com/ichilton). +* Stick to gem conventions - [@LTe](https://github.com/lte). +* [#13](https://github.com/ruby-grape/grape-rabl/pull/13): Update for Grape 0.3 compatibility - [@alovak](https://github.com/alovak). +* Format fix - [@LTe](https://github.com/LTe). #### v0.1.0 -* Updated w/ released Grape 0.2.3. [view commit](http://github.com/LTe/grape-rabl/commit/9a055dfd8e13e0952a587de7a2e19c9f762e939c) -* Added link to Rabl. [view commit](http://github.com/LTe/grape-rabl/commit/2a7650cb5f9327761cac8b928453e451a973e131) -* Grape 0.2.x and put back dependency status. [view commit](http://github.com/LTe/grape-rabl/commit/9c1183f3758db8a79737ff35f0c328be646a3f65) -* Grape 0.2.3. [view commit](http://github.com/LTe/grape-rabl/commit/d06a6559a02095e1d84fbbd8df0c3eccdd31930b) -* Updated Grape dependency via .gemspec. [view commit](http://github.com/LTe/grape-rabl/commit/fd44b6a91fa327438eac968fea62ac00ec3ae01f) +* Updated w/released Grape 0.2.3 - [@dblock](https://github.com/dblock). +* Added link to Rabl - [@dblock](https://github.com/dblock). +* Grape 0.2.x and put back dependency status - [@dblock](https://github.com/dblock). +* Grape 0.2.3 - [@dblock](https://github.com/dblock). +* Updated Grape dependency via .gemspec - [@dblock](https://github.com/dblock). #### v0.0.6 -* Use Grape formatter syntax instead of monkey-patching. [view commit](http://github.com/LTe/grape-rabl/commit/bfba4c382933fd0f912d9114676b6d79d627c3be) -* Close block code in README. [view commit](http://github.com/LTe/grape-rabl/commit/f397a0de4399d0797b5e327d56234464091d7e3d) -* Change home to user. [view commit](http://github.com/LTe/grape-rabl/commit/45178ec13c613d872c65475b330d20a548459681) +* [#6](https://github.com/ruby-grape/grape-rabl/pull/6): Use Grape formatter syntax instead of monkey-patching - [@dblock](https://github.com/dblock). +* Close block code in README - [@LTe](https://github.com/LTe). +* Change home to user - [@LTe](https://github.com/LTe). #### v0.0.5 -* Respect default_format for rabl response. [view commit](http://github.com/LTe/grape-rabl/commit/ac54ebbb1d43d1fb76ee9516c5aa683c750c73b0) +* Respect `default_format` for rabl response - [@LTe](https://github.com/LTe). #### v0.0.4 -* Require `grape/rabl`. [view commit](http://github.com/LTe/grape-rabl/commit/e99a185b20974f5e72ac3c19ec377a5853780a33) +* Require `grape/rabl` - [@LTe](https://github.com/LTe). #### v0.0.3 -* Template without `.rabl`. [view commit](http://github.com/LTe/grape-rabl/commit/cecca03a680f8ae50b406e1b8c170eba27d1bc99) +* Template without `.rabl` - [@LTe](https://github.com/LTe). #### v0.0.2 -* Add Travis. [view commit](http://github.com/LTe/grape-rabl/commit/71c905bc91066c6fdb628afb555561e23219e213) -* Remove ruby debug. [view commit](http://github.com/LTe/grape-rabl/commit/f80fad14a49b14ae7264b08eff12832c37cbd0b2) -* Works with rubinius. [view commit](http://github.com/LTe/grape-rabl/commit/fceece344de095916ded7c477bb5891537bb8663) -* Add dependency status. [view commit](http://github.com/LTe/grape-rabl/commit/66820fb52155c65d4cd9bd7b67f0f22c1105fa46) +* Add Travis - [@LTe](https://github.com/LTe). +* Remove ruby debug - [@LTe](https://github.com/LTe). +* Works with rubinius - [@LTe](https://github.com/LTe). +* Add dependency status - [@LTe](https://github.com/LTe). #### v0.0.1 -* Initial public release. [@LTe](https://github.com/lte) +* Initial public release - [@LTe](https://github.com/lte). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..97d0466 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,118 @@ +Contributing to Grape-Rabl +========================== + +Grape-Rabl is work of [many of contributors](https://github.com/ruby-grape/grape-rabl/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/ruby-grape/grape-rabl/pulls), [propose features and discuss issues](https://github.com/ruby-grape/grape-rabl/issues). When in doubt, ask a question in the [Grape Google Group](http://groups.google.com/group/ruby-grape). + +#### Fork the Project + +Fork the [project on Github](https://github.com/ruby-grape/grape-rabl) and check out your copy. + +``` +git clone https://github.com/contributor/grape-rabl.git +cd grape-rabl +git remote add upstream https://github.com/ruby-grape/grape-rabl.git +``` + +#### Create a Topic Branch + +Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. + +``` +git checkout master +git pull upstream master +git checkout -b my-feature-branch +``` + +#### Bundle Install and Test + +Ensure that you can build the project and run tests. + +``` +bundle install +bundle exec rake +``` + +#### Write Tests + +Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/grape-rabl](spec/grape-rabl). + +We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. + +#### Write Code + +Implement your feature or bug fix. + +Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted. + +Make sure that `bundle exec rake` completes without errors. + +#### Write Documentation + +Document any external behavior in the [README](README.md). + +#### Update Changelog + +Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. Make it look like every other line, including your name and link to your Github account. + +#### Commit Changes + +Make sure git knows your name and email address: + +``` +git config --global user.name "Your Name" +git config --global user.email "contributor@example.com" +``` + +Writing good commit logs is important. A commit log should describe what changed and why. + +``` +git add ... +git commit +``` + +#### Push + +``` +git push origin my-feature-branch +``` + +#### Make a Pull Request + +Go to https://github.com/contributor/grape-rabl and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. + +#### Rebase + +If you've been working on a change for a while, rebase with upstream/master. + +``` +git fetch upstream +git rebase upstream/master +git push origin my-feature-branch -f +``` + +#### Update CHANGELOG Again + +Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows. + +``` +* [#123](https://github.com/ruby-grape/grape-rabl/pull/123): Reticulated splines - [@contributor](https://github.com/contributor). +``` + +Amend your previous commit and force push the changes. + +``` +git commit --amend +git push origin my-feature-branch -f +``` + +#### Check on Your Pull Request + +Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. + +#### Be Patient + +It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! + +#### Thank You + +Please do know that we really appreciate and value your time and work. We love you, really. diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 0000000..f05fedd --- /dev/null +++ b/Dangerfile @@ -0,0 +1 @@ +danger.import_dangerfile(gem: 'ruby-grape-danger') diff --git a/Gemfile b/Gemfile index 5fb1c79..38f35e9 100644 --- a/Gemfile +++ b/Gemfile @@ -3,18 +3,15 @@ source 'https://rubygems.org' gemspec group :development do - gem "rubocop", "0.20.1" + gem 'rubocop', '0.51.0' end group :test do - gem "json", '~> 1.7.7' - gem "rspec", "~> 2.12.0" - gem "rack-test" - gem "rake" - gem "coveralls", require: false - gem "rabl", :github => "nesquena/rabl" - - platforms :rbx do - gem "iconv" - end + gem 'coveralls', require: false + gem 'json' + gem 'rabl' + gem 'rack-test' + gem 'rake' + gem 'rspec' + gem 'ruby-grape-danger', '~> 0.1.1', require: false end diff --git a/README.md b/README.md index 95bee8e..f8836d0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ # Grape::Rabl -Use [Rabl](https://github.com/nesquena/rabl) templates in [Grape](https://github.com/intridea/grape)! +Use [Rabl](https://github.com/nesquena/rabl) templates in [Grape](https://github.com/ruby-grape/grape)! -[![Build Status](https://secure.travis-ci.org/LTe/grape-rabl.png)](http://travis-ci.org/LTe/grape-rabl) -[![Dependency Status](https://gemnasium.com/LTe/grape-rabl.png)](https://gemnasium.com/LTe/grape-rabl) -[![Code Climate](https://codeclimate.com/github/LTe/grape-rabl.png)](https://codeclimate.com/github/LTe/grape-rabl) -[![Coverage Status](https://coveralls.io/repos/LTe/grape-rabl/badge.png?branch=master)](https://coveralls.io/r/LTe/grape-rabl?branch=master) -[![Gem Version](https://badge.fury.io/rb/grape-rabl.png)](http://badge.fury.io/rb/grape-rabl) +[![Gem Version](http://img.shields.io/gem/v/grape-rabl.svg)](http://badge.fury.io/rb/grape-rabl) +[![Build Status](http://img.shields.io/travis/ruby-grape/grape-rabl.svg)](https://travis-ci.org/ruby-grape/grape-rabl) +[![Code Climate](https://codeclimate.com/github/ruby-grape/grape-rabl.svg)](https://codeclimate.com/github/ruby-grape/grape-rabl) +[![Coverage Status](https://img.shields.io/coveralls/ruby-grape/grape-rabl.svg)](https://coveralls.io/r/ruby-grape/grape-rabl?branch=master) ## Installation @@ -21,20 +20,16 @@ And then execute: $ bundle -## Usage +## Upgrading -### Require grape-rabl +See [UPGRADING](UPGRADING.md). -```ruby -# config.ru -require 'grape/rabl' -``` +## Usage ### Setup view root directory + ```ruby # config.ru -require 'grape/rabl' - use Rack::Config do |env| env['api.tilt.root'] = '/path/to/view/root/directory' end @@ -78,8 +73,6 @@ You can override the default layout conventions: ```ruby # config.ru -require 'grape/rabl' - use Rack::Config do |env| env['api.tilt.root'] = '/path/to/view/root/directory' env['api.tilt.layout'] = 'layouts/another' @@ -88,14 +81,12 @@ end ### Enable template caching -Gape-rabl allows for template caching after templates are loaded initially. +Grape-rabl allows for template caching after templates are loaded initially. You can enable template caching: ```ruby # config.ru -require 'grape/rabl' - Grape::Rabl.configure do |config| config.cache_template_loading = true # default: false end @@ -114,8 +105,6 @@ get "/home", :rabl => "view.rabl" ```ruby # config.ru -require 'grape/rabl' - use Rack::Config do |env| env['api.tilt.root'] = '/path/to/view/root/directory' end @@ -149,7 +138,7 @@ class UserAPI < Grape::API @history = User.find(params[:id]).history end - # do not use rabl, fallback to the defalt Grape JSON formatter + # do not use rabl, fallback to the default Grape response formatter get '/users' do User.all end @@ -205,19 +194,10 @@ end ## Specs -See ["Writing Tests"](https://github.com/intridea/grape#writing-tests) in [https://github.com/intridea/grape](grape) README. +See ["Writing Tests"](https://github.com/ruby-grape/grape#writing-tests) in [grape](https://github.com/ruby-grape/grape) README. Enjoy :) - ## Contributing -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Added some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request - - -[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/LTe/grape-rabl/trend.png)](https://bitdeli.com/free "Bitdeli Badge") - +See [CONTRIBUTING](CONTRIBUTING.md). diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..a90834f --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,64 @@ +# Releasing Grape-Rabl + +There're no particular rules about when to release grape-rabl. Release bug fixes frequently, features not so frequently and breaking API changes rarely. + +### Release + +Run tests, check that all tests succeed locally. + +``` +bundle install +rake +``` + +Check that the last build succeeded in [Travis CI](https://travis-ci.org/ruby-grape/grape-rabl) for all supported platforms. + +Increment the version, modify [lib/grape-rabl/version.rb](lib/grape-rabl/version.rb). + +* Increment the third number if the release has bug fixes and/or very minor features, only (eg. change `0.7.1` to `0.7.2`). +* Increment the second number if the release contains major features or breaking API changes (eg. change `0.7.1` to `0.8.0`). + +Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. + +``` +### 0.7.2 (February 6, 2014) +``` + +Remove the line with "Your contribution here.", since there will be no more contributions to this release. + +Commit your changes. + +``` +git add CHANGELOG.md lib/grape-rabl/version.rb +git commit -m "Preparing for release, 0.7.2." +git push origin master +``` + +Release. + +``` +$ rake release + +grape-rabl 0.7.2 built to pkg/grape-rabl-0.7.2.gem. +Tagged v0.7.2. +Pushed git commits and tags. +Pushed grape-rabl 0.7.2 to rubygems.org. +``` + +### Prepare for the Next Version + +Add the next release to [CHANGELOG.md](CHANGELOG.md). + +``` +#### Next + +* Your contribution here. +``` + +Commit your changes. + +``` +git add CHANGELOG.md +git commit -m "Preparing for next development iteration, 0.7.3." +git push origin master +``` diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index ce4b18c..1dd0ac7 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,5 @@ #!/usr/bin/env rake -require "bundler/gem_tasks" +require 'bundler/gem_tasks' require 'rspec/core' require 'rspec/core/rake_task' @@ -9,6 +9,6 @@ RSpec::Core::RakeTask.new(:spec) do |spec| end require 'rubocop/rake_task' -Rubocop::RakeTask.new(:rubocop) +RuboCop::RakeTask.new(:rubocop) -task default: [:rubocop, :spec] +task default: %i[rubocop spec] diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..365512b --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,32 @@ +# Upgrading Grape::Rabl + +## Upgrading to >= 0.5 + +### Fallback rendering when no RABL template is defined + +Prior to v0.5.0, Grape::Rabl would always render content as JSON when no Rabl template was specified for a request. Beginning in v0.5.0 Grape::Rabl will now fallback to using the default response format [as determined by Grape](https://github.com/ruby-grape/grape#api-formats) + +```ruby +class SampleApi < Grape::API + format :xml + formatter :xml, Grape::Formatter::Rabl + + get 'list' do + %w[thing] + end +end +``` + +#### Former behavior + +```ruby +response.body # => ["thing"] +``` + +#### Current behavior + +```ruby +response.body # => \n\n thing\n +``` + +See [#34](https://github.com/ruby-grape/grape-rabl/pull/34) for more information. diff --git a/grape-rabl.gemspec b/grape-rabl.gemspec index cbf120d..321ed27 100644 --- a/grape-rabl.gemspec +++ b/grape-rabl.gemspec @@ -1,23 +1,23 @@ -# -*- encoding: utf-8 -*- + require File.expand_path('../lib/grape-rabl/version', __FILE__) Gem::Specification.new do |gem| - gem.authors = ["Piotr Niełacny"] - gem.email = ["piotr.nielacny@gmail.com"] - gem.description = %q{Use rabl in grape} - gem.summary = %q{Use rabl in grape} - gem.homepage = "https://github.com/LTe/grape-rabl" + gem.authors = ['Piotr Niełacny'] + gem.email = ['piotr.nielacny@gmail.com'] + gem.description = 'Use rabl in grape' + gem.summary = 'Use rabl in grape' + gem.homepage = 'https://github.com/ruby-grape/grape-rabl' - gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } gem.files = `git ls-files`.split("\n") gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - gem.name = "grape-rabl" - gem.require_paths = ["lib"] + gem.name = 'grape-rabl' + gem.require_paths = ['lib'] gem.version = Grape::Rabl::VERSION - gem.required_ruby_version = '>= 1.9.3' + gem.required_ruby_version = '>= 2.2.0' - gem.add_dependency "grape" - gem.add_dependency "rabl" - gem.add_dependency "tilt" - gem.add_dependency "i18n" + gem.add_dependency 'grape' + gem.add_dependency 'i18n' + gem.add_dependency 'rabl' + gem.add_dependency 'tilt' end diff --git a/lib/grape-rabl.rb b/lib/grape-rabl.rb index f085d06..5b56ea1 100644 --- a/lib/grape-rabl.rb +++ b/lib/grape-rabl.rb @@ -1,6 +1,6 @@ require 'rabl' require 'grape' -require 'hashie/hash' +require 'grape/rabl' require 'grape-rabl/tilt' require 'grape-rabl/version' require 'grape-rabl/formatter' @@ -10,7 +10,7 @@ module Grape module Rabl class << self - def configure(&block) + def configure yield(configuration) configuration end diff --git a/lib/grape-rabl/formatter.rb b/lib/grape-rabl/formatter.rb index 10bffce..d0734ba 100644 --- a/lib/grape-rabl/formatter.rb +++ b/lib/grape-rabl/formatter.rb @@ -1,85 +1,100 @@ require 'json' module Grape - module Formatter - module Rabl + module Rabl + class Formatter class << self - attr_reader :env - attr_reader :endpoint - - def call(object, env) - @env = env - @endpoint = env['api.endpoint'] - - if rablable? - rabl do |template| - engine = tilt_template(template) - output = engine.render endpoint, locals - if layout_template - layout_template.render(endpoint) { output } - else - output - end - end - else - Grape::Formatter::Json.call object, env - end + def tilt_cache + @tilt_cache ||= ::Tilt::Cache.new end + end + + attr_reader :env, :endpoint, :object - private + def initialize(object, env) + @env = env + @endpoint = env['api.endpoint'] + @object = object + end - def view_path(template) - if template.split('.')[-1] == 'rabl' - File.join(env['api.tilt.root'], template) - else - File.join(env['api.tilt.root'], (template + '.rabl')) + def render + if rablable? + rabl do |template| + engine = tilt_template(template) + output = engine.render endpoint, locals + if layout_template + layout_template.render(endpoint) { output } + else + output + end end + else + fallback_formatter.call object, env end + end - def rablable? - !!endpoint.options[:route_options][:rabl] - end + private - def rabl - template = endpoint.options[:route_options][:rabl] - fail 'missing rabl template' unless template - set_view_root unless env['api.tilt.root'] - yield template - end + # Find a formatter to fallback to. `env[Grape::Env::API_FORMAT]` will always be a + # valid formatter, otherwise a HTTP 406 error would have already have been thrown + def fallback_formatter + Grape::Formatter.formatter_for(env[Grape::Env::API_FORMAT]) + end - def locals - endpoint.options[:route_options][:rabl_locals] || {} + def view_path(template) + if template.split('.')[-1] == 'rabl' + File.join(env['api.tilt.root'], template) + else + File.join(env['api.tilt.root'], (template + '.rabl')) end + end - def set_view_root - fail "Use Rack::Config to set 'api.tilt.root' in config.ru" - end + def rablable? + !!rabl_template + end - def tilt_template(template) - if Grape::Rabl.configuration.cache_template_loading - tilt_cache.fetch(template) { ::Tilt.new(view_path(template), tilt_options) } - else - ::Tilt.new(view_path(template), tilt_options) - end - end + def rabl + raise 'missing rabl template' unless rabl_template + set_view_root unless env['api.tilt.root'] + yield rabl_template + end - def tilt_cache - @tilt_cache ||= ::Tilt::Cache.new - end + def locals + env['api.tilt.rabl_locals'] || endpoint.options[:route_options][:rabl_locals] || {} + end + + def rabl_template + env['api.tilt.rabl'] || endpoint.options[:route_options][:rabl] + end - def tilt_options - { format: env['api.format'], view_path: env['api.tilt.root'] } + def set_view_root + raise "Use Rack::Config to set 'api.tilt.root' in config.ru" + end + + def tilt_template(template) + if Grape::Rabl.configuration.cache_template_loading + Grape::Rabl::Formatter.tilt_cache.fetch(tilt_cache_key(template)) { ::Tilt.new(view_path(template), tilt_options) } + else + ::Tilt.new(view_path(template), tilt_options) end + end - def layout_template - layout_path = view_path(env['api.tilt.layout'] || 'layouts/application') - if Grape::Rabl.configuration.cache_template_loading - tilt_cache.fetch(layout_path) { ::Tilt.new(layout_path, tilt_options) if File.exist?(layout_path) } - else - ::Tilt.new(layout_path, tilt_options) if File.exist?(layout_path) - end + def tilt_options + { format: env['api.format'], view_path: env['api.tilt.root'] } + end + + def layout_template + layout_path = view_path(env['api.tilt.layout'] || 'layouts/application') + if Grape::Rabl.configuration.cache_template_loading + Grape::Rabl::Formatter.tilt_cache.fetch(tilt_cache_key(layout_path)) { ::Tilt.new(layout_path, tilt_options) if File.exist?(layout_path) } + else + ::Tilt.new(layout_path, tilt_options) if File.exist?(layout_path) end end + + def tilt_cache_key(path) + Digest::MD5.hexdigest("#{path}#{tilt_options}") + end end end end diff --git a/lib/grape-rabl/render.rb b/lib/grape-rabl/render.rb index c36412e..5838159 100644 --- a/lib/grape-rabl/render.rb +++ b/lib/grape-rabl/render.rb @@ -1,10 +1,12 @@ -module GrapeRabl - module Render - def render(options = {}) - env['api.endpoint'].options[:route_options][:rabl] = options.delete(:rabl) if options.include?(:rabl) - env['api.endpoint'].options[:route_options][:rabl_locals] = options.delete(:locals) +module Grape + module Rabl + module Render + def render(options = {}) + env['api.tilt.rabl'] = options[:rabl] + env['api.tilt.rabl_locals'] = options[:locals] + end end end end -Grape::Endpoint.send(:include, GrapeRabl::Render) +Grape::Endpoint.send(:include, Grape::Rabl::Render) diff --git a/lib/grape-rabl/version.rb b/lib/grape-rabl/version.rb index 092622a..82bbe09 100644 --- a/lib/grape-rabl/version.rb +++ b/lib/grape-rabl/version.rb @@ -1,5 +1,5 @@ module Grape module Rabl - VERSION = '0.3.1' + VERSION = '0.5.0'.freeze end end diff --git a/lib/grape/rabl.rb b/lib/grape/rabl.rb index db75dd3..61cf891 100644 --- a/lib/grape/rabl.rb +++ b/lib/grape/rabl.rb @@ -1 +1,13 @@ require 'grape-rabl' + +module Grape + module Formatter + module Rabl + class << self + def call(object, env) + Grape::Rabl::Formatter.new(object, env).render + end + end + end + end +end diff --git a/spec/grape_rabl_configuration.rb b/spec/grape_rabl_configuration.rb index 41a7f49..db58120 100644 --- a/spec/grape_rabl_configuration.rb +++ b/spec/grape_rabl_configuration.rb @@ -3,16 +3,16 @@ describe 'Grape::Rabl configuration' do context 'configuration' do it 'returns default values' do - Grape::Rabl.configuration.cache_template_loading.should == false + expect(Grape::Rabl.configuration.cache_template_loading).to eq(false) end it 'should set and reset configuration' do Grape::Rabl.configure do |config| config.cache_template_loading = true end - Grape::Rabl.configuration.cache_template_loading.should be == true + expect(Grape::Rabl.configuration.cache_template_loading).to eq(true) Grape::Rabl.reset_configuration! - Grape::Rabl.configuration.cache_template_loading.should == false + expect(Grape::Rabl.configuration.cache_template_loading).to eq(false) end end end diff --git a/spec/grape_rabl_formatter_spec.rb b/spec/grape_rabl_formatter_spec.rb new file mode 100644 index 0000000..eed3275 --- /dev/null +++ b/spec/grape_rabl_formatter_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe 'Grape::Rabl formatter' do + subject do + Class.new(Grape::API) + end + + let(:xml_render) do + %( + + + bad + things + happened + + +) + end + + def app + subject + end + + context 'rendering' do + context 'when no rabl template is specified' do + before do + # Grape::API defaults to the following declarations: + # content_type :xml, 'application/xml' + # content_type :json, 'application/json' + # content_type :binary, 'application/octet-stream' + # content_type :txt, 'text/plain' + # default_format :txt + subject.formatter :xml, Grape::Formatter::Rabl + subject.formatter :txt, Grape::Formatter::Rabl + subject.get('/oops') { { errors: %w[bad things happened] } } + expect_any_instance_of(Grape::Rabl::Formatter).to receive(:render).and_call_original + end + + it 'falls back to :txt given no other format information' do + get '/oops' + expect(last_response.body).to eq('{:errors=>["bad", "things", "happened"]}') + expect(last_response.headers['Content-Type']).to eq('text/plain') + end + + it 'falls back to the file extension if it is a valid format' do + get '/oops.xml' + expect(last_response.body).to eq(xml_render) + expect(last_response.headers['Content-Type']).to eq('application/xml') + end + + it 'falls back to the value of the `format` parameter in the query string if it is provided' do + get '/oops?format=xml' + expect(last_response.body).to eq(xml_render) + expect(last_response.headers['Content-Type']).to eq('application/xml') + end + + it 'falls back to the format set by the `format` option if it is a valid format' do + # `format` option must be declared before endpoint + subject.format :xml + subject.get('/oops/2') { { errors: %w[bad things happened] } } + + get '/oops/2' + expect(last_response.body).to eq(xml_render) + expect(last_response.headers['Content-Type']).to eq('application/xml') + end + + it 'falls back to the `Accept` header if it is a valid format' do + get '/oops', {}, 'HTTP_ACCEPT' => 'application/xml' + expect(last_response.body).to eq(xml_render) + expect(last_response.headers['Content-Type']).to eq('application/xml') + end + + it 'falls back to the default_format option if it is a valid format' do + # `default_format` option must be declared before endpoint + subject.default_format :xml + subject.get('/oops/2') { { errors: %w[bad things happened] } } + + get '/oops/2' + expect(last_response.body).to eq(xml_render) + expect(last_response.headers['Content-Type']).to eq('application/xml') + end + end + end +end diff --git a/spec/grape_rabl_layout_spec.rb b/spec/grape_rabl_layout_spec.rb index 1f55af8..6ec7fc3 100644 --- a/spec/grape_rabl_layout_spec.rb +++ b/spec/grape_rabl_layout_spec.rb @@ -28,8 +28,9 @@ def app end get('/about') - parsed_response.should == - JSON.parse(%Q({"status":200,"result":{"user":{"name":"LTe","project":{"name":"First"}}}})) + expect(parsed_response).to eq( + JSON.parse(%({"status":200,"result":{"user":{"name":"LTe","project":{"name":"First"}}}})) + ) end end @@ -47,17 +48,18 @@ def app get('/about') puts last_response.body - parsed_response.should == - JSON.parse(%Q({"result":{"user":{"name":"LTe","project":{"name":"First"}}}})) + expect(parsed_response).to eq( + JSON.parse(%({"result":{"user":{"name":"LTe","project":{"name":"First"}}}})) + ) end end context 'layout cache' do before do - @views_dir = FileUtils.mkdir_p("#{File.expand_path("..", File.dirname(__FILE__))}/tmp")[0] + @views_dir = FileUtils.mkdir_p("#{File.expand_path('..', File.dirname(__FILE__))}/tmp")[0] @layout = "#{@views_dir}/layouts/application.rabl" FileUtils.cp_r("#{File.dirname(__FILE__)}/views/layout_test/.", @views_dir) - subject.before { env['api.tilt.root'] = "#{File.expand_path("..", File.dirname(__FILE__))}/tmp" } + subject.before { env['api.tilt.root'] = "#{File.expand_path('..', File.dirname(__FILE__))}/tmp" } subject.get('/home', rabl: 'user') do @user = OpenStruct.new(name: 'LTe', email: 'email@example.com') @project = OpenStruct.new(name: 'First') @@ -75,24 +77,24 @@ def app config.cache_template_loading = true end get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) old_response = last_response.body open(@layout, 'a') { |f| f << 'node(:test) { "test" }' } get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) new_response = last_response.body - old_response.should == new_response + expect(old_response).to eq(new_response) end it 'should serve new template if cache_template_loading' do get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) old_response = last_response.body open(@layout, 'a') { |f| f << 'node(:test) { "test" }' } get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) new_response = last_response.body - old_response.should_not == new_response + expect(old_response).not_to eq(new_response) end end end diff --git a/spec/grape_rabl_partials_spec.rb b/spec/grape_rabl_partials_spec.rb index fcea707..ea40d96 100644 --- a/spec/grape_rabl_partials_spec.rb +++ b/spec/grape_rabl_partials_spec.rb @@ -25,7 +25,8 @@ def app end get('/home') - parsed_response.should == - JSON.parse("{\"project\":{\"name\":\"First\",\"info\":{\"type\":\"paper\"},\"author\":{\"author\":\"LTe\"}}}") + expect(parsed_response).to eq( + JSON.parse('{"project":{"name":"First","info":{"type":"paper"},"author":{"author":"LTe"}}}') + ) end end diff --git a/spec/grape_rabl_spec.rb b/spec/grape_rabl_spec.rb index 8484d40..601506d 100644 --- a/spec/grape_rabl_spec.rb +++ b/spec/grape_rabl_spec.rb @@ -6,8 +6,9 @@ end before do - subject.format :json + subject.default_format :json subject.formatter :json, Grape::Formatter::Rabl + subject.formatter :xml, Grape::Formatter::Rabl subject.helpers MyHelper end @@ -18,7 +19,7 @@ def app it 'should work without rabl template' do subject.get('/home') { 'Hello World' } get '/home' - last_response.body.should == "\"Hello World\"" + expect(last_response.body).to eq('"Hello World"') end it 'should raise error about root directory' do @@ -26,11 +27,11 @@ def app subject.get('/home', rabl: true) {} get '/home' rescue Exception => e - e.message.should include "Use Rack::Config to set 'api.tilt.root' in config.ru" + expect(e.message).to include "Use Rack::Config to set 'api.tilt.root' in config.ru" end end - context 'titl root is setup' do + context 'titl root is setup' do let(:parsed_response) { JSON.parse(last_response.body) } before do @@ -41,7 +42,7 @@ def app it 'should execute helper' do subject.get('/home', rabl: 'helper') { @user = OpenStruct.new } get '/home' - parsed_response.should == JSON.parse("{\"user\":{\"helper\":\"my_helper\"}}") + expect(parsed_response).to eq(JSON.parse('{"user":{"helper":"my_helper"}}')) end end @@ -75,47 +76,52 @@ def app it 'renders template passed as argument to render method' do get('/home') - parsed_response.should == JSON.parse('{"admin":{"name":"LTe"}}') + expect(parsed_response).to eq(JSON.parse('{"admin":{"name":"LTe"}}')) end it 'renders admin template' do get('/admin/1') - parsed_response.should == JSON.parse('{"admin":{"name":"LTe"}}') + expect(parsed_response).to eq(JSON.parse('{"admin":{"name":"LTe"}}')) end it 'renders user template' do get('/admin/2') - parsed_response.should == JSON.parse('{"user":{"name":"LTe","project":null}}') + expect(parsed_response).to eq(JSON.parse('{"user":{"name":"LTe","project":null}}')) end it 'renders template passed as argument to render method with locals' do get('/home-detail') - parsed_response.should == JSON.parse('{"admin":{"name":"LTe","details":"amazing detail"}}') + expect(parsed_response).to eq(JSON.parse('{"admin":{"name":"LTe","details":"amazing detail"}}')) end it 'renders with locals without overriding template' do get('/about-detail') - parsed_response.should == JSON.parse('{"user":{"name":"LTe","details":"just a user","project":null}}') + expect(parsed_response).to eq(JSON.parse('{"user":{"name":"LTe","details":"just a user","project":null}}')) end it 'does not save rabl options after called #render method' do get('/home') get('/about') - parsed_response.should == JSON.parse('{"user":{"name":"LTe","project":null}}') + expect(parsed_response).to eq(JSON.parse('{"user":{"name":"LTe","project":null}}')) + end + + it 'does not modify endpoint options' do + get '/home' + expect(last_request.env['api.endpoint'].options[:route_options][:rabl]).to eq 'user' end end it 'should respond with proper content-type' do subject.get('/home', rabl: 'user') {} get('/home') - last_response.headers['Content-Type'].should == 'application/json' + expect(last_response.headers['Content-Type']).to eq('application/json') end it 'should not raise error about root directory' do subject.get('/home', rabl: 'user') {} get '/home' - last_response.status.should eq 200 - last_response.body.should_not include "Use Rack::Config to set 'api.tilt.root' in config.ru" + expect(last_response.status).to eq 200 + expect(last_response.body).not_to include "Use Rack::Config to set 'api.tilt.root' in config.ru" end ['user', 'user.rabl'].each do |rabl_option| @@ -126,16 +132,16 @@ def app end get '/home' - parsed_response.should == JSON.parse('{"user":{"name":"LTe","email":"email@example.com","project":{"name":"First"}}}') + expect(parsed_response).to eq(JSON.parse('{"user":{"name":"LTe","email":"email@example.com","project":{"name":"First"}}}')) end end describe 'template cache' do before do - @views_dir = FileUtils.mkdir_p("#{File.expand_path("..", File.dirname(__FILE__))}/tmp")[0] + @views_dir = FileUtils.mkdir_p("#{File.expand_path('..', File.dirname(__FILE__))}/tmp")[0] @template = "#{@views_dir}/user.rabl" FileUtils.cp("#{File.dirname(__FILE__)}/views/user.rabl", @template) - subject.before { env['api.tilt.root'] = "#{File.expand_path("..", File.dirname(__FILE__))}/tmp" } + subject.before { env['api.tilt.root'] = "#{File.expand_path('..', File.dirname(__FILE__))}/tmp" } subject.get('/home', rabl: 'user') do @user = OpenStruct.new(name: 'LTe', email: 'email@example.com') @project = OpenStruct.new(name: 'First') @@ -152,24 +158,44 @@ def app config.cache_template_loading = true end get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) old_response = last_response.body open(@template, 'a') { |f| f << 'node(:test) { "test" }' } get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) new_response = last_response.body - old_response.should == new_response + expect(old_response).to eq(new_response) + end + + it 'should maintain different cached templates for different formats' do + Grape::Rabl.configure do |config| + config.cache_template_loading = true + end + get '/home' + expect(last_response.status).to eq(200) + json_response = last_response.body + get '/home.xml' + expect(last_response.status).to eq(200) + xml_response = last_response.body + expect(json_response).not_to eq(xml_response) + open(@template, 'a') { |f| f << 'node(:test) { "test" }' } + get '/home.xml' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq(xml_response) + get '/home.json' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq(json_response) end - it 'should serve new template if cache_template_loading' do + it 'should serve new template unless cache_template_loading' do get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) old_response = last_response.body open(@template, 'a') { |f| f << 'node(:test) { "test" }' } get '/home' - last_response.status.should be == 200 + expect(last_response.status).to eq(200) new_response = last_response.body - old_response.should_not == new_response + expect(old_response).not_to eq(new_response) end end end diff --git a/spec/grape_rabl_xml_spec.rb b/spec/grape_rabl_xml_spec.rb index 788905e..2ee2394 100644 --- a/spec/grape_rabl_xml_spec.rb +++ b/spec/grape_rabl_xml_spec.rb @@ -14,7 +14,7 @@ def app subject end - context 'with xml format' do + context 'with xml format' do before do subject.before do env['api.tilt.root'] = "#{File.dirname(__FILE__)}/views" @@ -25,7 +25,7 @@ def app it 'should respond with proper content-type' do subject.get('/home', rabl: 'user') {} get('/home') - last_response.headers['Content-Type'].should == 'application/xml' + expect(last_response.headers['Content-Type']).to eq('application/xml') end ['user', 'user.rabl'].each do |rabl_option| @@ -37,7 +37,7 @@ def app get '/home' - last_response.body.should == %Q( + expect(last_response.body).to eq(%( LTe email@example.com @@ -45,7 +45,7 @@ def app First -) +)) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e69f15f..9e20682 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,6 +15,7 @@ RSpec.configure do |config| config.include Rack::Test::Methods + config.raise_errors_for_deprecations! end Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } diff --git a/spec/views/_partial.rabl b/spec/views/_partial.rabl index 3470f4a..b2ea33b 100644 --- a/spec/views/_partial.rabl +++ b/spec/views/_partial.rabl @@ -1 +1 @@ -attributes :type \ No newline at end of file +attributes :type diff --git a/spec/views/admin.rabl b/spec/views/admin.rabl index 6255667..8c74f29 100644 --- a/spec/views/admin.rabl +++ b/spec/views/admin.rabl @@ -1,6 +1,6 @@ object @user => :admin attributes :name -node :details, :unless => lambda { |n| locals[:details].nil? } do |m| +node :details, unless: ->(_n) { locals[:details].nil? } do |_m| locals[:details] end diff --git a/spec/views/info.rabl b/spec/views/info.rabl index 21ab4a6..770c09d 100644 --- a/spec/views/info.rabl +++ b/spec/views/info.rabl @@ -1 +1 @@ -attributes :author \ No newline at end of file +attributes :author diff --git a/spec/views/project.rabl b/spec/views/project.rabl index 61539ea..7595600 100644 --- a/spec/views/project.rabl +++ b/spec/views/project.rabl @@ -2,9 +2,9 @@ object @project => :project attributes :name node :info do - partial "partial", object: @project.type + partial 'partial', object: @project.type end child @author => :author do - extends "info" -end \ No newline at end of file + extends 'info' +end diff --git a/spec/views/user.rabl b/spec/views/user.rabl index 8ba931d..4b8c5c7 100644 --- a/spec/views/user.rabl +++ b/spec/views/user.rabl @@ -5,6 +5,6 @@ child @project => :project do attributes :name end -node :details, :unless => lambda { |n| locals[:details].nil? } do |m| +node :details, unless: ->(_n) { locals[:details].nil? } do |_m| locals[:details] end