{"id":17585003,"url":"https://github.com/tommeier/rails-prg","last_synced_at":"2025-10-28T06:34:30.896Z","repository":{"id":14331766,"uuid":"17041260","full_name":"tommeier/rails-prg","owner":"tommeier","description":"Rails handler for implementing full POST-REDIRECT-GET pattern (even on errors)","archived":false,"fork":false,"pushed_at":"2015-04-14T08:10:30.000Z","size":431,"stargazers_count":21,"open_issues_count":3,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-10-08T03:55:07.485Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tommeier.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-02-21T01:02:51.000Z","updated_at":"2020-09-27T22:02:45.000Z","dependencies_parsed_at":"2022-09-03T08:52:17.874Z","dependency_job_id":null,"html_url":"https://github.com/tommeier/rails-prg","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/tommeier/rails-prg","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommeier%2Frails-prg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommeier%2Frails-prg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommeier%2Frails-prg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommeier%2Frails-prg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tommeier","download_url":"https://codeload.github.com/tommeier/rails-prg/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tommeier%2Frails-prg/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281397336,"owners_count":26493908,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-28T02:00:06.022Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-22T02:23:49.714Z","updated_at":"2025-10-28T06:34:30.880Z","avatar_url":"https://github.com/tommeier.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rails::Prg (post-redirect-get)\n\n[![Build Status](https://travis-ci.org/tommeier/rails-prg.png)](https://travis-ci.org/tommeier/rails-prg)\n[![Selenium Test Status](https://saucelabs.com/buildstatus/rails-prg)](https://saucelabs.com/u/rails-prg)\n[![Selenium Test Status](https://saucelabs.com/browser-matrix/rails-prg.svg)](https://saucelabs.com/u/rails-prg)\n\nSecure applications disable browser history and internal cache. Unfortunately, this causes problems with most browsers when following the standard Rails pattern for displaying errors.\n\nWe never really see an issue as Rails developers because we usually allow browser-history and internal store. This gem is ***only*** required when `no-cache, no-store` is applied in your headers for a secure application.\n\nStandard Rails method for error handling:\n  * POST form\n  * Error generated -\u003e Render same action\n  * POST form\n  * No errors -\u003e Redirect to successful action\n\nAt this point, and the back button is pressed; each browser handles it slightly differently. Firefox skips back two pages in the history with new content and no error, and chrome skips back one, and displays the previous content (with error).\n\nIn a secure application, browsers are unable to determine the content from the internal cache and raise an error. Example from Chrome when clicking back button after successful redirect raises an `ERR_CACHE_MISS`:\n\n![Example of Chrome ERR_CACHE_MISS](https://f.cloud.github.com/assets/19973/2430174/318aa678-acc0-11e3-9bf8-0535d51a2fac.png)\n\nFor full protection from ERR_CACHE_MISS, and equivalent in other browsers, the pattern should be altered to follow a full POST-REDIRECT-GET patten.\n\nFull Post-Redirect-Get pattern:\n  * POST form\n  * Error generated -\u003e ***redirect*** back displaying errors\n  * POST form\n  * No errors -\u003e Redirect to successful action\n\nThis way the browser will always have a consistent back-button history to traverse without\ntriggering browser errors. This error can also be triggered by:\n  * Browser crashing (and attempting restore)\n  * Abnormal closure of browser such as power cutting out (and attempting restore)\n  * 'Restore' last session\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    `gem 'rails-prg'`\n\nAnd then execute:\n\n    `$ bundle`\n\nOr install it yourself as:\n\n    `$ gem install rails-prg`\n\n## Usage\n\nThe standard controller method for Rails (in a changing action like Create/Update) would look like:\n\n```\ndef update\n  if @object.save\n    flash[:notice] = 'Huzzah - I was successful!'\n    redirect_to objects_path\n  else\n    flash[:error] = 'Oh no - saving failed'\n\n    render :edit\n  end\nend\n```\n\nTo enable full Post-Redirect-Get for errors, the object has to be reinitialized after the redirect with both the set params and the new errors via the flash object (one request only). For security reasons, this will raise errors unless `permitted` params are passed for the redirect (see [strong parameters](http://api.rubyonrails.org/classes/ActionController/Parameters.html)).\n\nUsing `Rails-Prg` and to implement full POST-REDIRECT-GET this action (and any other change action such as `create`) should be changed to the following.\n\nExample with filters:\n\n```\nbefore_filter :load_object, only: [:edit, :update]\nbefore_filter :load_redirected_objects!, only: [:edit]\n\ndef update\n  if @object.save\n    flash[:notice] = 'Huzzah - I was successful!'\n    redirect_to objects_path\n  else\n    set_redirected_object!('@object', @object, clean_params) # Pass errors\n    redirect_to edit_object_path(@object)                    # Redirect back\n  end\nend\n```\n\nOn redirection to the edit page the filter `set_redirected_object!` will assign any params passed on to the object (by submission) and any errors present in the flash object to act as normal, as if it was simply rendered on the previous page.\n\nExample without filters:\n\n```\ndef new\n  @object = Object.new\n  load_redirected_objects!\nend\n\ndef create\n  @object = Object.new(params[:object])\n\n  if @object.save\n    flash[:notice] = 'Huzzah - I was created!'\n    redirect_to objects_path\n  else\n    set_redirected_object!('@object', @object, safe_params)  # Pass errors\n    redirect_to new_object_path                              # Redirect back\n  end\nend\n```\n\nThis strategy also has the benefit of being completely uniform across all browsers in behaviour. Before, for instance, Chrome ended on a different page to Firefox when clicking back (one skips a page in history and goes further back, chrome displays with the errors instead).\n\n\n## Further explanation\n\nThe way Rails `render`s an error instead of redirecting, is completely expected and normal. The primary reason is for performance, no additional HTTP hit required, and the object is already loaded (with errors). Pretty much every framework has a similar strategy. In fact, you can duplicate the browser cache bug in the majority of secure websites online today.\n\nHowever, though better for performance reasons, this breaks the old browser pattern of POST-REDIRECT-GET (http://en.wikipedia.org/wiki/Post/Redirect/Get)\n\n```\nPost/Redirect/Get (PRG) is a web development design pattern that prevents some duplicate form\n submissions, creating a more intuitive interface for user agents (users). PRG implements\nbookmarks and the refresh button in a predictable way that does not create duplicate\nform submissions.\n```\n\n![POST REDIRECT GET example](http://upload.wikimedia.org/wikipedia/commons/3/3c/PostRedirectGet_DoubleSubmitSolution.png)\n\nAs such, for a secure application, we need to always ensure `no-cache,no-store` is set, and always use the POST-REDIRECT-GET pattern.\n\n### Browser peculiarities\n\nChrome (other browsers have their own equivalent) has the following code that breaks with the Rails method when `no-store` is set:\n  * https://chromium.googlesource.com/chromium/chromium/+/master/webkit/appcache/appcache_response.cc\n\nChrome code:\n```\nvoid AppCacheResponseReader::ContinueReadData() {\n  if (!entry_)  {\n    ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);\n    return;\n  }\n}\n```\n\nBasically, with `no-store` as a header, and allowing 'back button' to be used, the browser gets into a state, where it attempts to load the rendered error page (without redirect) and the headers have already blocked saving the history, and raises the `ERR_CACHE_MISS` error.\n\nWith POST-REDIRECT-GET pattern this is avoided (as the form submission page always ends in redirect)\n\nExample of error displayed on Chrome:\n\n![Example of Chrome ERR_CACHE_MISS Process](https://f.cloud.github.com/assets/19973/2430175/4117352a-acc0-11e3-9deb-ba209fa49097.gif)\n\n## Contributing\n\n1. Fork it ( http://github.com/tommeier/rails-prg/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Add specs and ensure all tests pass (in multiple browsers)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## Running tests\n\nTo display in Chrome browsers, ensure you have the latest `chromedriver` installed:\n\n`brew install chromedriver`\n\n### Running all tests, with setup, Chrome and Firefox browser features\n\n`script/spec`\n\n### Running features only in Chrome\n\n`BROWSER=chrome rspec spec/rails/prg/features`\n\n### Running features only in Firefox (default)\n\n`BROWSER=firefox rspec spec/rails/prg/features`\n\n## Appendix\n\n### How to to generate dummy rails app for test structure (use when updating rails)\n\n  * Command for dummy rails app\n  * Scaffolding request objects:\n    * `rails generate scaffold ExamplePrg subject:text:uniq body:text published:boolean`\n    * `rails generate scaffold ErrorDuplicator subject:text:uniq body:text published:boolean`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftommeier%2Frails-prg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftommeier%2Frails-prg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftommeier%2Frails-prg/lists"}