├── .gitignore ├── .travis.yml ├── Appraisals ├── CHANGELOG.md ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── bin └── rails ├── fastly-rails.gemspec ├── gemfiles ├── rails_3.gemfile ├── rails_4.gemfile ├── rails_41.gemfile └── rails_5.gemfile ├── lib ├── fastly-rails.rb └── fastly-rails │ ├── action_controller │ ├── cache_control_headers.rb │ └── surrogate_key_headers.rb │ ├── active_record │ └── surrogate_key.rb │ ├── client.rb │ ├── configuration.rb │ ├── engine.rb │ ├── errors.rb │ ├── mongoid │ └── surrogate_key.rb │ ├── rack │ └── remove_set_cookie_header.rb │ └── version.rb └── test ├── dummy ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── application_controller.rb │ │ ├── books_controller.rb │ │ └── concerns │ │ │ └── .keep │ ├── helpers │ │ └── application_helper.rb │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ ├── book.rb │ │ └── concerns │ │ │ └── .keep │ └── views │ │ ├── books │ │ ├── index.html.erb │ │ └── show.html.erb │ │ └── layouts │ │ └── application.html.erb ├── bin │ ├── bundle │ ├── rails │ └── rake ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── _rails_version_check.rb │ │ ├── backtrace_silencers.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ └── routes.rb ├── db │ ├── migrate │ │ ├── 20140407202136_create_books.rb │ │ └── 20150312044151_add_service_id_to_books.rb │ └── schema.rb ├── lib │ └── assets │ │ └── .keep ├── log │ └── .keep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico └── test │ ├── controllers │ └── books_controller_test.rb │ ├── factories │ └── books.rb │ ├── fixtures │ └── books.yml │ ├── integration │ └── fastly_headers_test.rb │ ├── lib │ └── remove_set_cookie_header_test.rb │ └── models │ └── book_test.rb ├── fastly-rails ├── action_controller │ └── api_test.rb ├── active_record │ └── purge_test.rb ├── cache_control_headers_test.rb ├── client_test.rb └── configuration_test.rb ├── fastly-rails_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | pkg/ 4 | test/dummy/db/*.sqlite3 5 | test/dummy/db/*.sqlite3-journal 6 | test/dummy/log/*.log 7 | test/dummy/tmp/ 8 | test/dummy/.sass-cache 9 | gemfiles/*.lock 10 | Gemfile.lock 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | before_script: 'bundle exec rake test:setup' 3 | script: 'bundle exec rake test:all' 4 | cache: bundler 5 | rvm: 6 | - 2.3.3 7 | - 2.2.2 8 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise 'rails-3' do 2 | gem 'rails', '~> 3.2.18' 3 | gem 'test-unit', '~> 3.0' 4 | end 5 | 6 | appraise 'rails-4' do 7 | gem 'rails', '~> 4.0.5' 8 | end 9 | 10 | appraise 'rails-41' do 11 | gem 'rails', '~> 4.1.1' 12 | end 13 | 14 | appraise 'rails-5' do 15 | gem 'rails', '~> 5.0' 16 | gem 'rails-controller-testing' 17 | end 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.8.0](https://github.com/fastly/fastly-rails/tree/v0.8.0) (2017-02-03) 4 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.7.1...v0.8.0) 5 | 6 | **Fixed bugs:** 7 | 8 | - Failing purges with fastly-ruby v1.1.5 [\#35](https://github.com/fastly/fastly-rails/issues/35) 9 | 10 | **Closed issues:** 11 | 12 | - "See note on cookies below" link in the Readme goes to 404. [\#65](https://github.com/fastly/fastly-rails/issues/65) 13 | - Rails 5 Support [\#64](https://github.com/fastly/fastly-rails/issues/64) 14 | - soft\_purge fails with - ArgumentError - wrong number of arguments \(given 2, expected 1\) [\#61](https://github.com/fastly/fastly-rails/issues/61) 15 | - Does not work with rails-api [\#38](https://github.com/fastly/fastly-rails/issues/38) 16 | 17 | **Merged pull requests:** 18 | 19 | - Support stale-while-revalidate and stale-if-error [\#73](https://github.com/fastly/fastly-rails/pull/73) ([lanej](https://github.com/lanej)) 20 | - test: use WebMock to disable live connections [\#72](https://github.com/fastly/fastly-rails/pull/72) ([lanej](https://github.com/lanej)) 21 | - chore: remove mime-types dependency [\#71](https://github.com/fastly/fastly-rails/pull/71) ([lanej](https://github.com/lanej)) 22 | - add rails-api support [\#70](https://github.com/fastly/fastly-rails/pull/70) ([lanej](https://github.com/lanej)) 23 | - remove generator configuration from railtie [\#69](https://github.com/fastly/fastly-rails/pull/69) ([lanej](https://github.com/lanej)) 24 | - Add Rails 5 support [\#68](https://github.com/fastly/fastly-rails/pull/68) ([lanej](https://github.com/lanej)) 25 | 26 | ## [v0.7.1](https://github.com/fastly/fastly-rails/tree/v0.7.1) (2016-07-29) 27 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/0.7.0...v0.7.1) 28 | 29 | **Closed issues:** 30 | 31 | - purging\_enabled not considered [\#59](https://github.com/fastly/fastly-rails/issues/59) 32 | - Soft Purge fails on 0.7.0 [\#57](https://github.com/fastly/fastly-rails/issues/57) 33 | 34 | **Merged pull requests:** 35 | 36 | - Bump fastly-ruby to 1.6 [\#60](https://github.com/fastly/fastly-rails/pull/60) ([blithe](https://github.com/blithe)) 37 | - fix ArgumentError for FastlyRails::Client\#purge\_by\_key [\#58](https://github.com/fastly/fastly-rails/pull/58) ([lanej](https://github.com/lanej)) 38 | 39 | ## [0.7.0](https://github.com/fastly/fastly-rails/tree/0.7.0) (2016-06-02) 40 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.6.0...0.7.0) 41 | 42 | **Merged pull requests:** 43 | 44 | - Adds soft purging from fastly-ruby client [\#56](https://github.com/fastly/fastly-rails/pull/56) ([gschorkopf](https://github.com/gschorkopf)) 45 | 46 | ## [v0.6.0](https://github.com/fastly/fastly-rails/tree/v0.6.0) (2016-05-10) 47 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.5.0...v0.6.0) 48 | 49 | **Closed issues:** 50 | 51 | - Enable use of current version of mime-types [\#52](https://github.com/fastly/fastly-rails/issues/52) 52 | 53 | **Merged pull requests:** 54 | 55 | - Bump fastly-ruby to 1.4.0 [\#55](https://github.com/fastly/fastly-rails/pull/55) ([blithe](https://github.com/blithe)) 56 | - bumping fastly dependency to 1.3.0 [\#54](https://github.com/fastly/fastly-rails/pull/54) ([caueguerra](https://github.com/caueguerra)) 57 | 58 | ## [v0.5.0](https://github.com/fastly/fastly-rails/tree/v0.5.0) (2016-04-04) 59 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.4.1...v0.5.0) 60 | 61 | **Merged pull requests:** 62 | 63 | - Depend on railties instead of rails [\#51](https://github.com/fastly/fastly-rails/pull/51) ([clupprich](https://github.com/clupprich)) 64 | 65 | ## [v0.4.1](https://github.com/fastly/fastly-rails/tree/v0.4.1) (2016-03-02) 66 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.3.0...v0.4.1) 67 | 68 | **Closed issues:** 69 | 70 | - purging\_enabled isn't supported in gem version 0.3.0 [\#46](https://github.com/fastly/fastly-rails/issues/46) 71 | - FastlyRails shouldn't raise error when api key isn't set [\#44](https://github.com/fastly/fastly-rails/issues/44) 72 | 73 | **Merged pull requests:** 74 | 75 | - Fixed link to finding API keys documentation [\#50](https://github.com/fastly/fastly-rails/pull/50) ([phantomwhale](https://github.com/phantomwhale)) 76 | - Update fastly-ruby gem to 1.2.1 [\#49](https://github.com/fastly/fastly-rails/pull/49) ([phantomwhale](https://github.com/phantomwhale)) 77 | - bump gem version [\#48](https://github.com/fastly/fastly-rails/pull/48) ([set5think](https://github.com/set5think)) 78 | - use static title instead of faker [\#45](https://github.com/fastly/fastly-rails/pull/45) ([set5think](https://github.com/set5think)) 79 | - Remove unneccessary attr\_readers [\#43](https://github.com/fastly/fastly-rails/pull/43) ([sobbybutter](https://github.com/sobbybutter)) 80 | - add link to docs for service ids and api keys [\#41](https://github.com/fastly/fastly-rails/pull/41) ([thommahoney](https://github.com/thommahoney)) 81 | - Allow purging to be toggled [\#39](https://github.com/fastly/fastly-rails/pull/39) ([gingermusketeer](https://github.com/gingermusketeer)) 82 | 83 | ## [v0.3.0](https://github.com/fastly/fastly-rails/tree/v0.3.0) (2015-03-13) 84 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.2.0...v0.3.0) 85 | 86 | **Closed issues:** 87 | 88 | - Incompatibilities with "service\_id" as a column name [\#36](https://github.com/fastly/fastly-rails/issues/36) 89 | 90 | **Merged pull requests:** 91 | 92 | - Rename service\_id to fastly\_service\_identifier [\#40](https://github.com/fastly/fastly-rails/pull/40) ([set5think](https://github.com/set5think)) 93 | 94 | ## [v0.2.0](https://github.com/fastly/fastly-rails/tree/v0.2.0) (2014-10-02) 95 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.7...v0.2.0) 96 | 97 | **Merged pull requests:** 98 | 99 | - Skey purge authentication [\#29](https://github.com/fastly/fastly-rails/pull/29) ([mmay](https://github.com/mmay)) 100 | 101 | ## [v0.1.7](https://github.com/fastly/fastly-rails/tree/v0.1.7) (2014-10-01) 102 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.6...v0.1.7) 103 | 104 | **Merged pull requests:** 105 | 106 | - Fix purge method receiver regression [\#30](https://github.com/fastly/fastly-rails/pull/30) ([jumph4x](https://github.com/jumph4x)) 107 | 108 | ## [v0.1.6](https://github.com/fastly/fastly-rails/tree/v0.1.6) (2014-09-23) 109 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.5...v0.1.6) 110 | 111 | **Merged pull requests:** 112 | 113 | - Add optional middleware for removing `Set-Cookie` [\#28](https://github.com/fastly/fastly-rails/pull/28) ([jessieay](https://github.com/jessieay)) 114 | - Purge without fetching the service first [\#27](https://github.com/fastly/fastly-rails/pull/27) ([mmay](https://github.com/mmay)) 115 | - Rename SurrogateControlHeaders to SurrogateKeyHeaders [\#26](https://github.com/fastly/fastly-rails/pull/26) ([mmay](https://github.com/mmay)) 116 | 117 | ## [v0.1.5](https://github.com/fastly/fastly-rails/tree/v0.1.5) (2014-09-04) 118 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.4...v0.1.5) 119 | 120 | **Closed issues:** 121 | 122 | - Version 0.1.3 breaks Namespaced routes in my app [\#23](https://github.com/fastly/fastly-rails/issues/23) 123 | 124 | **Merged pull requests:** 125 | 126 | - Index action should join resource keys [\#25](https://github.com/fastly/fastly-rails/pull/25) ([jessieay](https://github.com/jessieay)) 127 | 128 | ## [v0.1.4](https://github.com/fastly/fastly-rails/tree/v0.1.4) (2014-06-12) 129 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.2...v0.1.4) 130 | 131 | **Fixed bugs:** 132 | 133 | - Internal Server Error [\#19](https://github.com/fastly/fastly-rails/issues/19) 134 | 135 | **Closed issues:** 136 | 137 | - rake fastly:purge\_everything task [\#16](https://github.com/fastly/fastly-rails/issues/16) 138 | - Consider enforcing a Ruby style guide with Hound [\#13](https://github.com/fastly/fastly-rails/issues/13) 139 | 140 | **Merged pull requests:** 141 | 142 | - ensure configuration test never fails due to randomization [\#22](https://github.com/fastly/fastly-rails/pull/22) ([set5think](https://github.com/set5think)) 143 | - add service\_id as a configuration attribute, and fix purge [\#21](https://github.com/fastly/fastly-rails/pull/21) ([set5think](https://github.com/set5think)) 144 | 145 | ## [v0.1.2](https://github.com/fastly/fastly-rails/tree/v0.1.2) (2014-05-08) 146 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.1...v0.1.2) 147 | 148 | **Closed issues:** 149 | 150 | - Opt-in vs Opt-out for Edge Caching [\#2](https://github.com/fastly/fastly-rails/issues/2) 151 | 152 | **Merged pull requests:** 153 | 154 | - Add Rails v4.1.0 to appraisal [\#17](https://github.com/fastly/fastly-rails/pull/17) ([ezkl](https://github.com/ezkl)) 155 | - Add surrogate key adapter for Mongoid [\#15](https://github.com/fastly/fastly-rails/pull/15) ([joshfrench](https://github.com/joshfrench)) 156 | - Don't encourage use of hard-coded credentials [\#14](https://github.com/fastly/fastly-rails/pull/14) ([harlow](https://github.com/harlow)) 157 | 158 | ## [v0.1.1](https://github.com/fastly/fastly-rails/tree/v0.1.1) (2014-04-28) 159 | [Full Changelog](https://github.com/fastly/fastly-rails/compare/v0.1.0...v0.1.1) 160 | 161 | **Merged pull requests:** 162 | 163 | - Clarify surrogate key definition in readme [\#12](https://github.com/fastly/fastly-rails/pull/12) ([aspires](https://github.com/aspires)) 164 | - Move to opt-in edge caching [\#11](https://github.com/fastly/fastly-rails/pull/11) ([mmay](https://github.com/mmay)) 165 | - Use `self.method` instead of `extend self` [\#10](https://github.com/fastly/fastly-rails/pull/10) ([gabebw](https://github.com/gabebw)) 166 | - Add some more documentation [\#9](https://github.com/fastly/fastly-rails/pull/9) ([gabebw](https://github.com/gabebw)) 167 | - remove purgeable and fastly dir - not used at all, and not implemented [\#8](https://github.com/fastly/fastly-rails/pull/8) ([set5think](https://github.com/set5think)) 168 | - Clarify logic around authentication [\#6](https://github.com/fastly/fastly-rails/pull/6) ([gabebw](https://github.com/gabebw)) 169 | - Remove unnecessary InstanceMethods module [\#5](https://github.com/fastly/fastly-rails/pull/5) ([gabebw](https://github.com/gabebw)) 170 | - remove unnecessary items in test book model [\#4](https://github.com/fastly/fastly-rails/pull/4) ([set5think](https://github.com/set5think)) 171 | - clean up Configuration implementation [\#3](https://github.com/fastly/fastly-rails/pull/3) ([chaslemley](https://github.com/chaslemley)) 172 | - Update broken README link [\#1](https://github.com/fastly/fastly-rails/pull/1) ([harlow](https://github.com/harlow)) 173 | 174 | ## [v0.1.0](https://github.com/fastly/fastly-rails/tree/v0.1.0) (2014-04-17) 175 | 176 | 177 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Declare your gem's dependencies in fastly-rails.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Fastly, Inc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fastly Rails Plugin [![Build Status](https://travis-ci.org/fastly/fastly-rails.svg?branch=master)](https://travis-ci.org/fastly/fastly-rails) 2 | 3 | **Deprecation Notice** 4 | 5 | This repository has been deprecated. The official Fastly Ruby [client](https://docs.fastly.com/api/clients#ruby) is the [fastly-ruby](https://github.com/fastly/fastly-ruby) gem. 6 | 7 | Although this repository is no longer supported, it could be useful as a reference. 8 | 9 | ---- 10 | 11 | # Introduction 12 | Fastly dynamic caching integration for Rails. 13 | 14 | To use Fastly dynamic caching, you tag *any* response you wish to cache with unique Surrogate-Key HTTP Header(s) and then hit the Fastly API purge endpoint with the surrogate key when the response changes. The purge instantly replaces the cached response with a fresh response from origin. 15 | 16 | This plugin provides three main things: 17 | - Instance and class methods on ActiveRecord (or Mongoid) objects for surrogate keys and purging 18 | - Controller helpers to set Cache-Control and Surrogate-Control response headers 19 | - A controller helper to set Surrogate-Key headers on responses 20 | 21 | If you're not familiar with Fastly Surrogate Keys, you might want to check out [API Caching](http://www.fastly.com/blog/api-caching-part-1) and [Fastly Surrogate Keys](http://www.fastly.com/blog/surrogate-keys-part-1) for a primer. 22 | 23 | # Setup 24 | 25 | Add to your Gemfile 26 | 27 | ````ruby 28 | gem 'fastly-rails' 29 | ```` 30 | 31 | ## Configuration 32 | 33 | For information about how to find your Fastly API Key or a Fastly Service ID, refer to our [documentation](https://docs.fastly.com/guides/account-management-and-security/finding-and-managing-your-account-info). 34 | 35 | Create an initializer for Fastly configuration 36 | 37 | ````ruby 38 | FastlyRails.configure do |c| 39 | c.api_key = ENV['FASTLY_API_KEY'] # Fastly api key, required 40 | c.max_age = 86400 # time in seconds, optional, defaults to 2592000 (30 days) 41 | c.stale_while_revalidate = 86400 # time in seconds, optional, defaults to nil 42 | c.stale_if_error = 86400 # time in seconds, optional, defaults to nil 43 | c.service_id = ENV['SERVICE_ID'] # The Fastly service you will be using, required 44 | c.purging_enabled = !Rails.env.development? # No need to configure a client locally (AVAILABLE ONLY AS OF 0.4.0) 45 | end 46 | ```` 47 | > Note: purging only requires that you authenticate with your `api_key`. However, you can provide a `user` and `password` if you are using other endpoints in fastly-ruby that require full-auth. 48 | > Also, you must provide a service_id for purges to work. 49 | 50 | ## Usage 51 | 52 | ### Surrogate Keys 53 | 54 | Surrogate keys are what Fastly uses to purge groups of individual objects from our caches. 55 | 56 | This plugin adds a few methods to generate surrogate keys automatically. `table_key` and `record_key` methods are added to any ActiveRecord::Base instance. `table_key` is also added to any ActiveRecord::Base class. In fact, `table_key` on an instance just calls `table_key` on the class. 57 | 58 | We've chosen a simple surrogate key pattern by default. It is: 59 | 60 | ````ruby 61 | table_key: self.class.table_key # calls table_name on the class 62 | record_key: "#{table_key}/#{self.id}" 63 | ```` 64 | 65 | e.g. If you have an ActiveRecord Model named Book. 66 | 67 | ````ruby 68 | table key: books 69 | record key: books/1, books/2, books/3, etc... 70 | ```` 71 | 72 | You can easily override these methods in your models to use custom surrogate keys that may fit your specific application better: 73 | 74 | ````ruby 75 | def self.table_key 76 | "my_custom_table_key" 77 | end 78 | 79 | def record_key 80 | "my_custom_record_key"# Ensure these are unique for each record 81 | end 82 | ```` 83 | 84 | ### Headers 85 | 86 | This plugin adds a `set_cache_control_headers` method to ActionController. You'll need to add this in a `before_filter` or `after_filter` [see note on cookies below](https://github.com/fastly/fastly-rails#sessions-cookies-and-private-data) to any controller action that you wish to edge cache (see example below). The method sets Cache-Control and Surrogate-Control HTTP Headers with a default of 30 days (remember you can configure this, see the initializer setup above). 87 | 88 | It's up to you to set Surrogate-Key headers for objects that you want to be able to purge. 89 | 90 | To do this use the `set_surrogate_key_header` method on GET actions. 91 | 92 | ````ruby 93 | class BooksController < ApplicationController 94 | # include this before_filter in controller endpoints that you wish to edge cache 95 | before_filter :set_cache_control_headers, only: [:index, :show] 96 | # This can be used with any customer actions. Set these headers for GETs that you want to cache 97 | # e.g. before_filter :set_cache_control_headers, only: [:index, :show, :my_custom_action] 98 | 99 | def index 100 | @books = Book.all 101 | set_surrogate_key_header 'books', @books.map(&:record_key) 102 | end 103 | 104 | def show 105 | @book = Book.find(params[:id]) 106 | set_surrogate_key_header @book.record_key 107 | end 108 | end 109 | ```` 110 | 111 | ### Purges 112 | 113 | Any object that inherits from ActiveRecord will have `purge_all`, `soft_purge_all`, and `table_key` class methods available as well as `purge`, `soft_purge`, `purge_all`, and `soft_purge_all` instance methods. 114 | 115 | Example usage is show below. 116 | 117 | ````ruby 118 | class BooksController < ApplicationController 119 | 120 | def create 121 | @book = Book.new(params) 122 | if @book.save 123 | @book.purge_all 124 | render @book 125 | end 126 | end 127 | 128 | def update 129 | @book = Book.find(params[:id]) 130 | if @book.update(params) 131 | @book.purge 132 | render @book 133 | end 134 | end 135 | 136 | def delete 137 | @book = Book.find(params[:id]) 138 | if @book.destroy 139 | @book.purge # purge the record 140 | @book.purge_all # purge the collection so the record is no longer there 141 | end 142 | end 143 | end 144 | ```` 145 | 146 | To simplify controller methods, you could use ActiveRecord callbacks. e.g. 147 | 148 | ````ruby 149 | class Book < ActiveRecord::Base 150 | after_create :purge_all 151 | after_save :purge 152 | after_destroy :purge, :purge_all 153 | ... 154 | 155 | end 156 | ```` 157 | 158 | We have left these out intentially, as they could potentially cause issues when running locally or testing. If you do use these, pay attention, as using callbacks could also inadvertently overwrite HTTP Headers like Cache-Control or Set-Cookie and cause responses to not be properly cached. 159 | 160 | ### Service id 161 | 162 | One thing to note is that currently we expect a service_id to be defined in your FastlyRails.configuration. However, we've added localized methods so that your models can override your global service_id, if you needed to operate on more than one for any reason. NOTE: As of 0.3.0, we've renamed the class-level and instance-level `service_id` methods to `fastly_service_identifier` in the active_record and mongoid mix-ins. See the CHANGELOG for a link to the Github issue. 163 | 164 | Currently, this would require you to basically redefine `fastly_service_identifier` on the class level of your model: 165 | 166 | ````ruby 167 | class Book < ActiveRecord::Base 168 | def self.fastly_service_identifier 169 | 'MYSERVICEID' 170 | end 171 | end 172 | ```` 173 | 174 | 175 | ### Sessions, Cookies, and private data 176 | 177 | By default, Fastly will not cache any response containing a `Set-Cookie` header. In general, this is beneficial because caching responses that contain sensitive data is typically not done on shared caches. 178 | 179 | In this plugin the `set_cache_control_headers` method removes the `Set-Cookie` header from a 180 | request. In some cases, other libraries, particularily middleware, may insert or modify HTTP Headers outside the scope of where the `set_cache_control_heades` method is invoked in a controller action. For example, some authentication middleware will add a `Set-Cookie` header into requests *after* fastly-rails removes it. 181 | 182 | This can cause some requests that can (and should) be cached to not be cached due to the presence of `Set-Cookie`. 183 | 184 | In order to remove the `Set-Cookie` header in these cases, fastly-rails provides an optional 185 | piece of middleware that removes `Set-Cookie` when the `Surrogate-Control` or `Surrogate-Key` 186 | header is present (the `Surrogate-Control` header is also inserted by the 187 | `set_cache_control_headers` method and indicates that you want the endpoint to 188 | be cached by Fastly and do not need cookies). 189 | 190 | #### fastly-rails middleware to delete `Set-Cookie` 191 | 192 | To override a piece of middleware in Rails, insert custom middleware before 193 | it. Once you've identified which middleware is inserting the `Set-Cookie` 194 | header, add the following (in this example, `ExampleMiddleware` is what we are 195 | trying to override`: 196 | 197 | ```ruby 198 | # config/application.rb 199 | 200 | config.middleware.insert_before( 201 | ExampleMiddleware, 202 | "FastlyRails::Rack::RemoveSetCookieHeader" 203 | ) 204 | ``` 205 | 206 | 207 | 208 | ### Example 209 | 210 | Check out our example [todo app](https://github.com/mmay/todo) which has a full example of fastly-rails integration in a simple rails app. 211 | 212 | ## Future Work 213 | 214 | - Add an option to send purges in batches. 215 | 216 | > This will cut down on response delay from waiting for large amounts of purges to happen. This would primarily be geared towards write-heavy apps. 217 | 218 | - Your feedback 219 | 220 | ## Testing 221 | 222 | First, install all required gems: 223 | 224 | ```sh 225 | $ appraisal install 226 | ``` 227 | 228 | This engine is capable of testing against multiple versions of Rails. It uses the appraisal gem. To make this happen, use the appraisal command in lieu of `rake test`: 229 | 230 | ```sh 231 | $ appraisal rake test # tests against all the defined versions in the Appraisals file 232 | 233 | $ appraisal rails-3 rake test # finds a defined version in the Appraisals file called "rails-3" and only runs tests against this version 234 | ```` 235 | 236 | ## Supported Platforms 237 | We [run tests](https://travis-ci.org/fastly/fastly-rails) using all combinations of the following versions of Ruby and Rails: 238 | 239 | Ruby: 240 | - 1.9.3 241 | - 2.1.1 242 | 243 | Rails: 244 | - v3.2.18 245 | - v4.0.5 246 | - v4.1.1 247 | 248 | ### Other Platforms 249 | As of v0.1.2, *experimental* Mongoid support was added by @joshfrench of [Upworthy](http://www.upworthy.com/). 250 | 251 | ## Credits 252 | 253 | This plugin was developed by [Fastly](http://www.fastly.com/) with lots of help from our friend at [Hotel Tonight](http://www.hoteltonight.com), [Harlow Ward](https://twitter.com/futuresanta). Check out his blog about [Cache Invalidation with Fastly Surrogate Keys](http://www.hward.com/varnish-cache-invalidation-with-fastly-surrogate-keys) which is where many of the ideas used in this plugin originated. 254 | 255 | -- 256 | This project rocks and uses MIT-LICENSE. 257 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'rubygems' 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | 8 | require 'rdoc/task' 9 | 10 | RDoc::Task.new(:rdoc) do |rdoc| 11 | rdoc.rdoc_dir = 'rdoc' 12 | rdoc.title = 'FastlyRails' 13 | rdoc.options << '--line-numbers' 14 | rdoc.rdoc_files.include('README.rdoc') 15 | rdoc.rdoc_files.include('lib/**/*.rb') 16 | end 17 | 18 | APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) 19 | 20 | Bundler::GemHelper.install_tasks 21 | 22 | namespace :test do 23 | desc 'Install dependencies for all tests with appraisal' 24 | task :setup do 25 | sh 'bin/rails db:environment:set RAILS_ENV=test 2>&1 >/dev/null || exit 0' 26 | sh 'appraisal install' 27 | end 28 | 29 | desc 'Run all tests with appraisal' 30 | task :all do 31 | sh 'appraisal rake test' 32 | end 33 | end 34 | 35 | require 'rake/testtask' 36 | 37 | Rake::TestTask.new(:test) do |t| 38 | t.libs << 'lib' 39 | t.libs << 'test' 40 | t.pattern = 'test/**/*_test.rb' 41 | t.verbose = false 42 | end 43 | 44 | 45 | task default: :test 46 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. 3 | 4 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 5 | ENGINE_PATH = File.expand_path('../../lib/fastly-rails/engine', __FILE__) 6 | 7 | require 'rails/all' 8 | require 'rails/engine/commands' 9 | -------------------------------------------------------------------------------- /fastly-rails.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | require "fastly-rails/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "fastly-rails" 7 | s.version = FastlyRails::VERSION 8 | s.authors = ["Michael May", "Hassan Shahid", "Ezekiel Templin", "Thomas O'Neil", 'Blithe Rocher'] 9 | s.email = ["michael@fastly.com", "hassan@fastly.com", "ezekiel@fastly.com", "tommy@fastly.com", 'blithe@fastly.com'] 10 | s.homepage = "https://github.com/fastly/fastly-rails" 11 | s.summary = "Fastly instant purging integration for Rails" 12 | s.description = "Creates surrogate keys on ActiveRecord models, sets cache/surrogate headers, and adds a purge method to ActiveRecord objects." 13 | 14 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] 15 | s.test_files = Dir["test/**/*"] 16 | 17 | s.add_dependency "railties", '> 2', '< 6' 18 | s.add_dependency 'fastly', '~> 1.6' 19 | 20 | s.add_development_dependency "sqlite3" 21 | s.add_development_dependency "database_cleaner" 22 | s.add_development_dependency "factory_girl_rails" 23 | s.add_development_dependency "ffaker" 24 | s.add_development_dependency "minitest-spec-rails" 25 | s.add_development_dependency "appraisal" 26 | s.add_development_dependency 'webmock', ((RUBY_VERSION <= '1.9.3') ? '2.2.0' : '>= 2.3.0') 27 | s.add_development_dependency 'rails' 28 | end 29 | -------------------------------------------------------------------------------- /gemfiles/rails_3.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 3.2.18" 6 | gem "test-unit", "~> 3.0" 7 | 8 | gemspec :path => "../" 9 | -------------------------------------------------------------------------------- /gemfiles/rails_4.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 4.0.5" 6 | 7 | gemspec :path => "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_41.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 4.1.1" 6 | 7 | gemspec :path => "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_5.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.0" 6 | gem "rails-controller-testing" 7 | 8 | gemspec :path => "../" 9 | -------------------------------------------------------------------------------- /lib/fastly-rails.rb: -------------------------------------------------------------------------------- 1 | require "fastly-rails/engine" 2 | require "fastly-rails/client" 3 | require "fastly-rails/configuration" 4 | require "fastly-rails/errors" 5 | 6 | module FastlyRails 7 | 8 | def self.configuration 9 | @configuration ||= Configuration.new 10 | end 11 | 12 | def self.configure 13 | yield configuration if block_given? 14 | end 15 | 16 | def self.service_id 17 | raise NoServiceIdProvidedError if configuration.invalid_service_id? 18 | configuration.service_id 19 | end 20 | 21 | def self.purging_enabled? 22 | configuration.purging_enabled? 23 | end 24 | 25 | def self.purge_by_key(*args) 26 | client.purge_by_key(*args) if purging_enabled? 27 | end 28 | 29 | def self.client 30 | raise NoAPIKeyProvidedError unless configuration.authenticatable? 31 | 32 | @client ||= Client.new( 33 | :api_key => configuration.api_key, 34 | :user => configuration.user, 35 | :password => configuration.password, 36 | ) 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/fastly-rails/action_controller/cache_control_headers.rb: -------------------------------------------------------------------------------- 1 | module FastlyRails 2 | module CacheControlHeaders 3 | extend ActiveSupport::Concern 4 | 5 | # Sets Cache-Control and Surrogate-Control HTTP headers 6 | # Surrogate-Control is stripped at the cache, Cache-Control persists (in case of other caches in front of fastly) 7 | # Defaults are: 8 | # Cache-Control: 'public, no-cache' 9 | # Surrogate-Control: 'max-age: 30 days 10 | # custom config example: 11 | # {cache_control: 'public, no-cache, maxage=xyz', surrogate_control: 'max-age: blah'} 12 | def set_cache_control_headers(max_age = FastlyRails.configuration.max_age, opts = {}) 13 | request.session_options[:skip] = true # no cookies 14 | response.headers['Cache-Control'] = opts[:cache_control] || "public, no-cache" 15 | response.headers['Surrogate-Control'] = opts[:surrogate_control] || build_surrogate_control(max_age, opts) 16 | end 17 | 18 | private 19 | def build_surrogate_control(max_age, opts) 20 | surrogate_control = "max-age=#{max_age}" 21 | stale_while_revalidate = opts[:stale_while_revalidate] || FastlyRails.configuration.stale_while_revalidate 22 | stale_if_error = opts[:stale_if_error] || FastlyRails.configuration.stale_if_error 23 | 24 | surrogate_control += ", stale-while-revalidate=#{stale_while_revalidate}" if stale_while_revalidate 25 | surrogate_control += ", stale-if-error=#{stale_if_error}" if stale_if_error 26 | surrogate_control 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/fastly-rails/action_controller/surrogate_key_headers.rb: -------------------------------------------------------------------------------- 1 | module FastlyRails 2 | module SurrogateKeyHeaders 3 | 4 | # Sets Surrogate-Key HTTP header with one or more keys 5 | # strips session data from the request 6 | def set_surrogate_key_header(*surrogate_keys) 7 | request.session_options[:skip] = true # No Set-Cookie 8 | response.headers['Surrogate-Key'] = surrogate_keys.join(' ') 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/fastly-rails/active_record/surrogate_key.rb: -------------------------------------------------------------------------------- 1 | # Adds surrogate key methods to ActiveRecord models 2 | # Purge methods use a POST over PURGE 3 | # The choice of this HTTP method should not effect anything 4 | module FastlyRails 5 | module ActiveRecord 6 | module SurrogateKey 7 | extend ActiveSupport::Concern 8 | 9 | module ClassMethods 10 | 11 | def purge_all 12 | FastlyRails.purge_by_key(table_key) 13 | end 14 | 15 | def soft_purge_all 16 | FastlyRails.purge_by_key(table_key, true) 17 | end 18 | 19 | def table_key 20 | table_name 21 | end 22 | 23 | def fastly_service_identifier 24 | FastlyRails.service_id 25 | end 26 | end 27 | 28 | def record_key 29 | "#{table_key}/#{id}" 30 | end 31 | 32 | def table_key 33 | self.class.table_key 34 | end 35 | 36 | def purge 37 | FastlyRails.purge_by_key(record_key) 38 | end 39 | 40 | def soft_purge 41 | FastlyRails.purge_by_key(record_key, true) 42 | end 43 | 44 | def purge_all 45 | self.class.purge_all 46 | end 47 | 48 | def soft_purge_all 49 | self.class.soft_purge_all 50 | end 51 | 52 | def fastly_service_identifier 53 | self.class.fastly_service_identifier 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/fastly-rails/client.rb: -------------------------------------------------------------------------------- 1 | require 'fastly' 2 | require 'uri' 3 | 4 | module FastlyRails 5 | # A simple wrapper around the fastly-ruby client. 6 | class Client < DelegateClass(Fastly) 7 | def initialize(opts={}) 8 | super(Fastly.new(opts)) 9 | end 10 | 11 | def purge_by_key(*args) 12 | Fastly::Service.new({id: FastlyRails.service_id}, FastlyRails.client).purge_by_key(*args) 13 | end 14 | 15 | def purge_url(key) 16 | "/service/#{FastlyRails.service_id}/purge/#{URI.escape(key)}" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/fastly-rails/configuration.rb: -------------------------------------------------------------------------------- 1 | module FastlyRails 2 | class Configuration 3 | # 30 days 4 | MAX_AGE_DEFAULT = '2592000' 5 | 6 | attr_accessor :api_key, :user, :password, :max_age, :service_id, :stale_while_revalidate, :stale_if_error 7 | attr_writer :purging_enabled 8 | 9 | def self.max_age_default 10 | MAX_AGE_DEFAULT 11 | end 12 | 13 | def initialize 14 | @max_age = MAX_AGE_DEFAULT 15 | @purging_enabled = true 16 | end 17 | 18 | def authenticatable? 19 | !!api_key 20 | end 21 | 22 | def invalid_service_id? 23 | service_id_nil? || service_id_blank? 24 | end 25 | 26 | def purging_enabled? 27 | @purging_enabled 28 | end 29 | 30 | private 31 | 32 | def has_credentials? 33 | user && password 34 | end 35 | 36 | def service_id_nil? 37 | service_id.nil? 38 | end 39 | 40 | def service_id_blank? 41 | service_id.blank? 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/fastly-rails/engine.rb: -------------------------------------------------------------------------------- 1 | require 'fastly-rails/active_record/surrogate_key' 2 | require 'fastly-rails/mongoid/surrogate_key' 3 | require 'fastly-rails/action_controller/cache_control_headers' 4 | require 'fastly-rails/action_controller/surrogate_key_headers' 5 | require 'fastly-rails/rack/remove_set_cookie_header' 6 | 7 | module FastlyRails 8 | class Engine < ::Rails::Engine 9 | isolate_namespace FastlyRails 10 | 11 | ActiveSupport.on_load :action_controller do 12 | ActionController::Base.send :include, FastlyRails::CacheControlHeaders 13 | ActionController::Base.send :include, FastlyRails::SurrogateKeyHeaders 14 | 15 | if defined?(ActionController::API) 16 | ActionController::API.send :include, FastlyRails::CacheControlHeaders 17 | ActionController::API.send :include, FastlyRails::SurrogateKeyHeaders 18 | end 19 | end 20 | 21 | ActiveSupport.on_load :active_record do 22 | ::ActiveRecord::Base.send :include, FastlyRails::ActiveRecord::SurrogateKey 23 | end 24 | 25 | ActiveSupport.on_load :mongoid do 26 | ::Mongoid::Document.send :include, FastlyRails::Mongoid::SurrogateKey 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/fastly-rails/errors.rb: -------------------------------------------------------------------------------- 1 | module FastlyRails 2 | 3 | class NoAPIKeyProvidedError < ArgumentError; end 4 | 5 | class NoServiceIdProvidedError < ArgumentError; end 6 | end 7 | -------------------------------------------------------------------------------- /lib/fastly-rails/mongoid/surrogate_key.rb: -------------------------------------------------------------------------------- 1 | # Adds surrogate key methods to ActiveRecord models 2 | module FastlyRails 3 | module Mongoid 4 | module SurrogateKey 5 | extend ActiveSupport::Concern 6 | 7 | module ClassMethods 8 | 9 | def purge_all 10 | FastlyRails.purge_by_key(table_key) 11 | end 12 | 13 | def table_key 14 | collection_name 15 | end 16 | 17 | def fastly_service_identifier 18 | FastlyRails.service_id 19 | end 20 | end 21 | 22 | def record_key 23 | "#{table_key}/#{id}" 24 | end 25 | 26 | def table_key 27 | self.class.table_key 28 | end 29 | 30 | def purge 31 | FastlyRails.purge_by_key(record_key) 32 | end 33 | 34 | def purge_all 35 | self.class.purge_all 36 | end 37 | 38 | def fastly_service_identifier 39 | self.class.fastly_service_identifier 40 | end 41 | 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/fastly-rails/rack/remove_set_cookie_header.rb: -------------------------------------------------------------------------------- 1 | module FastlyRails 2 | module Rack 3 | class RemoveSetCookieHeader 4 | def initialize(app) 5 | @app = app 6 | end 7 | 8 | def call(env) 9 | status, headers, response = @app.call(env) 10 | 11 | if headers["Surrogate-Control"] || headers["Surrogate-Key"] 12 | headers.delete("Set-Cookie") 13 | end 14 | 15 | [status, headers, response] 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/fastly-rails/version.rb: -------------------------------------------------------------------------------- 1 | module FastlyRails 2 | VERSION = "0.8.0" 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Dummy::Application.load_tasks 7 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require_tree . 14 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/books_controller.rb: -------------------------------------------------------------------------------- 1 | class BooksController < ApplicationController 2 | before_filter :set_cache_control_headers, only: [:index, :show] 3 | before_filter :find_book, :only => [:show, :edit, :update, :destroy] 4 | 5 | def index 6 | @books = Book.all 7 | set_surrogate_key_header 'books', @books.map(&:record_key) 8 | end 9 | 10 | def show 11 | set_surrogate_key_header @book.record_key 12 | end 13 | 14 | def new 15 | @book = Book.new 16 | end 17 | 18 | def create 19 | @book = Book.new(books_params) 20 | if @book.save 21 | redirect_to books_path 22 | else 23 | flash[:notice] = "failed to create book" 24 | render :new 25 | end 26 | end 27 | 28 | def edit 29 | end 30 | 31 | def update 32 | if rails_4? 33 | method = :update 34 | else 35 | method = :update_attributes 36 | end 37 | if @book.send(method, books_params) 38 | redirect_to book_path(@book) 39 | else 40 | flash[:notice] = "failed to update book" 41 | render :edit 42 | end 43 | end 44 | 45 | def destroy 46 | if @book.destroy 47 | redirect_to books_path 48 | else 49 | flash[:notice] = "failed to destroy book" 50 | redirect_to book_path(@book) 51 | end 52 | end 53 | 54 | private 55 | 56 | def books_params 57 | if rails_4? 58 | params.require(:book).permit! 59 | else 60 | params[:book] 61 | end 62 | end 63 | 64 | def find_book 65 | @book = Book.find(params[:id]) 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/app/mailers/.keep -------------------------------------------------------------------------------- /test/dummy/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/app/models/.keep -------------------------------------------------------------------------------- /test/dummy/app/models/book.rb: -------------------------------------------------------------------------------- 1 | class Book < ActiveRecord::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/views/books/index.html.erb: -------------------------------------------------------------------------------- 1 |

