{"id":19316724,"url":"https://github.com/maxim/narrative","last_synced_at":"2025-04-12T13:40:53.996Z","repository":{"id":66282597,"uuid":"594559594","full_name":"maxim/narrative","owner":"maxim","description":"Rails template promoting narrative-centric (rather than model-centric) architectural approach.","archived":false,"fork":false,"pushed_at":"2023-03-23T17:27:50.000Z","size":71,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-26T08:13:57.724Z","etag":null,"topics":["architecture","rails-templates"],"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/maxim.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2023-01-28T23:07:57.000Z","updated_at":"2024-10-22T09:27:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"5c8e9d03-72f4-4b8f-a104-c7029f51773d","html_url":"https://github.com/maxim/narrative","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim%2Fnarrative","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim%2Fnarrative/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim%2Fnarrative/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxim%2Fnarrative/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxim","download_url":"https://codeload.github.com/maxim/narrative/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248574333,"owners_count":21126994,"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","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":["architecture","rails-templates"],"created_at":"2024-11-10T01:12:24.699Z","updated_at":"2025-04-12T13:40:53.974Z","avatar_url":"https://github.com/maxim.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Narrative - a Rails template\n\n\u003e **nar·ra·tive** (_noun_) — a spoken or written account of connected events; a story.\n\n## Overview\n\nThis template consists of a few patterns overlayed on top of Rails. Explore the [ARCHITECTURE.md](example_app/ARCHITECTURE.md) of the [example_app](example_app) to see what they are.\n\n## Usage\n\nStart a new rails app with the latest release.\n\n```shell\nrails new my_app -m https://github.com/maxim/narrative/releases/latest/download/narrative.rb\n```\n\nCheck [releases](https://github.com/maxim/narrative/releases) if you'd like to pick an older one.\n\n## Philosophy\n\nNarrative-centric approach promotes the idea that your codebase should be telling short stories in your app's entry routines. All the major business actions should be visible at this level. The opposite of a narrative-centric would be model-centric, where your entry points contain a single call into a core model, and the rest of the story is hidden in callbacks. See [Rails — narrative vs model centric approach](https://max.engineer/rails-narratives-vs-models) for some unpacking.\n\nThis template follows the following principles to help make clear narratives:\n\n1. **DIY over reusable legacy code** — instead of giving you more framework to learn, this template only adds a few well-documented patterns that you and your team are encouraged to follow yourselves. Almost nothing here adds functionality or hooks into Rails itself, and therefore doesn't inhibit Rails upgradeability.\n2. **Good constructors** — perform most data transformation, filtering, and coersion in constructor methods. All struct objects introduced by this template are read only, but you can add constructors such as `from_something` to build them from all kinds of other data. In the end, these constructors will always call `.new` with the final attributes of the object itself. This practice is facilitated via the use of [portrayal gem](https://github.com/maxim/portrayal).\n3. **Split abstractions on IO**[^1] — always split network calls, database calls, filesystem access, ENV access, and similar actions into standalone function calls directly from your entry points (i.e. \"shell\" code). Do not tangle them into core logic. Examples of entry points are: controller actions, rake tasks (and other CLI tools), background job actions. You could designate more entry points, depending on your app's interfaces.\n4. **Routines over rich callbacks** — unlike Basecamp's approach, narrative approach promotes the use of transactions in your controllers. When you need to be sure that multiple actions are performed together, wrap them into a transaction block (the helper is provided by this template). Not all callbacks are bad. It's still okay to use them for attribute preparations (`before_*`), and additional db requests tied intimately to the same model/operation. For more information on the difference in approach see [Rails — narrative vs model centric approach](https://max.engineer/rails-narratives-vs-models).\n5. **Strict view arguments** — Each controller action must provide at most one page object to the view. Everything that the view needs must be in that object. Don't place god objects, such as ActiveRecord models into the page, it would defeat the purpose. The only values you should allow are primitive types (strings, numbers, etc), and container types (arrays, hashes, structs, other page objects) which themselves have the same restriction. A good rule of thumb is that the entire thing should be JSON-serializable. For more info on what should go into page objects, see [Don’t Build A General Purpose API To Power Your Own Front End](https://max.engineer/server-informed-ui).\n6. **Readability over consistency** — although this template comes with [standard gem](https://github.com/testdouble/standardrb) and [rubocop](https://github.com/rubocop/rubocop), the rules are relaxed to allow for more expressiveness in your code. It's useful to have some guardrails, but most spacing and alignment is left up to case-by-case readability consideration. These rules are likely to get further relaxed over time. See [Writing Maintainable Code is a Communication Skill](https://max.engineer/maintainable-code) for my views on code maintainability. The \"How\" part is especially relevant here.\n\n## Additional notes\n\nRubocop will find a couple of issues out of the box. I recommend autocorrecting them.\n\n---\n\n[^1]: There's nothing specific in the template that makes you follow this principle, but it is how you end up with proper steps in your narratives.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxim%2Fnarrative","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxim%2Fnarrative","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxim%2Fnarrative/lists"}