├── .rspec ├── .document ├── lib ├── grape-cache_control.rb └── grape │ ├── cache_control │ ├── version.rb │ └── helpers.rb │ └── cache_control.rb ├── spec ├── support │ ├── timecop.rb │ ├── codeclimate.rb │ ├── rack-test.rb │ └── pry.rb ├── spec_helper.rb └── unit │ └── grape │ └── cache_control_spec.rb ├── .yardopts ├── Gemfile ├── CHANGELOG.md ├── .gitignore ├── .rubocop.yml ├── Rakefile ├── .travis.yml ├── LICENSE.txt ├── grape-cache_control.gemspec ├── CONTRIBUTING.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | LICENSE.md 2 | README.md 3 | lib/**/*.rb -------------------------------------------------------------------------------- /lib/grape-cache_control.rb: -------------------------------------------------------------------------------- 1 | require 'grape/cache_control' 2 | -------------------------------------------------------------------------------- /spec/support/timecop.rb: -------------------------------------------------------------------------------- 1 | require 'timecop' 2 | Timecop.freeze 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | - 3 | CHANGELOG.md 4 | CONTRIBUTING.md 5 | LICENSE.md 6 | README.md -------------------------------------------------------------------------------- /spec/support/codeclimate.rb: -------------------------------------------------------------------------------- 1 | require 'codeclimate-test-reporter' 2 | CodeClimate::TestReporter.start -------------------------------------------------------------------------------- /spec/support/rack-test.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | RSpec.configure do |config| 3 | config.include Rack::Test::Methods 4 | end 5 | -------------------------------------------------------------------------------- /lib/grape/cache_control/version.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module CacheControl 3 | VERSION = '1.0.1' unless defined?(Grape::CacheControl::VERSION) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/pry.rb: -------------------------------------------------------------------------------- 1 | if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' 2 | begin 3 | require 'pry' unless ENV['CI'] 4 | rescue LoadError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem 'rake', '~> 10.0' 7 | gem 'rspec' 8 | gem 'rack-test' 9 | gem 'timecop' 10 | gem 'codeclimate-test-reporter' 11 | end -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Next Release 2 | ============ 3 | * Your contribution here. 4 | 5 | 1.0.1 (07/07/2015) 6 | ================== 7 | * [Adding stale](https://github.com/karlfreeman/grape-cache_control/pull/1) - [@cabbit](https://github.com/cabbit). 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | bin -------------------------------------------------------------------------------- /lib/grape/cache_control/helpers.rb: -------------------------------------------------------------------------------- 1 | module Grape 2 | module CacheControl 3 | module Helpers 4 | def cache_control(*args) 5 | Grape::CacheControl.cache_control(self, *args) 6 | end 7 | 8 | def expires(amount, *values) 9 | Grape::CacheControl.expires(self, amount, *values) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | 3 | require 'bundler' 4 | Bundler.setup 5 | 6 | %w(support).each do |dir| 7 | Dir.glob(File.expand_path("../#{dir}/**/*.rb", __FILE__), &method(:require)) 8 | end 9 | 10 | require 'grape-cache_control' 11 | 12 | RSpec.configure do |config| 13 | config.expect_with :rspec do |c| 14 | c.syntax = :expect 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Includes: 3 | - Rakefile 4 | - Gemfile 5 | Excludes: 6 | - script/** 7 | - vendor/** 8 | - bin/** 9 | LineLength: 10 | Enabled: false 11 | MethodLength: 12 | Enabled: false 13 | ClassLength: 14 | Enabled: false 15 | Documentation: 16 | Enabled: false 17 | Encoding: 18 | Enabled: false 19 | Blocks: 20 | Enabled: false 21 | AlignParameters: 22 | Enabled: false 23 | HashSyntax: 24 | EnforcedStyle: ruby19 -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.setup 3 | Bundler::GemHelper.install_tasks 4 | 5 | require 'rspec/core/rake_task' 6 | desc 'Run all examples' 7 | RSpec::Core::RakeTask.new(:spec) 8 | 9 | begin 10 | require 'yard' 11 | YARD::Rake::YardocTask.new 12 | rescue LoadError 13 | end 14 | 15 | begin 16 | require 'rubocop/rake_task' 17 | desc 'Run rubocop' 18 | RuboCop::RakeTask.new(:rubocop) 19 | rescue LoadError 20 | end 21 | 22 | task default: :spec 23 | task test: :spec 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | bundler_args: --without development 4 | rvm: 5 | - ruby-head 6 | - ruby 7 | - jruby-head 8 | - jruby 9 | - 2.1.0 10 | - 2.0.0 11 | - 1.9.3 12 | - rbx-2 13 | matrix: 14 | fast_finish: true 15 | allow_failures: 16 | - rvm: ruby-head 17 | - rvm: ruby 18 | - rvm: jruby-head 19 | - rvm: jruby 20 | - rvm: rbx-2 21 | notifications: 22 | email: false 23 | env: 24 | - CODECLIMATE_REPO_TOKEN=d1d234d73d5d9aa513cb00c58183548bd845400fa6d2660466ac380999eb8c3d -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Karl Freeman 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /grape-cache_control.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'grape/cache_control/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'grape-cache_control' 8 | spec.version = Grape::CacheControl::VERSION 9 | spec.authors = ['Karl Freeman'] 10 | spec.email = ['karlfreeman@gmail.com'] 11 | spec.summary = %q{Cache-Control and Expires helpers for Grape} 12 | spec.description = %q{Cache-Control and Expires helpers for Grape} 13 | spec.homepage = 'https://github.com/karlfreeman/grape-cache_control' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ['lib'] 20 | spec.required_ruby_version = '>= 1.9.3' 21 | 22 | spec.add_dependency 'grape', '~> 0.3' 23 | 24 | spec.add_development_dependency 'bundler', '~> 1.5' 25 | spec.add_development_dependency 'rake', '~> 10.0' 26 | spec.add_development_dependency 'kramdown', '>= 0.14' 27 | spec.add_development_dependency 'rubocop' 28 | spec.add_development_dependency 'pry' 29 | spec.add_development_dependency 'yard' 30 | end -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | In the spirit of [free software][free-sw], **everyone** is encouraged to help 3 | improve this project. 4 | 5 | [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html 6 | 7 | Here are some ways *you* can contribute: 8 | 9 | * by using alpha, beta, and prerelease versions 10 | * by reporting bugs 11 | * by suggesting new features 12 | * by writing or editing documentation 13 | * by writing specifications 14 | * by writing code (**no patch is too small**: fix typos, add comments, clean up 15 | inconsistent whitespace) 16 | * by refactoring code 17 | * by closing [issues][] 18 | * by reviewing patches 19 | 20 | [issues]: https://github.com/karlfreeman/grape-cache_control/issues 21 | 22 | ## Submitting an Issue 23 | We use the [GitHub issue tracker][issues] to track bugs and features. Before 24 | submitting a bug report or feature request, check to make sure it hasn't 25 | already been submitted. When submitting a bug report, please include a [Gist][] 26 | that includes a stack trace and any details that may be necessary to reproduce 27 | the bug, including your gem version, Ruby version, and operating system. 28 | Ideally, a bug report should include a pull request with failing specs. 29 | 30 | [gist]: https://gist.github.com/ 31 | 32 | ## Submitting a Pull Request 33 | 1. [Fork the repository.][fork] 34 | 2. [Create a topic branch.][branch] 35 | 3. Add specs for your unimplemented feature or bug fix. 36 | 4. Run `bundle exec rake spec`. If your specs pass, return to step 3. 37 | 5. Implement your feature or bug fix. 38 | 6. Run `bundle exec rake spec`. If your specs fail, return to step 5. 39 | 7. Add, commit, and push your changes. 40 | 8. [Submit a pull request.][pr] 41 | 42 | [fork]: http://help.github.com/fork-a-repo/ 43 | [branch]: http://learn.github.com/p/branching.html 44 | [pr]: http://help.github.com/send-pull-requests/ 45 | -------------------------------------------------------------------------------- /lib/grape/cache_control.rb: -------------------------------------------------------------------------------- 1 | require 'grape' 2 | require 'grape/cache_control/version' 3 | 4 | module Grape 5 | module CacheControl 6 | autoload :Helpers, 'grape/cache_control/helpers' 7 | Grape::Endpoint.send :include, Grape::CacheControl::Helpers 8 | 9 | CACHE_CONTROL = 'Cache-Control'.freeze 10 | EXPIRES = 'Expires'.freeze 11 | TRUE = 'true'.freeze 12 | SETTABLE_DIRECTIVES = [:max_age, :s_maxage, :stale_while_revalidate, :stale_while_error].freeze 13 | TRUTHY_DIRECTIVES = [:public, :private, :no_cache, :no_store, :no_transform, 14 | :must_revalidate, :proxy_revalidate].freeze 15 | ALL_DIRECTIVES = (TRUTHY_DIRECTIVES | SETTABLE_DIRECTIVES).freeze 16 | 17 | ALL_DIRECTIVES.each do |d| 18 | const_set(d.upcase, d.to_s.tr('_', '-')) 19 | end 20 | 21 | class << self 22 | def cache_control(request, *values) 23 | cc_directives = {} 24 | 25 | (cache_control_segments(request.header(CACHE_CONTROL)) | cache_control_values(*values)).each do |segment| 26 | directive, argument = segment.split('=', 2) 27 | cc_directives[directive.tr('-', '_').to_sym] = argument || true 28 | end 29 | 30 | if !cc_directives.empty? 31 | cc_header = [] 32 | cc_directives.delete(:public) if cc_directives.key? :private 33 | cc_directives.delete(:private) if cc_directives.key? :public 34 | cc_directives.each do |k, v| 35 | if SETTABLE_DIRECTIVES.include?(k) 36 | cc_header << "#{Grape::CacheControl.const_get(k.upcase)}=#{v.to_i}" 37 | elsif TRUTHY_DIRECTIVES.include?(k) && v == TRUE 38 | cc_header << Grape::CacheControl.const_get(k.upcase) 39 | end 40 | end 41 | request.header(CACHE_CONTROL, cc_header.join(', ')) 42 | end 43 | end 44 | 45 | def expires(request, amount, *values) 46 | if amount.is_a? Integer 47 | time = Time.now + amount.to_i 48 | max_age = amount 49 | else 50 | time = amount 51 | max_age = time - Time.now 52 | end 53 | 54 | values << { max_age: max_age } 55 | request.header(EXPIRES, time.httpdate) 56 | cache_control(request, *values) 57 | end 58 | 59 | private 60 | 61 | def cache_control_values(*values) 62 | cache_control_values = [] 63 | values.each do |value| 64 | if value.is_a? Hash 65 | cache_control_values.concat value.map { |k, v| 66 | argument = v.is_a?(Time) ? v - Time.now : v 67 | "#{Grape::CacheControl.const_get(k.upcase)}=#{argument}" 68 | } 69 | elsif value.is_a? Symbol 70 | cache_control_values << "#{value}=#{TRUE}" 71 | end 72 | end 73 | cache_control_values 74 | end 75 | 76 | def cache_control_segments(header = nil) 77 | segments = [] 78 | segments.concat header.delete(' ').split(',') unless header.nil? 79 | segments 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /spec/unit/grape/cache_control_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe Grape::CacheControl do 3 | subject { Class.new(Grape::API) } 4 | 5 | def app 6 | subject 7 | end 8 | 9 | context 'helpers' do 10 | describe 'cache_control' do 11 | it 'sets headers' do 12 | subject.get('/apples') do 13 | cache_control :public, max_age: 60.0 14 | end 15 | 16 | get 'apples' 17 | expect(last_response.headers['Cache-Control'].split(', ')).to include('public', 'max-age=60') 18 | end 19 | 20 | it 'treats symbols as true and hash keys with true values the same' do 21 | subject.get('/pears') do 22 | cache_control :public, no_cache: true 23 | end 24 | 25 | get 'pears' 26 | expect(last_response.headers['Cache-Control'].split(', ')).to include('public', 'no-cache') 27 | end 28 | 29 | it 'merges previous cache_control headers' do 30 | subject.get('/grapes') do 31 | cache_control :public, max_age: 60, s_maxage: 30 32 | cache_control :private 33 | end 34 | 35 | get 'grapes' 36 | expect(last_response.headers['Cache-Control'].split(', ')).to include('private', 'max-age=60', 's-maxage=30') 37 | end 38 | 39 | it 'allows removal of previous headers by setting a hash keys value as false' do 40 | subject.get('/pears') do 41 | cache_control :public, no_cache: true 42 | cache_control :private, no_cache: false 43 | end 44 | 45 | get 'pears' 46 | expect(last_response.headers['Cache-Control'].split(', ')).to_not include('no-cache') 47 | expect(last_response.headers['Cache-Control'].split(', ')).to include('private') 48 | end 49 | 50 | it 'ignores unsettable directives' do 51 | subject.get('/cherrys') do 52 | cache_control public: 60, max_age: 60 53 | end 54 | 55 | get 'cherrys' 56 | expect(last_response.headers['Cache-Control'].split(', ')).to_not include('public') 57 | expect(last_response.headers['Cache-Control'].split(', ')).to include('max-age=60') 58 | end 59 | 60 | it 'converts Time based values to Integers' do 61 | subject.get('/blueberries') do 62 | cache_control :public, max_age: (Time.now + 60) 63 | end 64 | 65 | get 'blueberries' 66 | expect(last_response.headers['Cache-Control'].split(', ')).to include('public', 'max-age=60') 67 | end 68 | 69 | it 'supports stale-while-revalidate' do 70 | subject.get('/stale_blueberries') do 71 | cache_control :public, stale_while_revalidate: 120 72 | end 73 | 74 | get 'stale_blueberries' 75 | expect(last_response.headers['Cache-Control'].split(', ')).to include('public', 'stale-while-revalidate=120') 76 | end 77 | 78 | it 'supports stale-while-error' do 79 | subject.get('/error_blueberries') do 80 | cache_control :public, stale_while_error: 120 81 | end 82 | 83 | get 'error_blueberries' 84 | expect(last_response.headers['Cache-Control'].split(', ')).to include('public', 'stale-while-error=120') 85 | end 86 | end 87 | 88 | describe 'expires' do 89 | it 'sets Expires header and passes along Cache-Control values' do 90 | subject.get('/grapefruits') do 91 | expires 60, :public, :no_cache 92 | end 93 | 94 | get 'grapefruits' 95 | expect(last_response.headers['Cache-Control'].split(', ')).to include('max-age=60', 'public', 'no-cache') 96 | expect(last_response.headers['Expires']).to eq((Time.now + 60).httpdate) 97 | end 98 | 99 | it 'allows Time to be specified as the expiry' do 100 | subject.get('/grapefruits') do 101 | expires((Time.now + 60), :public, :no_cache) 102 | end 103 | 104 | get 'grapefruits' 105 | expect(last_response.headers['Cache-Control'].split(', ')).to include('max-age=60', 'public', 'no-cache') 106 | expect(last_response.headers['Expires']).to eq((Time.now + 60).httpdate) 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grape::CacheControl 2 | 3 | `Cache-Control` and `Expires` helpers for [Grape](http://intridea.github.io/grape) 4 | 5 | ## Installation 6 | 7 | ```ruby 8 | gem 'grape-cache_control', '~> 0.0.1' 9 | ``` 10 | 11 | ```ruby 12 | require 'grape/cache_control' 13 | ``` 14 | 15 | ## Features / Usage Examples 16 | 17 | ```ruby 18 | # Cache-Control:public,max-age=900 19 | cache_control :public, max_age: 900 20 | 21 | # Cache-Control:public,max-age=900,stale-while-revalidate=1600 22 | cache_control :public, max_age: 900, stale_while_revalidate: 1600 23 | 24 | # Cache-Control:public,max-age=900,stale-while-error=1600 25 | cache_control :public, max_age: 900, stale_while_error: 1600 26 | 27 | # Cache-Control:public,no-store,max-age=900,s-maxage=86400 28 | cache_control :public, :no_store, max_age: 900, s_maxage:86400 29 | 30 | # Cache-Control:public,no-store,max-age=900,s-maxage=86400 31 | cache_control :public, :no_store, max_age: (Time.now + 900), s_maxage: (Time.now + 86400) 32 | 33 | # Cache-Control:private,must-revalidate,max=age=0 34 | cache_control :private, :must_revalidate, max_age: 0 35 | 36 | # Cache-Control:public,max-age=900 37 | # Expires:Tue, 25 Feb 2014 12:00:00 GMT 38 | expires 900, :public 39 | 40 | # Cache-Control:private,no-cache,no-store,max-age=900 41 | # Expires:Tue, 25 Feb 2014 12:00:00 GMT 42 | expires (Time.now + 900), :private, :no_store 43 | ``` 44 | 45 | ```ruby 46 | class API < Grape::API 47 | 48 | desc 'A completely random endpoint' 49 | get '/very-random' do 50 | # ensure proxy cache does not cache this 51 | cache_control :no_cache 52 | { very_random: Random.rand(100) } 53 | end 54 | 55 | desc 'An endpoint which rounds to the nearest 15 minutes' 56 | get '/fairly-static' do 57 | # cache for 15 minutes, publicly 58 | cache_control :public, max_age: 900 59 | { fairly_static: Time.at(Time.now.to_i - (Time.now.to_i % 900)) } 60 | end 61 | 62 | desc 'An endpoint which rounds to the nearest day' 63 | get '/reasonably-static' do 64 | # cache for 15 minutes, however allow the proxy cache to hold on to it a day 65 | cache_control :public, max_age: 900, s_maxage 86400 66 | { reasonably_static: Time.at(Time.now.to_i - (Time.now.to_i % 86400)) } 67 | end 68 | 69 | desc 'A very secure endpoint' 70 | get '/your-bank-account-details' do 71 | # explicitly don't store or cache this response and assume its immediately stale 72 | cache_control :private, :must_revalidate, max_age: 0 73 | { your_bank_account_details: '0000-0000-0000-0000' } 74 | end 75 | 76 | desc 'An endpoint which is sometimes private' 77 | get '/sometimes-private' do 78 | # cache for a minute but don't allow it to be stored 79 | cache_control :no_store, max_age: 60 80 | is_private = [true, false].sample 81 | # then add its privacy status 82 | cache_control is_private ? :public : :private 83 | { sometimes_private: is_private } 84 | end 85 | 86 | desc 'An endpoint which expires based on some business logic' 87 | get '/randomly-expiring' do 88 | # publicy cache this 89 | cache_control :public 90 | random_future_expiry = Random.rand(60 * 60) + 60 91 | is_expired = Time.now + random_future_expiry 92 | # then merge add it's expiration later on 93 | cache_control max_age: is_expired 94 | { randomly_expiring: is_expired } 95 | end 96 | 97 | end 98 | ``` 99 | 100 | Additional to the `cache_control` helper there is also an `expires` one too which augments cache_control as well as setting the `Expires` header 101 | 102 | ```ruby 103 | class API < Grape::API 104 | 105 | desc 'An endpoint which rounds to the nearest 15 minutes' 106 | get '/fairly-static' do 107 | # cache for 15 minutes, publicly 108 | expires 900, :public 109 | { fairly_static: Time.at(Time.now.to_i - (Time.now.to_i % 900)) } 110 | end 111 | 112 | desc 'An endpoint which expires based on some business logic' 113 | get '/randomly-expiring' do 114 | is_expired = Time.now + Random.rand(60 * 60) + 60 115 | # expire in some random future, publicly 116 | expires is_expired, :public 117 | { randomly_expiring: is_expired } 118 | end 119 | 120 | end 121 | ``` 122 | 123 | ## Badges 124 | 125 | [![Gem Version](http://img.shields.io/gem/v/grape-cache_control.svg)][gem] 126 | [![Build Status](http://img.shields.io/travis/karlfreeman/grape-cache_control.svg)][travis] 127 | [![Code Quality](http://img.shields.io/codeclimate/github/karlfreeman/grape-cache_control.svg)][codeclimate] 128 | [![Code Coverage](http://img.shields.io/codeclimate/coverage/github/karlfreeman/grape-cache_control.svg)][codeclimate] 129 | [![Gittip](http://img.shields.io/gittip/karlfreeman.svg)][gittip] 130 | 131 | ## Supported Ruby Versions 132 | 133 | This library aims to support and is [tested against][travis] the following Ruby 134 | implementations: 135 | 136 | - Ruby 2.1.0 137 | - Ruby 2.0.0 138 | - Ruby 1.9.3 139 | - [JRuby][jruby] 140 | 141 | # Credits 142 | 143 | Inspiration: 144 | 145 | - [Sinatra's Cache-Control/Expires](https://github.com/sinatra/sinatra/blob/faf2efc670bf4c6076c26d5234c577950c19b699/lib/sinatra/base.rb#L439-L492) 146 | 147 | Cribbed: 148 | 149 | - [Grape-Pagination](https://github.com/remind101/grape-pagination) 150 | 151 | [gem]: https://rubygems.org/gems/grape-cache_control 152 | [travis]: http://travis-ci.org/karlfreeman/grape-cache_control 153 | [codeclimate]: https://codeclimate.com/github/karlfreeman/grape-cache_control 154 | [gittip]: https://www.gittip.com/karlfreeman 155 | [jruby]: http://www.jruby.org 156 | --------------------------------------------------------------------------------