Books#index

2 |

Find me in app/views/books/index.html.erb

3 | -------------------------------------------------------------------------------- /test/dummy/app/views/books/show.html.erb: -------------------------------------------------------------------------------- 1 |

Books#show

2 |

Find me in app/views/books/show.html.erb

3 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> 6 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | Bundler.require(*Rails.groups) 6 | require "fastly-rails" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 15 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 16 | # config.time_zone = 'Central Time (US & Canada)' 17 | 18 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 19 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 20 | # config.i18n.default_locale = :de 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # Version of your assets, change this if you want to expire all your assets. 36 | config.assets.version = '1.0' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | 64 | # Ignore bad email addresses and do not raise email delivery errors. 65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 66 | # config.action_mailer.raise_delivery_errors = false 67 | 68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 69 | # the I18n.default_locale when a translation can not be found). 70 | config.i18n.fallbacks = true 71 | 72 | # Send deprecation notices to registered listeners. 73 | config.active_support.deprecation = :notify 74 | 75 | # Disable automatic flushing of the log to improve performance. 76 | # config.autoflush_log = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | end 81 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | config.active_support.test_order = :sorted 38 | end 39 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/_rails_version_check.rb: -------------------------------------------------------------------------------- 1 | def rails_4? 2 | Rails.version.to_f >= 4.0 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | if rails_4? 13 | Dummy::Application.config.secret_key_base = 'e77943c9d96c3346198a6cd36077fbc6e1a047599fc2cc7d04c77bffe53decb7d9abb99688a285fe28671f5f9c291480672921d56585b5a222cef54d4fae249b' 14 | else 15 | Dummy::Application.config.secret_token = 'e77943c9d96c3346198a6cd36077fbc6e1a047599fc2cc7d04c77bffe53decb7d9abb99688a285fe28671f5f9c291480672921d56585b5a222cef54d4fae249b' 16 | end 17 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | resources :books 4 | mount FastlyRails::Engine => "/fastly-rails" 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20140407202136_create_books.rb: -------------------------------------------------------------------------------- 1 | class CreateBooks < ActiveRecord::Migration 2 | def change 3 | create_table :books do |t| 4 | t.text :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20150312044151_add_service_id_to_books.rb: -------------------------------------------------------------------------------- 1 | class AddServiceIdToBooks < ActiveRecord::Migration 2 | def change 3 | add_column :books, :service_id, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20150312044151) do 15 | 16 | create_table "books", force: true do |t| 17 | t.text "name" 18 | t.datetime "created_at" 19 | t.datetime "updated_at" 20 | t.string "service_id" 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /test/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/log/.keep -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The page you were looking for doesn't exist.

