├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Appraisals ├── CHANGELOG.md ├── Dangerfile ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── app └── views │ └── errors │ ├── internal_server_error.text.erb │ └── not_acceptable.text.erb ├── gemfiles ├── rails_42.gemfile ├── rails_50.gemfile ├── rails_51.gemfile ├── rails_52.gemfile ├── rails_60.gemfile ├── rails_61.gemfile ├── rails_70.gemfile ├── rails_71.gemfile ├── rails_72.gemfile ├── rails_80.gemfile └── rails_edge.gemfile ├── lib ├── generators │ └── rambulance │ │ ├── exceptions_app_generator.rb │ │ ├── install_generator.rb │ │ └── templates │ │ ├── exceptions_app.rb │ │ ├── rambulance.rb │ │ └── views │ │ ├── bad_request.html.erb │ │ ├── bad_request.json.jbuilder │ │ ├── forbidden.html.erb │ │ ├── forbidden.json.jbuilder │ │ ├── internal_server_error.html.erb │ │ ├── internal_server_error.json.jbuilder │ │ ├── not_acceptable.text.erb │ │ ├── not_found.html.erb │ │ ├── not_found.json.jbuilder │ │ ├── unprocessable_content.html.erb │ │ └── unprocessable_content.json.jbuilder ├── rambulance.rb └── rambulance │ ├── engine.rb │ ├── exceptions_app.rb │ ├── railtie.rb │ ├── test_helper.rb │ └── version.rb ├── rambulance.gemspec └── test ├── exceptions_app_test.rb ├── fake_app ├── app │ ├── helpers │ │ └── application_helper.rb │ └── views │ │ ├── errors │ │ ├── bad_request.html.erb │ │ ├── forbidden.html.erb │ │ ├── internal_server_error.html.erb │ │ ├── internal_server_error.json.jbuilder │ │ ├── not_found.html.erb │ │ └── not_found.json.jbuilder │ │ ├── layouts │ │ ├── application.html.erb │ │ └── error.html.erb │ │ └── projects │ │ └── index.html.erb ├── lib │ └── exceptions_app.rb └── rails_app.rb ├── requests ├── error_json_test.rb └── error_page_test.rb ├── test_helper.rb └── test_helper_test.rb /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | stable: 8 | strategy: 9 | matrix: 10 | ruby_version: 11 | - '3.3' 12 | - '3.2' 13 | - '3.1' 14 | - '3.0' 15 | - '2.7' 16 | - '2.6' 17 | - '2.5' 18 | - 'jruby-9.3' 19 | - 'jruby-9.4' 20 | gemfile: 21 | - gemfiles/rails_80.gemfile 22 | - gemfiles/rails_72.gemfile 23 | - gemfiles/rails_71.gemfile 24 | - gemfiles/rails_70.gemfile 25 | - gemfiles/rails_61.gemfile 26 | - gemfiles/rails_60.gemfile 27 | - gemfiles/rails_52.gemfile 28 | - gemfiles/rails_51.gemfile 29 | - gemfiles/rails_50.gemfile 30 | - gemfiles/rails_42.gemfile 31 | exclude: 32 | - ruby_version: '3.3' 33 | gemfile: gemfiles/rails_61.gemfile 34 | - ruby_version: '3.3' 35 | gemfile: gemfiles/rails_60.gemfile 36 | - ruby_version: '3.3' 37 | gemfile: gemfiles/rails_52.gemfile 38 | - ruby_version: '3.3' 39 | gemfile: gemfiles/rails_51.gemfile 40 | - ruby_version: '3.3' 41 | gemfile: gemfiles/rails_50.gemfile 42 | - ruby_version: '3.3' 43 | gemfile: gemfiles/rails_42.gemfile 44 | - ruby_version: '3.2' 45 | gemfile: gemfiles/rails_61.gemfile 46 | - ruby_version: '3.2' 47 | gemfile: gemfiles/rails_60.gemfile 48 | - ruby_version: '3.2' 49 | gemfile: gemfiles/rails_52.gemfile 50 | - ruby_version: '3.2' 51 | gemfile: gemfiles/rails_51.gemfile 52 | - ruby_version: '3.2' 53 | gemfile: gemfiles/rails_50.gemfile 54 | - ruby_version: '3.2' 55 | gemfile: gemfiles/rails_42.gemfile 56 | - ruby_version: '3.1' 57 | gemfile: gemfiles/rails_80.gemfile 58 | - ruby_version: '3.1' 59 | gemfile: gemfiles/rails_60.gemfile 60 | - ruby_version: '3.1' 61 | gemfile: gemfiles/rails_52.gemfile 62 | - ruby_version: '3.1' 63 | gemfile: gemfiles/rails_51.gemfile 64 | - ruby_version: '3.1' 65 | gemfile: gemfiles/rails_50.gemfile 66 | - ruby_version: '3.1' 67 | gemfile: gemfiles/rails_42.gemfile 68 | - ruby_version: '3.0' 69 | gemfile: gemfiles/rails_80.gemfile 70 | - ruby_version: '3.0' 71 | gemfile: gemfiles/rails_72.gemfile 72 | - ruby_version: '3.0' 73 | gemfile: gemfiles/rails_52.gemfile 74 | - ruby_version: '3.0' 75 | gemfile: gemfiles/rails_51.gemfile 76 | - ruby_version: '3.0' 77 | gemfile: gemfiles/rails_50.gemfile 78 | - ruby_version: '3.0' 79 | gemfile: gemfiles/rails_42.gemfile 80 | - ruby_version: '2.7' 81 | gemfile: gemfiles/rails_80.gemfile 82 | - ruby_version: '2.7' 83 | gemfile: gemfiles/rails_72.gemfile 84 | - ruby_version: '2.7' 85 | gemfile: gemfiles/rails_71.gemfile 86 | - ruby_version: '2.7' 87 | gemfile: gemfiles/rails_70.gemfile 88 | - ruby_version: '2.7' 89 | gemfile: gemfiles/rails_52.gemfile 90 | - ruby_version: '2.7' 91 | gemfile: gemfiles/rails_51.gemfile 92 | - ruby_version: '2.7' 93 | gemfile: gemfiles/rails_50.gemfile 94 | - ruby_version: '2.7' 95 | gemfile: gemfiles/rails_42.gemfile 96 | - ruby_version: '2.6' 97 | gemfile: gemfiles/rails_80.gemfile 98 | - ruby_version: '2.6' 99 | gemfile: gemfiles/rails_72.gemfile 100 | - ruby_version: '2.6' 101 | gemfile: gemfiles/rails_71.gemfile 102 | - ruby_version: '2.6' 103 | gemfile: gemfiles/rails_70.gemfile 104 | - ruby_version: '2.6' 105 | gemfile: gemfiles/rails_42.gemfile 106 | - ruby_version: '2.5' 107 | gemfile: gemfiles/rails_80.gemfile 108 | - ruby_version: '2.5' 109 | gemfile: gemfiles/rails_72.gemfile 110 | - ruby_version: '2.5' 111 | gemfile: gemfiles/rails_71.gemfile 112 | - ruby_version: '2.5' 113 | gemfile: gemfiles/rails_70.gemfile 114 | - ruby_version: 'jruby-9.3' 115 | gemfile: gemfiles/rails_80.gemfile 116 | - ruby_version: 'jruby-9.3' 117 | gemfile: gemfiles/rails_72.gemfile 118 | - ruby_version: 'jruby-9.3' 119 | gemfile: gemfiles/rails_71.gemfile 120 | - ruby_version: 'jruby-9.3' 121 | gemfile: gemfiles/rails_70.gemfile 122 | - ruby_version: 'jruby-9.3' 123 | gemfile: gemfiles/rails_51.gemfile 124 | - ruby_version: 'jruby-9.3' 125 | gemfile: gemfiles/rails_50.gemfile 126 | - ruby_version: 'jruby-9.3' 127 | gemfile: gemfiles/rails_42.gemfile 128 | - ruby_version: 'jruby-9.4' 129 | gemfile: gemfiles/rails_80.gemfile 130 | - ruby_version: 'jruby-9.4' 131 | gemfile: gemfiles/rails_60.gemfile 132 | - ruby_version: 'jruby-9.4' 133 | gemfile: gemfiles/rails_52.gemfile 134 | - ruby_version: 'jruby-9.4' 135 | gemfile: gemfiles/rails_51.gemfile 136 | - ruby_version: 'jruby-9.4' 137 | gemfile: gemfiles/rails_50.gemfile 138 | - ruby_version: 'jruby-9.4' 139 | gemfile: gemfiles/rails_42.gemfile 140 | runs-on: ubuntu-22.04 141 | env: 142 | BUNDLE_GEMFILE: ${{ matrix.gemfile }} 143 | steps: 144 | - uses: actions/checkout@v4 145 | - name: Set up Ruby 146 | uses: ruby/setup-ruby@v1 147 | with: 148 | ruby-version: ${{ matrix.ruby_version }} 149 | bundler-cache: true 150 | - run: bundle exec rake 151 | 152 | rails_edge: 153 | needs: 154 | - stable 155 | runs-on: ubuntu-22.04 156 | env: 157 | BUNDLE_GEMFILE: gemfiles/rails_edge.gemfile 158 | steps: 159 | - uses: actions/checkout@v4 160 | - name: Set up Ruby 161 | uses: ruby/setup-ruby@v1 162 | with: 163 | ruby-version: 3.3 164 | bundler-cache: true 165 | - run: bundle exec rake 166 | 167 | ruby_edge: 168 | needs: 169 | - stable 170 | strategy: 171 | matrix: 172 | ruby_version: 173 | - 'ruby-head' 174 | # - 'jruby-head' 175 | gemfile: 176 | - gemfiles/rails_edge.gemfile 177 | - gemfiles/rails_80.gemfile 178 | runs-on: ubuntu-22.04 179 | env: 180 | BUNDLE_GEMFILE: ${{ matrix.gemfile }} 181 | steps: 182 | - uses: actions/checkout@v4 183 | - name: Set up Ruby 184 | uses: ruby/setup-ruby@v1 185 | with: 186 | ruby-version: ${{ matrix.ruby_version }} 187 | bundler-cache: true 188 | - run: bundle exec rake || echo "Ruby edge test is done." 189 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | gemfiles/*.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | *.bundle 20 | *.so 21 | *.o 22 | *.a 23 | mkmf.log 24 | *.log 25 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rails_42" do 2 | gem "activesupport", "~> 4.2.0" 3 | gem "actionpack", "~> 4.2.0" 4 | gem "railties", "~> 4.2.0" 5 | gem 'minitest', '~> 5.3.4' 6 | gem 'nokogiri', '1.6.8.1' if RUBY_VERSION == "2.0.0" 7 | end 8 | 9 | appraise "rails_50" do 10 | gem "activesupport", "~> 5.0.0" 11 | gem "actionpack", "~> 5.0.0" 12 | gem "railties", "~> 5.0.0" 13 | gem 'minitest', '~> 5.3.4' 14 | end 15 | 16 | appraise "rails_51" do 17 | gem "activesupport", "~> 5.1.0" 18 | gem "actionpack", "~> 5.1.0" 19 | gem "railties", "~> 5.1.0" 20 | end 21 | 22 | appraise "rails_52" do 23 | gem "activesupport", "~> 5.2.0" 24 | gem "actionpack", "~> 5.2.0" 25 | gem "railties", "~> 5.2.0" 26 | end 27 | 28 | appraise "rails_60" do 29 | gem "activesupport", "~> 6.0.0" 30 | gem "actionpack", "~> 6.0.0" 31 | gem "railties", "~> 6.0.0" 32 | end 33 | 34 | appraise "rails_61" do 35 | gem "activesupport", "~> 6.1.0" 36 | gem "actionpack", "~> 6.1.0" 37 | gem "railties", "~> 6.1.0" 38 | end 39 | 40 | appraise "rails_70" do 41 | gem "activesupport", "~> 7.0.0" 42 | gem "actionpack", "~> 7.0.0" 43 | gem "railties", "~> 7.0.0" 44 | end 45 | 46 | appraise "rails_71" do 47 | gem "activesupport", "~> 7.1.0" 48 | gem "actionpack", "~> 7.1.0" 49 | gem "railties", "~> 7.1.0" 50 | end 51 | 52 | appraise "rails_72" do 53 | gem "activesupport", "~> 7.2.0" 54 | gem "actionpack", "~> 7.2.0" 55 | gem "railties", "~> 7.2.0" 56 | end 57 | 58 | appraise "rails_80" do 59 | gem "activesupport", "~> 8.0.0" 60 | gem "actionpack", "~> 8.0.0" 61 | gem "railties", "~> 8.0.0" 62 | end 63 | 64 | appraise "rails_edge" do 65 | git 'https://github.com/rails/rails.git' do 66 | gem "activesupport", require: 'active_support' 67 | gem "actionpack", require: 'action_pack' 68 | gem "railties" 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | #### 🚨 Breaking Changes 4 | 5 | - No changes. 6 | 7 | #### ⭐️ New Features 8 | 9 | - Add support for Rails 8.0 ([#81](https://github.com/yuki24/rambulance/pull/81)) 10 | 11 | #### 🐞 Bug Fixes 12 | 13 | - No changes. 14 | 15 | ## [v3.3.0](https://github.com/yuki24/rambulance/tree/v3.3.0) 16 | 17 | _released at 2024-08-16 05:32:59 UTC_ 18 | 19 | #### New Features 20 | 21 | - Add support for Rails 7.1 ([e6d42b5](https://github.com/yuki24/rambulance/commit/e6d42b514d0bdbc2ea5b1603f85ecc9fa24b819a)) 22 | 23 | ## [v3.2.0](https://github.com/yuki24/rambulance/tree/v3.2.0) 24 | 25 | _released at 2024-08-04 03:04:26 UTC_ 26 | 27 | This release includes a **breaking change**. Please read the details below carefully. 28 | 29 | #### New Features 30 | 31 | - Fixed compatibility with Rack (see [rack/rack#2137](https://github.com/rack/rack/pull/2137)) ([#77](https://github.com/yuki24/rambulance/issues/77), [@tmaier](https://github.com/tmaier), [#78](https://github.com/yuki24/rambulance/pull/78)) 32 | 33 | #### Breaking Changes 34 | 35 | The Rack compatibility fix is related to Rack becoming more compliant with the IANA HTTP Status Code Registry. 36 | As a result: 37 | 38 | - `unprocessable_entity` has been renamed to `unprocessable_content`. 39 | - Rambulance will redirect any request from `unprocessable_entity` to `unprocessable_content` for backward compatibility. 40 | - Users **must** rename the view file from `app/views/errors/unprocessable_entity.html.erb` to `app/views/errors/unprocessable_content.html.erb`. 41 | - Users **should** update the hash values in their configurations: Change `unprocessable_entity` to `unprocessable_content` in `config.rescue_responses`/`config.action_dispatch.rescue_responses` 42 | 43 | ## [v3.1.0](https://github.com/yuki24/rambulance/tree/v3.1.0) 44 | 45 | _released at 2024-01-04 10:08:11 UTC_ 46 | 47 | #### New Features 48 | 49 | - The test helper is now compatible with Rails Edge (8.0) ([#73](https://github.com/yuki24/rambulance/pull/73), [#75](https://github.com/yuki24/rambulance/pull/75)) 50 | - Add support for Ruby 3.3 ([#74](https://github.com/yuki24/rambulance/pull/74)) 51 | 52 | ## [v3.0.0](https://github.com/yuki24/rambulance/tree/v3.0.0) 53 | 54 | _released at 2023-09-22 14:47:24 UTC_ 55 | 56 | #### New Features 57 | 58 | - Add support for Rails 7.1 ([be6f129](https://github.com/yuki24/rambulance/commit/be6f129dd4c1d190e90a3036f7f1099028ef4a1e)) 59 | 60 | #### Breaking Changes 61 | 62 | - Drop support for Ruby 2.3 and 2.4 ([c0d3439](https://github.com/yuki24/rambulance/commit/c0d3439d64552a88cb934d57bc57d2c06ff593c9)) 63 | 64 | ## [v2.3.0](https://github.com/yuki24/rambulance/tree/v2.3.0) 65 | 66 | _released at 2023-01-05 05:33:36 UTC_ 67 | 68 | #### New Features 69 | 70 | - Ruby 3.2 is now officially supported. 71 | 72 | ## [v2.2.0](https://github.com/yuki24/rambulance/tree/v2.2.0) 73 | 74 | _released at 2022-02-13 07:56:32 UTC_ 75 | 76 | #### Features 77 | 78 | - Add support for Ruby 3.1, Rails 7.0 and JRuby 9.3.3.0 79 | 80 | ## [v2.0.0](https://github.com/yuki24/rambulance/tree/v2.0.0) 81 | 82 | _released at 2021-09-22 02:49:18 UTC_ 83 | 84 | #### Breaking Changes 85 | 86 | - Drop support for Rails 4.1 and older ([#62](https://github.com/yuki24/rambulance/pull/62), [@yuki24](https://github.com/yuki24)) 87 | 88 | #### Features 89 | 90 | - Add support for Rails 6.1 and Ruby 3.0 ([#61](https://github.com/yuki24/rambulance/pull/61), [#63](https://github.com/yuki24/rambulance/pull/63), [@gingerlime](https://github.com/gingerlime), [@yuki24](https://github.com/yuki24)) 91 | 92 | #### Bug fixes 93 | 94 | - POST with invalid JSON triggers a 500 ([#59](https://github.com/yuki24/rambulance/pull/59), [@gingerlime](https://github.com/gingerlime)) 95 | - Fix 'directry' typo ([#57](https://github.com/yuki24/rambulance/pull/57), [@andyw8](https://github.com/andyw8)) 96 | 97 | ## [v1.1.1](https://github.com/yuki24/rambulance/tree/v1.1.1) 98 | 99 | _released at 2020-05-28 23:22:10 UTC_ 100 | 101 | #### Bug fixes 102 | 103 | - Fixes a bug where a malformed `Content-Type` header could break the exceptions app ([#55](https://github.com/yuki24/rambulance/pull/55), [@gingerlime](https://github.com/gingerlime)) 104 | 105 | ## [v1.1.0](https://github.com/yuki24/rambulance/tree/v1.1.0) 106 | 107 | _released at 2020-05-23 02:52:27 UTC_ 108 | 109 | #### Features 110 | 111 | - Add support for Ruby 2.7 ([718531c](https://github.com/yuki24/rambulance/commit/718531c45b61d01dce91f401fd81dd6aefdefb31)) 112 | 113 | #### Bug fixes 114 | 115 | - Fixes a bug where malformed MIME type in HTTP headers could break the exceptions app ([#53](https://github.com/yuki24/rambulance/issues/53), [#54](https://github.com/yuki24/rambulance/pull/54), [@gingerlime](https://github.com/gingerlime)) 116 | 117 | ## [v1.0.3](https://github.com/yuki24/rambulance/tree/v1.0.3) 118 | 119 | _released at 2019-10-03 03:39:17 UTC_ 120 | 121 | #### Fixes 122 | 123 | - Fixes a bug where custom exceptions app can not be loaded properly on Rails 6 and Zeitwerk ([#52](https://github.com/yuki24/rambulance/issues/52), [@sho918](https://github.com/sho918)) 124 | 125 | ## [v1.0.2](https://github.com/yuki24/rambulance/tree/v1.0.2) 126 | 127 | _released at 2019-06-06 23:39:38 UTC_ 128 | 129 | #### Fixes 130 | 131 | - Fixes a bug where `ExceptionsApp` is not loaded when action controller is not loaded ([ec9d9e5](https://github.com/yuki24/rambulance/commit/ec9d9e5de98eeec501042a0bbb95ae8ac8d7b4e3)) 132 | 133 | ## [v1.0.1](https://github.com/yuki24/rambulance/tree/v1.0.1) 134 | 135 | _released at 2019-06-04 16:30:38 UTC_ 136 | 137 | #### Fixes 138 | 139 | - Fixes an issue where `ExceptionsApp` prevents Rails from booting up ([#49](https://github.com/yuki24/rambulance/issues/49)) 140 | 141 | ## [v1.0.0](https://github.com/yuki24/rambulance/tree/v1.0.0) 142 | 143 | _released at 2019-06-03 05:01:14 UTC_ 144 | 145 | #### Features 146 | 147 | - Add support for Ruby 2.6 148 | - Add support for Rails 6.0 149 | 150 | #### Bug fixes 151 | 152 | - Fixes a bug where POST requests cause the exceptions app to throw an `ActionController::InvalidAuthenticityToken` error on Rails 5.2 ([d68d677](https://github.com/yuki24/rambulance/commit/d68d677de75059fa09f70e37c97f4bae95885c53), [#48](https://github.com/yuki24/rambulance/issues/48), [@adrianhuna](https://github.com/adrianhuna)) 153 | 154 | #### Breaking Changes 155 | 156 | - Drop support for Ruby \<= 2.2 157 | - Drop support for Rails \<= 4.1 158 | 159 | ## [v0.6.0](https://github.com/yuki24/rambulance/tree/v0.6.0) 160 | 161 | _released at 2018-03-08 18:36:16 UTC_ 162 | 163 | #### New features 164 | 165 | - Add support for Rails 5.2 166 | - Add a test helper method `#with_exceptions_app ` for easier testing ([f5c16b9](https://github.com/yuki24/rambulance/commit/f5c16b90ecf5eb4903faa30d760bf5863441e9c5), [#27](https://github.com/yuki24/rambulance/pull/27), [@kbaba1001](https://github.com/kbaba1001)) 167 | 168 | #### Bug fixes 169 | 170 | - Fixed a bug where an `ActionController::UnknownFormat` error could cause a number of issues ([1b824e6](https://github.com/yuki24/rambulance/commit/1b824e6c170479ed90e24df1680dd2dec7c98160), [#41](https://github.com/yuki24/rambulance/issues/41), [#42](https://github.com/yuki24/rambulance/pull/42), [@willnet](https://github.com/willnet), [@joker-777](https://github.com/joker-777)) 171 | - Fixed a bug where the methods on the `ExceptionApp` weren't easily inspectable ([6b4c834](https://github.com/yuki24/rambulance/commit/6b4c834bb0b8e81f619d9f598310ce68f4f9c66b)) 172 | 173 | ## [v0.5.0](https://github.com/yuki24/rambulance/tree/v0.5.0) 174 | 175 | _released at 2018-01-02 21:36:02 UTC_ 176 | 177 | #### New features 178 | 179 | - Add support for Ruby 2.5.0 180 | - Add support for Rails 5.1 181 | 182 | #### Breaking changes & deprecations 183 | 184 | - Drop support for Ruby 1.9.3 185 | - Drop support for Haml and Slim templates 186 | 187 | #### Bug fixes 188 | 189 | - Fixes a bug where the exceptions app fails to show an error page properly when a malformed body is posted ([474b6b3](https://github.com/yuki24/rambulance/commit/474b6b329e5590db3c0a7e33c795b18c00812729), [#40](https://github.com/yuki24/rambulance/issues/40), [@jasim](https://github.com/jasim)) 190 | 191 | ## [v0.4.0](https://github.com/yuki24/rambulance/tree/v0.4.0) 192 | 193 | _released at 2016-10-12 02:25:55 UTC_ 194 | 195 | - Added support for Rails 5 and edge 196 | - Added the ability to use helper methods in error templates([@willnet](https://github.com/willnet), [#37](https://github.com/yuki24/rambulance/pull/37)) 197 | 198 | ## [v0.3.1](https://github.com/yuki24/rambulance/tree/v0.3.1) 199 | 200 | _released at 2015-05-18 01:53:59 UTC_ 201 | 202 | - Added slim support ([@liubin](https://github.com/liubin), [#20](https://github.com/yuki24/rambulance/pull/20)) 203 | - `rails g rambulance:install` no longer generates jbuilder templates if it's undefined 204 | - The exceptions app no longer attempts to render layouts when the request format is json 205 | - Now `rails g rambulance:install` always copies `application.html.erb` to `error.html.erb` again 206 | - **incompatible change** : `error_layout` option for the install command has been removed in favor of the change above 207 | 208 | ## [v0.3.0](https://github.com/yuki24/rambulance/tree/v0.3.0) 209 | 210 | _released at 2015-01-23 13:42:35 UTC_ 211 | 212 | - Added 2 helper methods `#exception` and `#status_in_words` that you can use in views 213 | 214 | ## [v0.2.0](https://github.com/yuki24/rambulance/tree/v0.2.0) 215 | 216 | _released at 2014-09-18 03:38:18 UTC_ 217 | 218 | - Add support for Rails 4.2 beta 219 | - Do not copy application.html.erb when `rails g rambulance:install` [[@kenn](https://github.com/kenn), [#5](https://github.com/yuki24/rambulance/issues/5)] 220 | - Capture `ActionController::BadRequest` before processing action 221 | - Dynamically generate exception/status mapping in initializer 222 | - Add option to copy `application.html.erb` for error page layout 223 | 224 | ## [v0.1.2](https://github.com/yuki24/rambulance/tree/v0.1.2) 225 | 226 | _released at 2014-09-05 12:22:37 UTC_ 227 | 228 | - Fixed a bug where changing config.layout\_name doesn't actually switch layout [[@kenn](https://github.com/kenn), [#4](https://github.com/yuki24/rambulance/issues/4)] 229 | - Exceptions app now refers to `app/views/#{config.view_path}` by default 230 | 231 | ## [v0.1.1](https://github.com/yuki24/rambulance/tree/v0.1.1) 232 | 233 | _released at 2014-06-22 02:39:06 UTC_ 234 | 235 | - Fixed a bug where installer command doesn't work 236 | 237 | ## [v0.1.0](https://github.com/yuki24/rambulance/tree/v0.1.0) 238 | 239 | _released at 2014-06-20 01:00:02 UTC_ 240 | 241 | - First release of rambulance! 242 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Sometimes it's a README fix, or something like that - which isn't relevant for 2 | # including in a project's CHANGELOG for example 3 | declared_trivial = github.pr_title.include? "#trivial" 4 | 5 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet 6 | warn("PR is classed as Work in Progress") if github.pr_title.include? "WIP" 7 | 8 | # Warn when there is a big PR 9 | warn("Big PR") if git.lines_of_code > 500 10 | 11 | # Don't let testing shortcuts get into master by accident 12 | fail("fdescribe left in tests") if `grep -r fdescribe test/ `.length > 1 13 | fail("fit left in tests") if `grep -r fit test/ `.length > 1 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rambulance.gemspec 4 | gemspec 5 | gem 'minitest', '< 5.25.0' 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Yuki Nishijima 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rambulance [](https://github.com/yuki24/rambulance/actions/workflows/tests.yml) [](https://rubygems.org/gems/rambulance) 2 | 3 | A simple and safe way to dynamically render error pages for Rails apps. 4 | 5 | ## Features 6 | 7 | ### Simple and Safe 8 | 9 | Rambulance's exceptions app is simple, skinny and well-tested. It inherits from `ActionController::Base`, so it works fine even if your `ApplicationController` has an issue. 10 | 11 | ### Flexible 12 | 13 | You have full control of which error page to show for a specific exception. It also json rendering (perfect for API apps). It even provides a way to create a custom exceptions app. 14 | 15 | ### Easy installation and development 16 | 17 | You don't have to configure things that every single person has to do and Rambulance does everything for you. 18 | 19 | ## Installation and Usage 20 | 21 | Add this line to your application's Gemfile: 22 | 23 | ``` 24 | gem 'rambulance' 25 | ``` 26 | 27 | And then execute: 28 | 29 | ``` 30 | $ rails g rambulance:install 31 | ``` 32 | 33 | Rambulance's generator can only generate `erb` templates. If you want to use haml or slim templates, please see [How to Convert Your `.erb` to `.slim`](https://github.com/slim-template/slim/wiki/Template-Converters-ERB-to-SLIM) or [html2haml](https://github.com/haml/html2haml). 34 | 35 | Now you can start editing templates like `app/views/errors/not_found.html.erb`. Edit, run `rails server` and open [`localhost:3000/rambulance/not_found`](http://localhost:3000/rambulance/not_found)! 36 | 37 | ## Setting Pairs of Exceptions and HTTP Statuses 38 | 39 | Open `config/initializers/rambulance.rb` and to configure the list of pairs of exception/corresponding http status. 40 | For example, if you want to display: 41 | 42 | * 422(unprocessable entity) for `ActiveRecord::RecordNotUnique` 43 | * 403(forbidden) for `CanCan::AccessDenied` 44 | * 404(not found) for `YourCustomException` 45 | 46 | Then do the following: 47 | 48 | ```ruby 49 | # config/initializers/rambulance.rb 50 | config.rescue_responses = { 51 | "ActiveRecord::RecordNotUnique" => :unprocessable_content, 52 | "CanCan::AccessDenied" => :forbidden, 53 | "YourCustomException" => :not_found 54 | } 55 | ``` 56 | 57 | ## Special case `unprocessable_entity`/`unprocessable_content` (HTTP Status 422) 58 | 59 | Rack renamed `unprocessable_entity` to `unprocessable_content`. 60 | 61 | Rambulance supports both, but defaults to `unprocessable_content` with version 3.2.0 and later. 62 | `unprocessable_entity` is supported with a redirect to `unprocessable_content`. 63 | 64 | This means the view file is called `app/views/errors/unprocessable_content.html.erb`, not `app/views/errors/unprocessable_entity.html.erb`. 65 | 66 | ## Local Development 67 | 68 | ### Open `localhost:3000/rambulance/***` in Your Browser 69 | 70 | Just open one of the error pages via Rambulance: 71 | 72 | * [`localhost:3000/rambulance/not_found`](http://localhost:3000/rambulance/not_found) or 73 | * [`localhost:3000/rambulance/internal_server_error`](http://localhost:3000/rambulance/internal_server_error) 74 | 75 | This is useful when you want to edit templates without changing Rails configuration. 76 | 77 | ### Set `consider_all_requests_local` to _false_ 78 | 79 | Change `config.consider_all_requests_local` to _false_ in `config/environments/development.rb`. 80 | 81 | ```ruby 82 | config.consider_all_requests_local = false 83 | ``` 84 | 85 | This simulates how your production app displays error pages so you can actually raise an exception in your app and see how it works. Don't forget to change `consider_all_requests_local` back to _true_ after you tried this strategy. 86 | 87 | ## Custom Exceptions App 88 | 89 | If you want to do some more things in a exceptions app, you can also write your own custom exceptions app: 90 | 91 | ```sh 92 | $ rails g rambulance:exceptions_app 93 | ``` 94 | 95 | It will generate your own custom exceptions app. You can use most techniques you want to use in controllers like `before_filter` and rendering views since it's a grandchild of `ActionController::Base`! However there are still some restrictions, e.g. setting a flash notice works when rendering directly but not when redirecting because the ActionDispatch::Flash middleware is never hit. 96 | 97 | **Heavily customizing the exceptions app is strongly discouraged as there would be no guard against bugs that occur in the exceptions app.** 98 | 99 | ## Testing 100 | 101 | Rambulance ships with a test helper that allows you to test an error page generated by Rails. All you have to do is to `include Rambulance::TestHelper` and you will be able to use the `with_exceptions_app` DSDL: 102 | 103 | Rspec: 104 | 105 | ```ruby 106 | include Rambulance::TestHelper 107 | 108 | it "shows an error page" do 109 | with_exceptions_app do 110 | get '/does_not_exist' 111 | end 112 | 113 | assert_equal 404, response.status 114 | end 115 | ``` 116 | 117 | Minitest: 118 | 119 | ```ruby 120 | include Rambulance::TestHelper 121 | 122 | test "it shows an error page" do 123 | with_exceptions_app do 124 | get '/does_not_exist' 125 | end 126 | 127 | assert_equal 404, response.status 128 | end 129 | ``` 130 | 131 | Note that testing error pages is not encouraged in Rails as it leads to overuse of the `rescue_from` DSL in controllers. 132 | 133 | ## Supported Versions 134 | 135 | * Ruby 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3 and JRuby 9.3, 9.4 136 | * Rails 4.2, 5.0, 5.1, 5.2, 6.0, 6.1, 7.0, 7.1 and edge 137 | 138 | Rambulance doesn't work with Rails 3.1 and below since they don't provide a way to use a custom exceptions app. 139 | 140 | ## Contributing 141 | 142 | 1. Fork it ( https://github.com/yuki24/rambulance/fork ) 143 | 2. Create your feature branch (`git checkout -b my-new-feature`) 144 | 3. Commit your changes (`git commit -am 'Add some feature'`) 145 | 4. Push to the branch (`git push origin my-new-feature`) 146 | 5. Create a new Pull Request 147 | 148 | ## License 149 | 150 | Copyright (c) 2014-2015 Yuki Nishijima. See LICENSE.txt for further details. 151 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new('test:default') do |task| 5 | task.libs << "test" 6 | 7 | task.test_files = Dir['test/**/*_test.rb'] 8 | task.verbose = true 9 | task.warning = true 10 | end 11 | 12 | namespace :test do 13 | desc "Run Tests with a custom exceptions app" 14 | task :custom do 15 | sh "rake test:default CUSTOM_EXCEPTIONS_APP=1" 16 | end 17 | end 18 | 19 | task(:test).enhance %w(test:default test:custom) 20 | task default: :test 21 | -------------------------------------------------------------------------------- /app/views/errors/internal_server_error.text.erb: -------------------------------------------------------------------------------- 1 | Something went wrong. 2 | -------------------------------------------------------------------------------- /app/views/errors/not_acceptable.text.erb: -------------------------------------------------------------------------------- 1 | The requested content type is not acceptable. 2 | -------------------------------------------------------------------------------- /gemfiles/rails_42.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "~> 5.3.4" 6 | gem "activesupport", "~> 4.2.0" 7 | gem "actionpack", "~> 4.2.0" 8 | gem "railties", "~> 4.2.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_50.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "~> 5.3.4" 6 | gem "activesupport", "~> 5.0.0" 7 | gem "actionpack", "~> 5.0.0" 8 | gem "railties", "~> 5.0.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_51.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 5.1.0" 7 | gem "actionpack", "~> 5.1.0" 8 | gem "railties", "~> 5.1.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_52.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 5.2.0" 7 | gem "actionpack", "~> 5.2.0" 8 | gem "railties", "~> 5.2.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_60.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 6.0.0" 7 | gem "actionpack", "~> 6.0.0" 8 | gem "railties", "~> 6.0.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_61.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 6.1.0" 7 | gem "actionpack", "~> 6.1.0" 8 | gem "railties", "~> 6.1.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_70.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 7.0.0" 7 | gem "actionpack", "~> 7.0.0" 8 | gem "railties", "~> 7.0.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_71.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 7.1.0" 7 | gem "actionpack", "~> 7.1.0" 8 | gem "railties", "~> 7.1.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_72.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 7.2.0" 7 | gem "actionpack", "~> 7.2.0" 8 | gem "railties", "~> 7.2.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_80.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "minitest", "< 5.25.0" 6 | gem "activesupport", "~> 8.0.0" 7 | gem "actionpack", "~> 8.0.0" 8 | gem "railties", "~> 8.0.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_edge.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | git "https://github.com/rails/rails.git" do 6 | gem "activesupport", require: "active_support" 7 | gem "actionpack", require: "action_pack" 8 | gem "railties" 9 | end 10 | 11 | gem "minitest", "< 5.25.0" 12 | 13 | gemspec path: "../" 14 | -------------------------------------------------------------------------------- /lib/generators/rambulance/exceptions_app_generator.rb: -------------------------------------------------------------------------------- 1 | module Rambulance 2 | module Generators 3 | class ExceptionsAppGenerator < Rails::Generators::Base 4 | source_root File.expand_path('../templates', __FILE__) 5 | 6 | def self.banner #:nodoc: 7 | <<-BANNER.chomp 8 | rails g rambulance:exceptions_app 9 | 10 | Creates a custom exceptions app. 11 | BANNER 12 | end 13 | 14 | desc '' 15 | def copy_exceptions_app #:nodoc: 16 | copy_file "exceptions_app.rb", "app/handlers/exceptions_app.rb" 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/generators/rambulance/install_generator.rb: -------------------------------------------------------------------------------- 1 | module Rambulance 2 | module Generators 3 | class InstallGenerator < Rails::Generators::Base 4 | source_root File.expand_path('../templates', __FILE__) 5 | 6 | class_option :template_engine, 7 | type: :string, 8 | aliases: '-e', 9 | desc: 'Template engine for the views. Available options are "erb", "slim" and "haml".' 10 | 11 | def self.banner #:nodoc: 12 | <<-BANNER.chomp 13 | rails g rambulance:install 14 | 15 | Copies all error partial templates and an initializer to your application. 16 | BANNER 17 | end 18 | 19 | desc '' 20 | def copy_templates #:nodoc: 21 | say "generating templates:" 22 | filename_pattern = File.join(self.class.source_root, "views", "*.html.#{template_engine}") 23 | Dir.glob(filename_pattern).map {|f| File.basename f }.each do |f| 24 | copy_file "views/#{f}", "app/views/errors/#{f}" 25 | end 26 | 27 | if defined?(Jbuilder) 28 | filename_pattern = File.join(self.class.source_root, "views", "*.json.jbuilder") 29 | Dir.glob(filename_pattern).map {|f| File.basename f }.each do |f| 30 | copy_file "views/#{f}", "app/views/errors/#{f}" 31 | end 32 | end 33 | end 34 | 35 | def copy_layout #:nodoc: 36 | say "\ncopying app/views/layouts/application.html.#{template_engine} to app/views/layouts/error.html.#{template_engine}:" 37 | copy_file Rails.root.join("app/views/layouts/application.html.#{template_engine}"), "app/views/layouts/error.html.#{template_engine}" 38 | end 39 | 40 | def copy_initializer #:nodoc: 41 | say "\n" "generating initializer:" 42 | template "rambulance.rb", "config/initializers/rambulance.rb" 43 | end 44 | 45 | private 46 | 47 | def template_engine 48 | options[:template_engine].try(:to_s).try(:downcase) || 'erb' 49 | end 50 | 51 | def longest_error_name_size 52 | ActionDispatch::ExceptionWrapper.rescue_responses.keys.sort_by(&:size).last.size 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/generators/rambulance/templates/exceptions_app.rb: -------------------------------------------------------------------------------- 1 | class ExceptionsApp < Rambulance::ExceptionsApp 2 | def bad_request 3 | end 4 | 5 | def forbidden 6 | end 7 | 8 | def internal_server_error 9 | end 10 | 11 | def not_found 12 | end 13 | 14 | def unprocessable_content 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/generators/rambulance/templates/rambulance.rb: -------------------------------------------------------------------------------- 1 | Rambulance.setup do |config| 2 | 3 | # List of pairs of exception/corresponding http status. In Rails, the default 4 | # mappings are below: 5 | # 6 | <%= 7 | ActionDispatch::ExceptionWrapper.rescue_responses.map do |error_class, status| 8 | " # #{error_class.ljust(longest_error_name_size)} => :#{status}" 9 | end.join(",\n") 10 | %> 11 | # 12 | # If you add exceptions in this config, Rambulance uses the pairs you defined 13 | # here *in addition* to the default mappings. You can also override the default 14 | # mappings although you don't have to in most cases. 15 | # If Rambulance receives an exception that is not listed here, it'll render 16 | # the internal server error template and return 500 as http status. 17 | config.rescue_responses = { 18 | # "ActiveRecord::RecordNotUnique" => :unprocessable_content, 19 | # "CanCan::AccessDenied" => :forbidden, 20 | # "Pundit::NotAuthorizedError" => :forbidden, 21 | # "YourCustomException" => :not_found 22 | } 23 | 24 | # The template name for the layout of the error pages. The default value is 25 | # 'error'. For example, if this value is set to "error_page", Rambulance uses 26 | # 'app/views/layout/error_page.html.erb' as a layout for all the error pages. 27 | config.layout_name = "error" 28 | 29 | # The directory name to organize error page templates. The default value is 30 | # 'errors'. For example, if this value is set to "error_pages", Rambulance 31 | # uses e.g. 'app/views/error_pages/not_found.html.erb'. 32 | config.view_path = "errors" 33 | 34 | end 35 | -------------------------------------------------------------------------------- /lib/generators/rambulance/templates/views/bad_request.html.erb: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 |
The service received a incomplete request.
58 |You are not authorized to access this page.
58 |If you are the application owner check the logs for more information.
58 |You may have mistyped the address or the page may have moved.
57 |If you are the application owner check the logs for more information.
59 |Maybe you tried to change something you didn't have access to.
57 |If you are the application owner check the logs for more information.
59 |Something went wrong!
19 | 20 | 21 | BODY 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/fake_app/rails_app.rb: -------------------------------------------------------------------------------- 1 | require 'jbuilder' 2 | 3 | # config 4 | class TestApp < Rails::Application 5 | config.secret_token = '964ab2f0fbbb68bc36f3cc487ca296bb8555fac50627924024c245a1599e5265' 6 | config.session_store :cookie_store, :key => '_myapp_session' 7 | config.active_support.cache_format_version = 7.1 if Rails::VERSION::STRING >= "7.1" 8 | config.active_support.deprecation = :log 9 | config.eager_load = false 10 | config.root = File.dirname(__FILE__) 11 | config.autoload_paths += ["#{config.root}/lib"] if ENV["CUSTOM_EXCEPTIONS_APP"] 12 | config.hosts = "www.example.com" 13 | 14 | if Rails::VERSION::STRING >= "5.2" 15 | config.action_controller.default_protect_from_forgery = true 16 | end 17 | end 18 | Rails.backtrace_cleaner.remove_silencers! 19 | Rails.application.initialize! 20 | 21 | # routes 22 | Rails.application.routes.draw do 23 | resources :users 24 | resources :projects, only: [:index, :create] 25 | end 26 | 27 | # custom exception class 28 | class CustomException < StandardError; end 29 | class ForbiddenException < StandardError; end 30 | 31 | Rambulance.setup do |config| 32 | config.layout_name = "error" 33 | config.rescue_responses = { 34 | 'TypeError' => :bad_request, 35 | 'CustomException' => :not_found, 36 | 'ForbiddenException' => :forbidden 37 | } 38 | end 39 | 40 | # controllers 41 | class ApplicationController < ActionController::Base 42 | append_view_path "spec/fake_app/views" 43 | if self.respond_to? :before_filter 44 | before_filter :bad_filter 45 | else 46 | before_action :bad_filter 47 | end 48 | 49 | private 50 | 51 | def bad_filter 52 | raise "This is a bad filter." 53 | end 54 | end 55 | 56 | class UsersController < ApplicationController 57 | if self.respond_to? :skip_before_action 58 | skip_before_action :bad_filter, except: :show 59 | else 60 | skip_filter :bad_filter, except: :show 61 | end 62 | 63 | def index 64 | raise CustomException 65 | end 66 | 67 | def show; end 68 | 69 | def new 70 | raise ActionController::InvalidAuthenticityToken 71 | end 72 | 73 | def create 74 | render text: 'created.', status: 201 75 | end 76 | 77 | def update 78 | render text: 'updated.', status: 201 79 | end 80 | 81 | def edit 82 | raise ForbiddenException 83 | end 84 | end 85 | 86 | class ProjectsController < ApplicationController 87 | if self.respond_to?(:skip_forgery_protection) 88 | skip_forgery_protection 89 | end 90 | 91 | if self.respond_to? :skip_before_action 92 | skip_before_action :bad_filter, except: :create 93 | else 94 | skip_filter :bad_filter, except: :create 95 | end 96 | 97 | def index; end 98 | def create; end 99 | end 100 | -------------------------------------------------------------------------------- /test/requests/error_json_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ErrorJsonTest < ActionDispatch::IntegrationTest 4 | test 'returns the 422 json for ActionController:InvalidAuthenticityToken but without its template' do 5 | get_json '/users/new' 6 | 7 | assert_equal 422, response.status 8 | assert_equal "Something went wrong", json_response['message'] 9 | end 10 | 11 | test 'returns the 500 json for RuntimeError' do 12 | get_json '/users/1.json' 13 | 14 | assert_equal 500, response.status 15 | assert_equal "Something went wrong", json_response['message'] 16 | end 17 | 18 | test 'returns the 404 json for CustomException' do 19 | get_json '/users.json' 20 | 21 | assert_equal 404, response.status 22 | assert_equal "Page not found", json_response['message'] 23 | end 24 | 25 | test 'returns the 404 json for ActinoController::RoutingError' do 26 | get_json '/doesnt_exist.json' 27 | 28 | assert_equal 404, response.status 29 | assert_equal "Page not found", json_response['message'] 30 | end 31 | 32 | test 'renders an error page on a POST request' do 33 | post '/projects.json' 34 | 35 | assert_equal 500, response.status 36 | assert_equal "Something went wrong", json_response['message'] 37 | end 38 | 39 | test 'returns an appropriate status based on the rails version when the HTTP Accept header is malformed' do 40 | if Rails::VERSION::STRING >= '5.1.0' 41 | post '/users', headers: { "HTTP_ACCEPT" => "image/apng*/*" } 42 | else 43 | post '/users', nil, "HTTP_ACCEPT" => "image/apng*/*" 44 | end 45 | 46 | if Rails::VERSION::STRING >= '6.0.0' 47 | assert_equal 406, response.status 48 | assert_equal "The requested content type is not acceptable.\n", response.body 49 | elsif Rails::VERSION::STRING >= '5.2.0' 50 | assert_equal 422, response.status 51 | elsif Rails::VERSION::STRING >= '5.1.0' 52 | assert_equal 500, response.status 53 | elsif Rails::VERSION::STRING >= '4.2.0' 54 | assert_equal 201, response.status 55 | end 56 | end 57 | 58 | test 'returns an appropriate status based on the rails version when the HTTP Content-type header is malformed' do 59 | if Rails::VERSION::STRING >= '5.1.0' 60 | post '/users', headers: { "CONTENT_TYPE" => "charset=gbk" } 61 | else 62 | post '/users', nil, "CONTENT_TYPE" => "charset=gbk" 63 | end 64 | 65 | if Rails::VERSION::STRING >= '6.0.0' 66 | assert_equal 406, response.status 67 | assert_equal "The requested content type is not acceptable.\n", response.body 68 | elsif Rails::VERSION::STRING >= '5.2.0' 69 | assert_equal 422, response.status 70 | elsif Rails::VERSION::STRING >= '5.1.0' 71 | assert_equal 500, response.status 72 | elsif Rails::VERSION::STRING >= '4.2.0' 73 | assert_equal 201, response.status 74 | end 75 | end 76 | 77 | test 'returns an appropriate status when JSON data is malformed' do 78 | if Rails::VERSION::STRING >= '5.1.0' 79 | post '/unknown/path', params: 'x', headers: { "CONTENT_TYPE" => "application/json" } 80 | else 81 | post '/unknown/path', 'x', "CONTENT_TYPE" => "application/json" 82 | end 83 | 84 | if Rails::VERSION::STRING >= '5.0.0' 85 | assert_equal 404, response.status 86 | elsif Rails::VERSION::STRING >= '4.2.0' 87 | assert_equal 400, response.status 88 | end 89 | end 90 | 91 | 92 | private 93 | 94 | def without_layouts 95 | if Rails::VERSION::STRING >= '7.1.0' 96 | yield 97 | else 98 | begin 99 | `mv test/fake_app/app/views/layouts/application.html.erb .` 100 | `mv test/fake_app/app/views/layouts/error.html.erb .` 101 | 102 | yield 103 | ensure 104 | `mv application.html.erb test/fake_app/app/views/layouts/` 105 | `mv error.html.erb test/fake_app/app/views/layouts/` 106 | end 107 | end 108 | end 109 | 110 | def get_json(path, params: {}, headers: {}) 111 | without_layouts do 112 | if Rails::VERSION::STRING >= '5.1.0' 113 | get path, params: params, headers: { "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/json" }.merge(headers) 114 | else 115 | get path, params, { "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/json" }.merge(headers) 116 | end 117 | end 118 | end 119 | 120 | def json_response 121 | JSON.parse(response.body) 122 | end 123 | end if !ENV["CUSTOM_EXCEPTIONS_APP"] 124 | -------------------------------------------------------------------------------- /test/requests/error_page_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ErrorPageTest < ActionDispatch::IntegrationTest 4 | test 'displays the corresponding error page if /rambulance/status_in_words is requested in development' do 5 | skip 6 | 7 | visit '/rambulance/forbidden' 8 | 9 | assert_equal 403, page.status_code 10 | assert_includes page.body, "Error page" 11 | assert_includes page.body, "Forbidden." 12 | 13 | visit '/rambulance/internal_server_error' 14 | 15 | assert_equal 500, page.status_code 16 | assert_includes page.body, "Error page" 17 | assert_includes page.body, "Something went wrong." 18 | end 19 | 20 | test 'displays the 500 page for RuntimeError' do 21 | visit '/users/1' 22 | 23 | assert_equal 500, page.status_code 24 | assert_includes page.body, "Error page" 25 | assert_includes page.body, "Something went wrong." 26 | end 27 | 28 | test 'displays the 404 page for CustomException' do 29 | visit '/users' 30 | 31 | assert_equal 404, page.status_code 32 | assert_includes page.body, "Error page" 33 | assert_includes page.body, "Page not found." 34 | end 35 | 36 | test 'displays the 406 page for unknown format' do 37 | visit '/projects' 38 | 39 | assert_equal 200, page.status_code # Just to make sure normal html request succeeds 40 | 41 | visit '/projects.jpeg' 42 | 43 | if Rails.version < '5.0.0' 44 | assert_equal 500, page.status_code 45 | else 46 | assert_equal 406, page.status_code 47 | assert_includes page.body, "The requested content type is not acceptable." 48 | end 49 | end 50 | 51 | test 'displays the 404 page for ActionController::RoutingError' do 52 | visit '/doesnt_exist' 53 | 54 | assert_equal 404, page.status_code 55 | assert_includes page.body, "Error page" 56 | assert_includes page.body, "Page not found." 57 | end 58 | 59 | test 'displays the 400 page when malformed parameters' do 60 | page.driver.post '/users?x[y]=1&x[y][][w]=2' 61 | 62 | assert_equal 400, page.status_code 63 | assert_includes page.body, "Error page" 64 | assert_includes page.body, "Bad request." 65 | end 66 | 67 | test 'displays the 404 page for request to non-existing page with malformed parameters' do 68 | page.driver.post '/doesnt_exist?x[y]=1&x[y][][w]=2' 69 | 70 | assert_equal 404, page.status_code 71 | assert_includes page.body, "Error page" 72 | assert_includes page.body, "Page not found." 73 | end 74 | 75 | if Rails.version < '4.2.0' 76 | # Versions eariler than 4.2 use `Rack::MethodOverride` that calls the 77 | # request#POST method. It is configured to be called before the request 78 | # reaches Rails' ShowException, and it's impossible to capture from it. 79 | # 80 | # Since there is no good solution for us to fix it, I will consider it 81 | # to be a "won't fix" edge case and leave a test case here. 82 | test 'raises ArgumentError when posting malformed body' do 83 | assert_raises ArgumentError do 84 | page.driver.post '/users', 'email=abcd%%72900' 85 | end 86 | end 87 | elsif Rails.version.start_with?('4.2') 88 | test 'displays the 201 page' do 89 | page.driver.post '/users', 'email=abcd%%72900' 90 | 91 | assert_equal 201, page.status_code 92 | assert_includes page.body, "created." 93 | end 94 | else 95 | test 'displays the 400 page for POST request with malformed body' do 96 | page.driver.post '/users', 'email=abcd%%72900' 97 | 98 | assert_equal 400, page.status_code 99 | assert_includes page.body, "Error page" 100 | assert_includes page.body, "Bad request." 101 | end 102 | 103 | test 'displays the 400 page for PUT request with malformed body' do 104 | page.driver.put '/users/1', 'email=abcd%%72900' 105 | 106 | assert_equal 400, page.status_code 107 | assert_includes page.body, "Error page" 108 | assert_includes page.body, "Bad request." 109 | end 110 | 111 | test 'displays the 404 page for request to non-existing page with malformed body' do 112 | page.driver.post '/doesnt_exist', 'email=abcd%%72900' 113 | 114 | assert_equal 404, page.status_code 115 | assert_includes page.body, "Error page" 116 | assert_includes page.body, "Page not found." 117 | end 118 | end 119 | 120 | test 'displays the 403 page for ForbiddenException' do 121 | visit '/users/1/edit' 122 | 123 | assert_equal 403, page.status_code 124 | assert_includes page.body, "Error page" 125 | assert_includes page.body, "Forbidden." 126 | end 127 | 128 | test 'uses the custom layou twith a custom layout name' do 129 | @org, Rambulance.layout_name = Rambulance.layout_name, "application" 130 | 131 | begin 132 | visit '/doesnt_exist' 133 | assert_includes page.body, "Application page" 134 | ensure 135 | Rambulance.layout_name = @org 136 | end 137 | end 138 | 139 | test 'uses the 500 page and returns 422 when the template is missing' do 140 | visit '/users/new' 141 | 142 | assert_equal 422, page.status_code 143 | assert_includes page.body, "Error page" 144 | assert_includes page.body, "Something went wrong." 145 | end 146 | end if !ENV["CUSTOM_EXCEPTIONS_APP"] 147 | 148 | class ErrorPageWithCustomExceptionsAppTest < ActionDispatch::IntegrationTest 149 | test 'displays 500 page for RuntimeError' do 150 | visit '/users/1' 151 | 152 | assert_equal 500, page.status_code 153 | assert_includes page.body, "Custom error page" 154 | end 155 | 156 | test 'displays 404 page for ActinoController::RoutingError' do 157 | visit '/doesnt_exist' 158 | 159 | assert_equal 404, page.status_code 160 | assert_includes page.body, "Error page" 161 | assert_includes page.body, "Page not found." 162 | end 163 | end if ENV["CUSTOM_EXCEPTIONS_APP"] 164 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | 4 | # RAILS_ENV needs to be set to development to mount the error page debug view 5 | ENV['RAILS_ENV'] ||= 'development' 6 | 7 | require 'rails' 8 | require 'minitest/autorun' 9 | require 'minitest/pride' 10 | require 'action_controller/railtie' 11 | require 'action_view/railtie' 12 | require 'bundler/setup' 13 | Bundler.require 14 | 15 | require 'fake_app/rails_app' 16 | require 'capybara/rails' 17 | 18 | class ActionDispatch::IntegrationTest 19 | include Capybara::DSL 20 | 21 | def teardown 22 | Capybara.reset_sessions! 23 | Capybara.use_default_driver 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/test_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TestHelperTest < ActionDispatch::IntegrationTest 4 | include Rambulance::TestHelper 5 | 6 | test '#with_exceptions_app can enable the exceptions app in test' do 7 | with_exceptions_app do 8 | get '/does_not_exist' 9 | end 10 | 11 | assert_equal 404, response.status 12 | end 13 | 14 | test '#with_exceptions_app can disable the exceptions app in test' do 15 | with_exceptions_app enabled: false do 16 | assert_raises ActionController::RoutingError do 17 | get '/does_not_exist' 18 | end 19 | end 20 | end 21 | end 22 | 23 | --------------------------------------------------------------------------------