{"id":19882920,"url":"https://github.com/thiagomajesk/briefcase","last_synced_at":"2026-06-11T00:30:58.417Z","repository":{"id":119490680,"uuid":"226152258","full_name":"thiagomajesk/briefcase","owner":"thiagomajesk","description":"A simple plug for transporting ephemeral data through requests","archived":false,"fork":false,"pushed_at":"2022-02-02T23:18:35.000Z","size":11,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-01T03:26:00.630Z","etag":null,"topics":["elixir","phoenix","plug","prg"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","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/thiagomajesk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-05T17:13:52.000Z","updated_at":"2022-02-02T23:18:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"ebfa468e-0ae0-4bbb-b50c-fa0d13f06484","html_url":"https://github.com/thiagomajesk/briefcase","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/thiagomajesk/briefcase","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagomajesk%2Fbriefcase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagomajesk%2Fbriefcase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagomajesk%2Fbriefcase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagomajesk%2Fbriefcase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thiagomajesk","download_url":"https://codeload.github.com/thiagomajesk/briefcase/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thiagomajesk%2Fbriefcase/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34177445,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"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":["elixir","phoenix","plug","prg"],"created_at":"2024-11-12T17:18:55.213Z","updated_at":"2026-06-11T00:30:58.391Z","avatar_url":"https://github.com/thiagomajesk.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Briefcase\n\nBriefcase is a simple plug for transporting temporary data through requests. You can read more about the motivation behind Briefcase in [this thread](https://elixirforum.com/t/is-prg-a-valid-technique-in-phoenix/27249/24).\n\n**Important Note:** This is code is still highly experimental\n\n## Use case\n\nImagine the following scenario: You are building your own blog which has posts and comments. A common implementation could be having RESTfull resources like so:\n\n```elixir\nresource \"/posts\", PostController do\n  resource /comments\", CommentController\nend\n```\n\nNow, let's imagine that you want to see all recent comments when you access a post:\n\n```elixir\ndef show(conn, %{\"post_id\" =\u003e post_id}) do\n comments = Posts.recent_comments(post_id)\n render(conn, \"show.html\", comments: comments)\nend\n```\n\nSo far so good... After you create a post, you can access it and see a list of recent comments. Now, we want to add a commment box so users can comment on those posts: \n\n```elixir\ndef show(conn, %{\"post_id\" =\u003e post_id}) do\n comments = Posts.recent_comments(post_id)\n comment_changeset = Posts.change_comment()\n render(conn, \"show.html\", comments: comments, comment_changeset: comment_changeset)\nend\n```\n\nFor the sake of the example, let's say we want to limit the comments to no more than 300 caracteres. After those validations are in place, we'll have something like this:\n\n```elixir\ndef create(conn, %{\"post_id\" =\u003e post_id, \"commment\" =\u003e comment_params}) do\n  post = Posts.get_post!(post_id)\n  case Posts.create_comment(post, comment_params) do\n    {:ok, comment} -\u003e redirect(to: Routes.post_path(conn, :show, post)\n    {:error, %Ecto.Changeset{} = changeset} -\u003e redirect(to: Routes.post_path(conn, :show, post)\n  end\nend\n``` \n\nNow notice that, because we have a separate resource to create comments, we want to return back to the post page to see that our comment was properly created. However, what happens if a user tries to create a invalid comment? If that the case, we should be able to show a helpfull message so the user can fix what's wrong and try again. \nOne way of doing that is to re-render the post page again, passing the recent comments and every other assign that its necessary to render that page again. Although _there are various ways of doing that¹_, what if we delegate the rendering responsability only to the `show` action of `PostController`? We do that by redirecting the user to the action which knows what is required for that page to properly function. Ok, but now that we are redirecting the user back to the post page, we won't be able to return the validated changeset which contains helpfull error message to our user, and that's where the PRG pattern commes to the rescue.\n\n\u003e ¹ A common alternative is to abstract the code into a separate, helper function that can be called from both controllers and helps compose the assigns necessary to render the page. However, this might not be the best option for some use cases.\n\n## Setup\n\nSince the package is not published on hex, the only way you can use it right now is by referencing the repository directly in in your `mix.exs` file:\n\n```elixir\ndefp deps do\n  [\n    {:briefcase, github: \"thiagomajesk/briefcase\"}\n  ]\nend\n```\n\nYou can also import Briefcase in your `\"project_web.ex\"` file to make life a little bit easier when using it:\n\n```elixir\ndef controller do\n  quote do\n    use Phoenix.Controller, namespace: ProjectWeb\n\n    import Plug.Conn\n    import ProjectWeb.Gettext\n    import Briefcase, only: [pack: 2, unpack: 3, peek: 3]\n    alias ProjectWeb.Router.Helpers, as: Routes\n  end\nend\n```\n\nYou should also, enable the plug globally in the browser pipeline in your `router.ex` file:\n\n```elixir\npipeline :browser do\n  plug :accepts, [\"html\"]\n  plug :fetch_session\n  plug :fetch_flash\n  plug :protect_from_forgery\n  plug :put_secure_browser_headers\n  plug Briefcase\nend\n```\n\n\u003e Since this plug relies heavily on the session, the recommended way of configuring it is globally. That way you won't have to worry about having artifacts living on the session for more time than they are supposed to.\n\n## Examples\n\n### Implementing the PRG (Post Redirect Get) pattern in Phoenix\n\nYou can use Briefcase to implement the [PRG](https://en.wikipedia.org/wiki/Post/Redirect/Get) pattern in your Phoenix controllers:\n\n\n```elixir\ndef new(conn, _params) do\n  # You can pass a default param to unpack/3 that will be used as a fallback\n  # (Notice that the modified %Plug.Conn{} struct is also returned)\n  {conn, changeset} = unpack(conn, :validation_errors, Context.change_resource(%Resource{}))\n  render(conn, \"new.html\", changeset: changeset)\nend\n\ndef create(conn, %{\"resource\" =\u003e resource_params}) do\n  case Context.create_resource(resource_params) do\n    {:ok, resource} -\u003e\n      conn\n      |\u003e put_flash(:info, \"Resource created successfully.\")\n      |\u003e redirect(to: Routes.resource_path(conn, :show, resource))\n\n    {:error, %Ecto.Changeset{} = changeset} -\u003e\n      conn\n      |\u003e pack([validation_errors: changeset])\n      |\u003e redirect(to: Routes.resource_path(conn, :new))\n  end\nend\n```\n\n## How it works\n\nBriefcase works by saving data temporarily in the session and watching if it has been consumed. After you consume the stored content with `unpack/3`, it will be marked for deletion. This will typically happen after your controller yields a response.   \nThe content stored by Briefcase is meant to be short-lived, this means that on the beginning of each request, any content that was not already marked as \"dirty\" will be, unless you use `peek/2` to consume its contents and remove the \"dirty state\". \n\nThe life-cycle is shortly explained bellow:\n\n```elixir\n# Recycling¹\ndef first_action(conn, _params) do\n  conn\n    |\u003e pack([first: value]) # Packs the content (not dirty)\n    |\u003e redirect(to: Routes.resource_path(conn, :second_action))\n    # Cleanup²\nend\n\n# Recycling¹\ndef first_action(conn, _params) do\n  # Retrieves the stored content without making it \"dirty\".\n  # Since the \"first\" key was marked dirty in the beginning of this request, \n  # using peek/2 will prevent its deletion at the end of this request\n  {conn, _value} = peek(conn, :first) # not \"dirty\" anymore\n \n  conn\n    |\u003e pack([second: value]) # pack new content (not dirty)\n    |\u003e redirect(to: Routes.resource_path(conn, :third_action))\n    # Cleanup²\nend\n\n# Recycling¹\ndef second_action(conn, _params) do\n\n  {conn, _first_value} = unpack(conn, :first) # makes \"first\" dirty\n  {conn, _second_value} = unpack(conn, :second) # makes \"second\" dirty\n\n  redirect(conn, to: Routes.resource_path(conn, :index))\n  # Cleanup² (the contents of \"first\" and \"second\" will be removed here)\nend\n```\n\n\u003e ¹**Recycling**: Any content not already marked as dirty becomes dirty before this request is processed  \n\n\u003e ²**Cleanup**: After the response, deletes any content that was previously marked as \"dirty\" \n\n## TODOs\n\n- [ ] Add option to save session store to database instead of cookie\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiagomajesk%2Fbriefcase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthiagomajesk%2Fbriefcase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthiagomajesk%2Fbriefcase/lists"}