54 |

You may have mistyped the address or the page may have moved.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The change you wanted was rejected.

54 |

Maybe you tried to change something you didn't have access to.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

We're sorry, but something went wrong.

54 |
55 |

If you are the application owner check the logs for more information.

56 | 57 | 58 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastly/fastly-rails/47a4c18832c2491d53fd9bfa3d94507dec792024/test/dummy/public/favicon.ico -------------------------------------------------------------------------------- /test/dummy/test/controllers/books_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BooksControllerTest < ActionController::TestCase 4 | 5 | def setup 6 | WebMock.stub_request(:any, /.*/). 7 | to_return( 8 | :status => 200, 9 | :body => "{}" 10 | ) 11 | @no_of_books = 5 12 | create_list :book, @no_of_books 13 | end 14 | 15 | test "index" do 16 | get :index 17 | assert_response :success, 'it should return successfully' 18 | assert_equal @no_of_books, assigns(:books).count, "it should retrieve #{@no_of_books} books" 19 | end 20 | 21 | test "index with multiple params" do 22 | get :index 23 | expected_surrogate_keys = 'books books/1 books/2 books/3 books/4 books/5' 24 | 25 | assert_equal expected_surrogate_keys, response.headers['Surrogate-Key'], 'surrogate keys returned' 26 | end 27 | 28 | test "show" do 29 | expected_id = 1 30 | get :show, {:id => expected_id} 31 | assert_response :success, 'it should return successfully' 32 | assert_not_nil assigns(:book), '@book should not be nil' 33 | assert_instance_of Book, assigns(:book), 'it should be an instance of a book' 34 | assert_equal expected_id, assigns(:book).id, 'book.id should be the expected id' 35 | assert_equal "books/#{expected_id}", response.headers['Surrogate-Key'], "surrogate key for book" 36 | end 37 | 38 | test "create" do 39 | expected_name = 'newly-created-book' 40 | assert_difference('Book.count') do 41 | post :create, :book => {'name' => expected_name} 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/dummy/test/factories/books.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :book do 5 | name 'some book name' 6 | service_id 'asdf' 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/test/fixtures/books.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyText 5 | 6 | two: 7 | name: MyText 8 | -------------------------------------------------------------------------------- /test/dummy/test/integration/fastly_headers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FastlyHeadersTest < ActionDispatch::IntegrationTest 4 | 5 | test '/books index page should have fastly headers' do 6 | 7 | get '/books' 8 | assert_response :success 9 | 10 | assert !request.session_options[:skip].nil? 11 | assert_equal true, request.session_options[:skip] 12 | 13 | assert response.headers.key?('Cache-Control') 14 | assert_equal 'public, no-cache', response.headers['Cache-Control'] 15 | 16 | assert response.headers.key?('Surrogate-Control') 17 | max_age = FastlyRails.configuration.max_age 18 | assert_equal "max-age=#{max_age}", response.headers['Surrogate-Control'] 19 | end 20 | 21 | test '/books/:id show page should have fastly headers' do 22 | 23 | create :book, :id => 1 24 | 25 | get '/books/1' 26 | assert_response :success 27 | 28 | assert !request.session_options[:skip].nil? 29 | assert_equal true, request.session_options[:skip] 30 | 31 | assert response.headers.key?('Cache-Control') 32 | assert_equal 'public, no-cache', response.headers['Cache-Control'] 33 | 34 | assert response.headers.key?('Surrogate-Control') 35 | max_age = FastlyRails.configuration.max_age 36 | assert_equal "max-age=#{max_age}", response.headers['Surrogate-Control'] 37 | 38 | 39 | end 40 | 41 | test 'POST /books should not have fastly headers/ fastly header values' do 42 | 43 | assert_difference('Book.count') do 44 | post '/books', 'book' => { 'name' => 'some new book' } 45 | end 46 | 47 | assert_nil request.session_options[:skip] 48 | 49 | assert response.headers.key?('Cache-Control') 50 | assert_equal 'no-cache', response.headers['Cache-Control'] 51 | 52 | assert !response.headers.key?('Surrogate-Control') 53 | 54 | end 55 | 56 | test 'PUT /books/:id should not have fastly headers/ fastly header values' do 57 | 58 | create :book, :id => 1, :name => 'some new book' 59 | 60 | put '/books/1', 'id' => 1, 'book' => { 'name' => 'changed book' } 61 | 62 | assert_equal 'changed book', Book.find(1).name 63 | 64 | assert_nil request.session_options[:skip] 65 | 66 | assert response.headers.key?('Cache-Control') 67 | assert_equal 'no-cache', response.headers['Cache-Control'] 68 | 69 | assert !response.headers.key?('Surrogate-Control') 70 | end 71 | 72 | test 'DELETE /books/:id should not have fastly headers/ fastly header values' do 73 | create :book, :id => 1 74 | 75 | delete '/books/1' 76 | 77 | assert Book.where(:id => 1).empty? 78 | 79 | assert_nil request.session_options[:skip] 80 | 81 | assert response.headers.key?('Cache-Control') 82 | assert_equal 'no-cache', response.headers['Cache-Control'] 83 | 84 | assert !response.headers.key?('Surrogate-Control') 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/dummy/test/lib/remove_set_cookie_header_test.rb: -------------------------------------------------------------------------------- 1 | describe FastlyRails::Rack::RemoveSetCookieHeader do 2 | it "removes 'set-cookie' header if 'surrogate-control' header present" do 3 | headers = { "Surrogate-Control" => "test", "Set-Cookie" => "NOOO" } 4 | 5 | app = Rack::Builder.new do 6 | use FastlyRails::Rack::RemoveSetCookieHeader 7 | run lambda { |env| Rack::Response.new("", 200, headers).finish } 8 | end 9 | 10 | env = Rack::MockRequest.env_for('/') 11 | response = Rack::MockResponse.new(*app.call(env)) 12 | 13 | assert_nil response.headers['Set-Cookie'] 14 | end 15 | 16 | it "removes 'set-cookie' header if 'surrogate-key' header present" do 17 | headers = { "Surrogate-Key" => "test", "Set-Cookie" => "NOOO" } 18 | 19 | app = Rack::Builder.new do 20 | use FastlyRails::Rack::RemoveSetCookieHeader 21 | run lambda { |env| Rack::Response.new("", 200, headers).finish } 22 | end 23 | 24 | env = Rack::MockRequest.env_for('/') 25 | response = Rack::MockResponse.new(*app.call(env)) 26 | 27 | assert_nil response.headers['Set-Cookie'] 28 | end 29 | 30 | it "keeps 'set-cookie' if no 'surrogate-control' or 'surrogate-key'" do 31 | headers = { "Set-Cookie" => "yes!!" } 32 | 33 | app = Rack::Builder.new do 34 | use FastlyRails::Rack::RemoveSetCookieHeader 35 | run lambda { |env| Rack::Response.new("", 200, headers).finish } 36 | end 37 | 38 | env = Rack::MockRequest.env_for('/') 39 | response = Rack::MockResponse.new(*app.call(env)) 40 | 41 | assert_equal response.headers['Set-Cookie'], "yes!!" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/dummy/test/models/book_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | # class BookTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | # end 8 | 9 | describe Book do 10 | 11 | let (:book) { create :book, id: 1 } 12 | 13 | it "should have Fastly::SurrogateKey instance methods" do 14 | 15 | [:record_key, :table_key, :purge, :purge_all, :soft_purge, :soft_purge_all].each do |method| 16 | assert_respond_to book, method 17 | end 18 | 19 | end 20 | 21 | it "should have Fastly::SurrogateKey class methods" do 22 | 23 | [:table_key, :purge_all, :soft_purge_all].each do |method| 24 | assert_respond_to Book, method 25 | end 26 | 27 | end 28 | 29 | it "should have a record_key of books/1" do 30 | assert_equal 'books/1', book.record_key 31 | end 32 | 33 | it "should have a table_key that returns its table name" do 34 | assert_equal 'books', book.table_key 35 | end 36 | 37 | it "should have class.table_key and instance.table_key that are equivalent" do 38 | assert_equal Book.table_key, book.table_key 39 | end 40 | 41 | describe 'fastly_service_identifier' do 42 | before do 43 | FastlyRails.configuration.service_id = 'some-service-id' 44 | end 45 | 46 | it 'is a class method' do 47 | assert_respond_to Book, :fastly_service_identifier 48 | end 49 | 50 | it 'is an instance method' do 51 | assert_respond_to book, :fastly_service_identifier 52 | end 53 | 54 | it 'is true that class.fastly_service_identifier == instance.fastly_service_identifier' do 55 | assert_equal Book.fastly_service_identifier, book.fastly_service_identifier 56 | end 57 | 58 | it 'does not equal the value of `service_id` on the model' do 59 | refute_equal book.service_id, book.fastly_service_identifier 60 | end 61 | 62 | after do 63 | FastlyRails.configuration.service_id = nil 64 | end 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /test/fastly-rails/action_controller/api_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../test_helper' 2 | 3 | describe 'ActionController::API' do 4 | before { skip unless defined?(ActionController::API) } 5 | 6 | let(:controller) { Class.new(ActionController::API) } 7 | 8 | it 'adds control modules' do 9 | assert_includes controller.ancestors, FastlyRails::SurrogateKeyHeaders 10 | assert_includes controller.ancestors, FastlyRails::CacheControlHeaders 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/fastly-rails/active_record/purge_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../test_helper' 2 | 3 | describe FastlyRails::ActiveRecord::SurrogateKey do 4 | let(:api_key) { 'test' } 5 | let(:user) { nil } 6 | let(:password) { nil } 7 | let(:max_age) { 100000 } 8 | let(:service_id) { 'someserviceid' } 9 | let(:client) { FastlyRails.client } 10 | 11 | before do 12 | FastlyRails.configure do |c| 13 | c.api_key = api_key 14 | c.user = user 15 | c.password = password 16 | c.max_age = max_age 17 | c.service_id = service_id 18 | end 19 | end 20 | 21 | it 'performs a soft_purge' do 22 | assert_equal({ 'status' => 'ok' }, Book.new(id: 1).soft_purge) 23 | end 24 | 25 | it 'performs a soft_purge_all' do 26 | assert_equal({ 'status' => 'ok' }, Book.new(id: 1).soft_purge_all) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/fastly-rails/cache_control_headers_test.rb: -------------------------------------------------------------------------------- 1 | require_relative '../test_helper' 2 | 3 | describe 'FastlyRails::CacheControlHeaders' do 4 | let(:controller) do 5 | Class.new do 6 | include FastlyRails::CacheControlHeaders 7 | 8 | attr_reader :request, :response 9 | 10 | def initialize 11 | @request = Struct.new(:session_options).new({}) 12 | @response = Struct.new(:headers).new({}) 13 | end 14 | end.new 15 | end 16 | 17 | before { FastlyRails.configure { |c| c.max_age = 2592000 } } 18 | 19 | it 'sets max_age' do 20 | controller.set_cache_control_headers(16) 21 | 22 | expected = {"Cache-Control"=>"public, no-cache", "Surrogate-Control"=>"max-age=16"} 23 | actual = controller.response.headers 24 | 25 | assert_equal expected, actual 26 | end 27 | 28 | it 'sets stale_while_revalidate' do 29 | controller.set_cache_control_headers(FastlyRails.configuration.max_age, stale_while_revalidate: 32) 30 | 31 | expected = {"Cache-Control"=>"public, no-cache", "Surrogate-Control"=>"max-age=2592000, stale-while-revalidate=32"} 32 | actual = controller.response.headers 33 | 34 | assert_equal expected, actual 35 | end 36 | 37 | it 'sets stale_if_error' do 38 | controller.set_cache_control_headers(FastlyRails.configuration.max_age, stale_if_error: 64) 39 | 40 | expected = {"Cache-Control"=>"public, no-cache", "Surrogate-Control"=>"max-age=2592000, stale-if-error=64"} 41 | actual = controller.response.headers 42 | 43 | assert_equal expected, actual 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/fastly-rails/client_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | describe FastlyRails::Client do 4 | 5 | let(:client) do 6 | FastlyRails::Client.new( 7 | :api_key => 'KEY' 8 | ) 9 | end 10 | 11 | it 'should create a fastly rails client' do 12 | assert_instance_of FastlyRails::Client, client 13 | end 14 | 15 | it 'should be delegated to the fastly client' do 16 | assert_instance_of Fastly, client.instance_variable_get('@delegate_dc_obj') 17 | end 18 | 19 | # This is kind of a static-ish test that technically isn't necessary 20 | # But if we decide to lock down the methods available from the client 21 | # through this interface, we can ensure they're available via this test 22 | 23 | it 'should respond to methods available in the fastly client' do 24 | 25 | methods = [ 26 | :purge, 27 | :get_service 28 | ] 29 | 30 | methods.each do |method| 31 | assert_respond_to client, method 32 | end 33 | 34 | end 35 | 36 | describe 'purge_by_key' do 37 | it 'raises if purge called and no service id configured' do 38 | FastlyRails.configuration.service_id = nil 39 | 40 | assert_raises FastlyRails::NoServiceIdProvidedError do 41 | client.purge_by_key('test') 42 | end 43 | end 44 | 45 | it 'should be authed' do 46 | assert_equal true, client.authed? 47 | assert_equal false, client.fully_authed? 48 | end 49 | 50 | describe 'when username and password are supplied' do 51 | before do 52 | client.client.user = 'USER' 53 | client.client.password = 'PASS' 54 | end 55 | 56 | it 'should be fully authed' do 57 | assert_equal true, client.authed? 58 | assert_equal true, client.fully_authed? 59 | end 60 | end 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /test/fastly-rails/configuration_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | describe FastlyRails::Configuration do 4 | let(:configuration) { FastlyRails::Configuration.new } 5 | 6 | it 'should create a fastly rails client' do 7 | assert_instance_of FastlyRails::Configuration, configuration 8 | end 9 | 10 | it 'should have configuration attributes' do 11 | methods = [ 12 | :api_key, 13 | :user, 14 | :password, 15 | :max_age, 16 | :stale_while_revalidate, 17 | :stale_if_error, 18 | :service_id 19 | ] 20 | 21 | methods.each do |method| 22 | assert_respond_to configuration, method 23 | end 24 | end 25 | 26 | describe 'max_age_default' do 27 | it 'should be a class method' do 28 | assert_respond_to FastlyRails::Configuration, :max_age_default 29 | end 30 | 31 | it 'should return the value of a constant MAX_AGE_DEFAULT' do 32 | assert_equal FastlyRails::Configuration::MAX_AGE_DEFAULT, FastlyRails::Configuration.max_age_default 33 | end 34 | end 35 | 36 | describe 'authenticatable?' do 37 | before do 38 | configuration.api_key = nil 39 | configuration.user = nil 40 | configuration.password = nil 41 | end 42 | 43 | it 'should be an instance method' do 44 | assert_respond_to configuration, :authenticatable? 45 | end 46 | 47 | it 'should return false if api_key is nil' do 48 | assert_equal false, configuration.authenticatable? 49 | end 50 | 51 | it 'should return false if only user is not nil' do 52 | configuration.user = 'user' 53 | 54 | assert_equal false, configuration.authenticatable? 55 | end 56 | 57 | it 'should return true if only api_key is not nil' do 58 | configuration.api_key = 'test' 59 | assert_equal true, configuration.authenticatable? 60 | end 61 | 62 | it 'should return false if only user and password are not nil' do 63 | configuration.user = 'user' 64 | configuration.password = 'password' 65 | 66 | assert_equal false, configuration.authenticatable? 67 | end 68 | end 69 | 70 | describe 'purging_enabled?' do 71 | it 'is enabled by default' do 72 | assert_equal true, configuration.purging_enabled? 73 | end 74 | 75 | it 'is enabled when set to true' do 76 | configuration.purging_enabled = true 77 | assert_equal true, configuration.purging_enabled? 78 | end 79 | 80 | it 'is disabled when set to false' do 81 | configuration.purging_enabled = false 82 | assert_equal false, configuration.purging_enabled? 83 | end 84 | end 85 | 86 | it 'should have a default value for max_age since none was provided' do 87 | assert_equal FastlyRails::Configuration.max_age_default, configuration.max_age 88 | end 89 | 90 | describe 'invalid_service_id?' do 91 | it 'should return true for a nil service_id' do 92 | assert_nil configuration.service_id 93 | assert_equal true, configuration.invalid_service_id? 94 | end 95 | 96 | it 'should return true for a blank service_id' do 97 | configuration.service_id = '' 98 | assert_equal true, configuration.invalid_service_id? 99 | end 100 | 101 | it 'should return false for a non-blank, non-nil service_id' do 102 | configuration.service_id = 'notblank' 103 | assert_equal false, configuration.invalid_service_id? 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /test/fastly-rails_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | describe FastlyRails do 4 | let(:api_key) { 'test' } 5 | let(:user) { nil } 6 | let(:password) { nil } 7 | let(:max_age) { 100000 } 8 | let(:configuration) { FastlyRails::Configuration.new } 9 | let(:service_id) { 'someserviceid' } 10 | let(:client) { FastlyRails.client } 11 | 12 | before do 13 | FastlyRails.instance_variable_set('@configuration', configuration) 14 | end 15 | 16 | it 'should be a module' do 17 | assert_kind_of Module, FastlyRails 18 | end 19 | 20 | describe 'credentials not provided' do 21 | it 'should raise an error if configuration is not authenticatable' do 22 | assert_equal false, configuration.authenticatable? 23 | assert_equal true, configuration.invalid_service_id? 24 | assert_raises FastlyRails::NoAPIKeyProvidedError do 25 | client 26 | end 27 | assert_raises FastlyRails::NoServiceIdProvidedError do 28 | FastlyRails.service_id 29 | end 30 | end 31 | end 32 | 33 | describe 'credentials provided' do 34 | before do 35 | FastlyRails.configure do |c| 36 | c.api_key = api_key 37 | c.user = user 38 | c.password = password 39 | c.max_age = max_age 40 | c.service_id = service_id 41 | end 42 | end 43 | 44 | it 'should have configuration options set up' do 45 | assert_equal api_key, configuration.api_key 46 | assert_equal user, configuration.user 47 | assert_equal password, configuration.password 48 | assert_equal max_age, configuration.max_age 49 | assert_equal service_id, configuration.service_id 50 | assert_equal true, configuration.purging_enabled? 51 | end 52 | 53 | it 'should return a valid client' do 54 | assert_instance_of FastlyRails::Client, client 55 | end 56 | end 57 | 58 | describe 'purge_by_key' do 59 | let(:client) { MiniTest::Mock.new } 60 | let(:key) { "key" } 61 | 62 | it 'delegates to the client when purging is enabled' do 63 | FastlyRails.stub(:client, client) do 64 | FastlyRails.stub(:purging_enabled?, true) do 65 | client.expect(:purge_by_key, nil, [key]) 66 | FastlyRails.purge_by_key(key) 67 | client.verify 68 | end 69 | end 70 | end 71 | 72 | it 'allows soft purging' do 73 | FastlyRails.stub(:client, client) do 74 | FastlyRails.stub(:purging_enabled?, true) do 75 | client.expect(:purge_by_key, nil, [key, true]) 76 | FastlyRails.purge_by_key(key, true) 77 | client.verify 78 | end 79 | end 80 | end 81 | 82 | it 'does nothing when purging is disabled' do 83 | configuration.purging_enabled = false 84 | FastlyRails.stub(:client, client) do 85 | FastlyRails.purge_by_key(key) 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 5 | require "rails/test_help" 6 | require "minitest/autorun" 7 | require 'database_cleaner' 8 | require 'ffaker' 9 | require 'factory_girl_rails' 10 | require 'webmock/minitest' 11 | 12 | begin 13 | require 'rails-controller-testing' 14 | rescue LoadError 15 | end 16 | 17 | Rails.backtrace_cleaner.remove_silencers! 18 | 19 | #include factories 20 | Dir["#{File.dirname(__FILE__)}/dummy/test/factories/*.rb"].each { |f| require f } 21 | # Load support files 22 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 23 | 24 | # Load fixtures from the engine 25 | # if ActiveSupport::TestCase.method_defined?(:fixture_path=) 26 | # ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) 27 | # ActiveSupport::TestCase.fixtures :all 28 | # end 29 | 30 | ActiveRecord::Migrator.migrate File.expand_path('../dummy/db/migrate/', __FILE__) 31 | 32 | class Minitest::Spec 33 | include FactoryGirl::Syntax::Methods 34 | 35 | before :each do 36 | stub_request(:any, "https://api.fastly.com/login"). 37 | to_return( 38 | :status => 200, 39 | :body => "{}" 40 | ) 41 | stub_request(:post, /https:\/\/api.fastly.com\/service\/.*\/purge\/.*/) 42 | .to_return( 43 | body: "{\"status\":\"ok\"}" 44 | ) 45 | 46 | WebMock.disable_net_connect! 47 | 48 | DatabaseCleaner.start 49 | end 50 | 51 | after :each do 52 | DatabaseCleaner.clean 53 | end 54 | 55 | end 56 | 57 | class ActionController::TestCase 58 | include FactoryGirl::Syntax::Methods 59 | end 60 | 61 | class ActionDispatch::IntegrationTest 62 | include WebMock::API 63 | include FactoryGirl::Syntax::Methods 64 | 65 | def setup 66 | stub_request(:any, /.*/). 67 | to_return( 68 | :status => 200, 69 | :body => "{}" 70 | ) 71 | 72 | end 73 | 74 | end 75 | --------------------------------------------------------------------------------