{"id":15534088,"url":"https://github.com/nullvoxpopuli/skinny_controllers","last_synced_at":"2025-09-11T01:40:12.817Z","repository":{"id":2358516,"uuid":"46292153","full_name":"NullVoxPopuli/skinny_controllers","owner":"NullVoxPopuli","description":"A pattern for allowing for easier testing of large projects' business logic","archived":false,"fork":false,"pushed_at":"2023-01-19T05:26:02.000Z","size":263,"stargazers_count":88,"open_issues_count":11,"forks_count":7,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-05T14:47:58.997Z","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/NullVoxPopuli.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}},"created_at":"2015-11-16T17:57:17.000Z","updated_at":"2024-02-08T16:59:19.000Z","dependencies_parsed_at":"2023-02-10T20:25:11.218Z","dependency_job_id":null,"html_url":"https://github.com/NullVoxPopuli/skinny_controllers","commit_stats":null,"previous_names":["nullvoxpopuli/skinny-controllers"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fskinny_controllers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fskinny_controllers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fskinny_controllers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NullVoxPopuli%2Fskinny_controllers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NullVoxPopuli","download_url":"https://codeload.github.com/NullVoxPopuli/skinny_controllers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249110602,"owners_count":21214362,"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":[],"created_at":"2024-10-02T11:40:56.335Z","updated_at":"2025-04-15T16:43:02.960Z","avatar_url":"https://github.com/NullVoxPopuli.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# skinny_controllers\n\n_skinny_controllers is a thin layer on top of rails with the goal of allowing for much easier unit-testability, inspired by ember_\n\nA demo app can be found [in the spec here](https://github.com/NullVoxPopuli/skinny_controllers/tree/master/spec/support/rails_app).\n\n[![Join the chat at https://gitter.im/NullVoxPopuli/skinny_controllers](https://badges.gitter.im/NullVoxPopuli/skinny_controllers.svg)](https://gitter.im/NullVoxPopuli/skinny_controllers?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![Gem Version](https://badge.fury.io/rb/skinny_controllers.svg)](https://badge.fury.io/rb/skinny_controllers)\n[![Build Status](https://travis-ci.org/NullVoxPopuli/skinny_controllers.svg?branch=master)](https://travis-ci.org/NullVoxPopuli/skinny_controllers)\n[![Code Climate](https://codeclimate.com/github/NullVoxPopuli/skinny_controllers/badges/gpa.svg)](https://codeclimate.com/github/NullVoxPopuli/skinny_controllers)\n[![Test Coverage](https://codeclimate.com/github/NullVoxPopuli/skinny_controllers/badges/coverage.svg)](https://codeclimate.com/github/NullVoxPopuli/skinny_controllers/coverage)\n[![Dependency Status](https://gemnasium.com/NullVoxPopuli/skinny_controllers.svg)](https://gemnasium.com/NullVoxPopuli/skinny_controllers)\n\nAn implementation of role-based policies and operations to help controllers lose weight.\n\nThe goal of this project is to help API apps be more slim, and separate logic as much as possible.\n\nIf you have an idea or suggestion for improved defaults, please submit an issue or pull request. :-)\n\n# Overview\n\n- Controllers _only_ contain render logic. Typically, `render json: model`\n- Business logic is encapsulated in operations.\n  - Without creating any new classes, `render json: model` will give you default CRUD functionality in your controller actions.\n  - There is one operation per controller action.\n- Policies help determine whether or not the `current_user` is allowed to perform an action on an object.\n- [Click here to see how this is different from trailblazer](https://github.com/NullVoxPopuli/skinny_controllers#how-is-this-different-from-trailblazer)\n\n\n# Installation\n\n```ruby\ngem 'skinny_controllers'\n```\nor\n\n```bash\ngem install skinny_controllers\n```\n\n# Usage\n\n## In a controller:\n\n```ruby\ninclude SkinnyControllers::Diet\n# ...\n# in your action\nrender json: model\n```\n\nand that's it!\n\n\n### What if you want to call your own operations?\n\nSometimes, magic is scary. You can call anything you want manually (operations and policies).\n\nHere is an example that manually makes the call to the Host Operations and passes the subdomain parameter in to filter the `Host` object on the subdomain.\n```ruby\ndef show\n  render json: host_from_subdomain, serializer: each_serializer\nend\n\nprivate\n\ndef host_from_subdomain\n  @host ||= HostOperations::Read.new(current_user, params, host_params).run\nend\n\ndef host_params\n  params.permit(:subdomain)\nend\n```\n\nThe parameters for directly calling an operation are as follows:\n\n| # | Parameter | Default when directly calling an operation | Implicit default | Purpose |\n|---|-------------------|--------------------------------------------|------------------------------|------------------------------------------|\n| 0 | current_user | n/a | `current_user` | the user performing the action |\n| 1 | controller_params | n/a | `params` | the full params hash from the controller |\n| 2 | params_for_action | `controller_params` | `create_params`, `index_params`, etc |  e.g.: requiring a foreign key when looking up index |\n| 3 | action | `controller_params[:action]` | `action_name` | the name of the current action |\n| 4 | options | `{}` | skinny_controllers_config options |\n\n### For JSON-API\n\nStrong parameters must be used on create/update actions.\n\nHere is an example params method\n\n```ruby\nprivate\n\ndef event_params\n  params\n    .require(:data)\n    .require(:attributes)\n    .permit(:name)\nend\n```\n\nNote that we don't need the id under the data hash, because in a RESTful api, the id will be available to us through the top level params hash.\n\n-------------------------------------------------------\n\n## How is this different from trailblazer?\n\nThis may not be horribly apparent, but here is a table overviewing some highlevel differences\n\n| Feature | - | skinny_controllers | [trailblazer](https://github.com/apotonick/trailblazer) |\n|----|----|----|----|\n| Purpose |-| API - works very well with [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers)| General - additional features for server-side rendered views |\n| Added Layers |-| Operations, Policies | Operations, Policies, Forms |\n| Validation |-| stay in models | moved to operations via contract block |\n| Additional objects|-| none | contacts, representers, callbacks, cells |\n| Rendering |-|  done in the controller, and up to the dev to decide how that is done. `ActiveModel::Serializers` with JSON-API is highly recommended |-| representers provide a way to define serializers for json, xml, json-api, etc |\n| App Structure |-|  same as rails. `app/operations` and `app/policies` are added | encourages a new structure 'concepts', where cells, view templates, assets, operations, etc are all under `concepts/{model-name}` |\n\n\n# Contributing\n\nPlease refer to each project's style guidelines and guidelines for submitting patches and additions. In general, we follow the \"fork-and-pull\" Git workflow.\n\n 1. **Fork** the repo on GitHub\n 2. **Clone** the project to your own machine\n 3. **Commit** changes to your own branch\n 4. **Push** your work back up to your fork\n 5. Submit a **Pull request** so that we can review your changes\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnullvoxpopuli%2Fskinny_controllers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnullvoxpopuli%2Fskinny_controllers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnullvoxpopuli%2Fskinny_controllers/lists"}