{"id":13828747,"url":"https://github.com/pragmarb/pragma","last_synced_at":"2025-05-08T21:16:37.263Z","repository":{"id":56888641,"uuid":"73838563","full_name":"pragmarb/pragma","owner":"pragmarb","description":"An expressive, opinionated ecosystem for building beautiful RESTful APIs with Ruby.","archived":false,"fork":false,"pushed_at":"2020-05-31T12:56:55.000Z","size":343,"stargazers_count":91,"open_issues_count":8,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-08T21:16:31.317Z","etag":null,"topics":["api","ecosystem","pragma","ruby","ruby-on-rails","trailblazer"],"latest_commit_sha":null,"homepage":"https://pragmarb.org","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/pragmarb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2016-11-15T17:31:00.000Z","updated_at":"2024-12-26T16:19:00.000Z","dependencies_parsed_at":"2022-08-20T23:40:52.384Z","dependency_job_id":null,"html_url":"https://github.com/pragmarb/pragma","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pragmarb","download_url":"https://codeload.github.com/pragmarb/pragma/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253149621,"owners_count":21861740,"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":["api","ecosystem","pragma","ruby","ruby-on-rails","trailblazer"],"created_at":"2024-08-04T09:03:06.330Z","updated_at":"2025-05-08T21:16:37.235Z","avatar_url":"https://github.com/pragmarb.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Pragma\n\n\u003e ## This project is not being maintained anymore.\n\u003e \n\u003e Unfortunately, I'm not doing much work on REST APIs (or APIs in general) anymore, which means\n\u003e it's been a while since I last used Pragma in a real project or improved the ecosystem. If anyone\n\u003e wants to take over the project and keep it alive, feel free to reach out to me at\n\u003e hello@alessandro.codes.   \n\n[![Build Status](https://travis-ci.org/pragmarb/pragma.svg?branch=master)](https://travis-ci.org/pragmarb/pragma)\n[![Coverage Status](https://coveralls.io/repos/github/pragmarb/pragma/badge.svg?branch=master)](https://coveralls.io/github/pragmarb/pragma?branch=master)\n[![Maintainability](https://api.codeclimate.com/v1/badges/e51e8d7489eb72ab97ba/maintainability)](https://codeclimate.com/github/pragmarb/pragma/maintainability)\n\nWelcome to Pragma, an expressive, opinionated ecosystem for building beautiful RESTful APIs with \nRuby.\n\nYou can think of this as a meta-gem that pulls in the following pieces:\n\n- [Pragma::Operation](https://github.com/pragmarb/pragma-operation);\n- [Pragma::Policy](https://github.com/pragmarb/pragma-policy);\n- [Pragma::Decorator](https://github.com/pragmarb/pragma-decorator);\n- [Pragma::Contract](https://github.com/pragmarb/pragma-contract).\n\nAdditionally, it also provides default CRUD operations that leverage all of the Pragma components\nand will make creating new resources in your API a breeze.\n\nLooking for a Rails integration? Check out [pragma-rails](https://github.com/pragmarb/pragma-rails)!\n\n## Philosophy\n\nPragma was created with a very specific goal in mind: to make the development of JSON APIs a matter\nof hours, not days. In other words, Pragma is for JSON APIs what Rails is for web applications.\n\nHere are the ground rules:\n\n1. **Pragma is opinionated.** With Pragma, you don't get to make a lot of choices and that's\n   _exactly_ why people are using it: they want to focus on the business logic of their API rather\n   than the useless details. We understand this approach will not work in some cases and that's\n   alright. If you need more personalization, only use a subset of Pragma (see item 2) or something\n   else.\n2. **Pragma is modular.** Pragma is built as a set of gems (currently 6), plus some standalone\n   tools. You can pick one or more modules and use them in your application as you see fit. Even\n   though they are completely independent from each other, they nicely integrate and work best when\n   used together, creating an ecosystem that will dramatically speed up your design and development\n   process.\n3. **Pragma is designed to be Rails-free.** Just as what happens with Trailblazer, our Rails \n   integration is decoupled from the rest of the ecosystem and all of the gems can be used without \n   Rails. This is just a byproduct of the project's design: Pragma is built with pure Ruby.\n   [pragma-rails](https://github.com/pragmarb/pragma-rails) is the only available framework \n   integration at the moment, but more will come! \n\n### Why not Trailblazer?\n\n[Trailblazer](https://github.com/trailblazer/trailblazer) and all of its companion projects are\nawesome. They are so awesome that Pragma is built on top of them: even though we're not using\nthe Trailblazer gem itself yet, many of the Pragma gems are simply extensions of their Trailblazer\ncounterparts:\n\n- decorators are [ROAR representers](https://github.com/apotonick/roar);\n- contracts are [Reform forms](https://github.com/apotonick/reform);\n- operations are [Trailblazer operations](https://github.com/trailblazer/trailblazer-operation).\n\nTrailblazer and Pragma have different (but similar) places in the Ruby world: Trailblazer is an\narchitecture for building all kinds of web applications in an intelligent, rational way, while\nPragma is an architecture for building JSON APIs. We have shamelessly taken all of the flexibility\nand awesomeness from the Trailblazer project and restricted it to a narrow field of work, providing\ntools, helpers and integrations that could never be part of Trailblazer due to their specificity.\n\nThank you, guys!\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'pragma'\n```\n\nAnd then execute:\n\n```console\n$ bundle\n```\n\nOr install it yourself as:\n\n```console\n$ gem install pragma\n```\n\n## Usage\n\n### Project Structure\n\nThis gem works best if you follow the recommended structure for organizing resources:\n\n```\n└── api\n    └── v1\n        └── article\n            ├── contract\n            │   ├── create.rb\n            │   └── update.rb\n            ├── operation\n            │   ├── create.rb\n            │   ├── destroy.rb\n            │   ├── index.rb\n            │   └── update.rb\n            └── decorator\n            |   ├── collection.rb\n            |   └── instance.rb\n            └── policy.rb\n```\n\nYour modules and classes would, of course, follow the same structure: `API::V1::Article::Policy` and \nso on and so forth.\n\nIf you adhere to this structure, the gem will be able to locate all of your classes without any\nexplicit configuration. This will save you a lot of time and is highly recommended.\n\n### Fantastic Five\n\nPragma comes with five built-in operations, often referred to as Fantastic Five (or \"FF\" for \nbrevity). They are, of course, Index, Show, Create, Update and Destroy. \n\nThese operations leverage the full power of the integrated Pragma ecosystem and require all four \ncomponents to be properly installed and configured in your application. You may reconfigure them\nto skip some of the steps, but it is highly recommended to use them as they come.\n\nYou can find these operations under [lib/pragma/operation](https://github.com/pragmarb/pragma/tree/master/lib/pragma/operation).\nTo use them, simply create your own operations and inherit from ours. For instance:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Create \u003c Pragma::Operation::Create\n          # This assumes that you have the following:\n          #   1) an Article model\n          #   2) a Policy (responding to #create?)\n          #   3) a Create contract\n          #   4) an Instance decorator\n        end\n      end\n    end\n  end\nend\n```\n\n## Macros\n\nThe FF are implemented through their own set of macros, which take care of stuff like authorizing,\npaginating, filtering etc.\n\nIf you want, you can use these macros in your own operations.\n\n### Classes\n\n**Used in:** Index, Show, Create, Update, Destroy\n\nThe `Classes` macro is responsible of tying together all the Pragma components: put it into an\noperation and it will determine the class names of the related policy, model, decorators and \ncontract. You can override any of these classes when defining the operation or at runtime if you\nwish.\n\nExample usage:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Create \u003c Pragma::Operation::Base\n          # Let the macro figure out class names.\n          step Pragma::Macro::Classes()\n          step :execute!\n          \n          # But override the contract.\n          self['contract.default.class'] = Contract::CustomCreate\n          \n          def execute!(options)\n            # `options` contains the following:\n            #    \n            #    `model.class`\n            #    `policy.default.class`\n            #    `policy.default.scope.class`\n            #    `decorator.instance.class`\n            #    `decorator.collection.class`\n            #    `contract.default.class` \n            #    \n            # These will be `nil` if the expected classes do not exist.\n          end\n        end\n      end\n    end\n  end\nend\n```\n\n### Model\n\n**Used in:** Index, Show, Create, Update, Destroy\n\nThe `Model` macro provides support for performing different operations with models. It can either\nbuild a new instance of the model, if you are creating a new record, for instance, or it can find\nan existing record by ID.\n\nExample of building a new record:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Create \u003c Pragma::Operation::Base\n          # This step can be done by Classes if you want.\n          self['model.class'] = ::Article\n           \n          step Pragma::Macro::Model(:build)\n          step :save!\n          \n          def save!(options)\n            # Here you'd usually validate and assign parameters before saving.\n  \n            # ...\n  \n            options['model'].save!\n          end\n        end\n      end\n    end\n  end\nend\n```\n\nAs we mentioned, `Model` can also be used to find a record by ID:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Show \u003c Pragma::Operation::Base\n          # This step can be done by Classes if you want.\n          self['model.class'] = ::Article\n           \n          step Pragma::Macro::Model(:find_by), fail_fast: true\n          step :respond!\n          \n          def respond!(options)\n            options['result.response'] = Response::Ok.new(\n              entity: options['model']\n            )\n          end\n        end\n      end\n    end\n  end\nend\n```\n\nIn the example above, if the record is not found, the macro will respond with `404 Not Found` and a\ndescriptive error message for you. If you want to override the error handling logic, you can remove \nthe `fail_fast` option and instead implement your own `failure` step.\n\n### Policy\n\n**Used in:** Index, Show, Create, Update, Destroy\n\nThe `Policy` macro ensures that the current user can perform an operation on a given record.\n\nHere's a usage example:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Show \u003c Pragma::Operation::Base\n          # This step can be done by Classes if you want.\n          self['policy.default.class'] = Policy\n          \n          step :model!\n          step Pragma::Macro::Policy(), fail_fast: true\n          # You can also specify a custom method to call on the policy:\n          # step Pragma::Macro::Policy(action: :custom_method), fail_fast: true\n          step :respond!\n          \n          def model!(params:, **)\n            options['model'] = ::Article.find(params[:id])\n          end\n        end\n      end\n    end\n  end\nend\n```\n\nIf the user is not authorized to perform the operation (i.e. if the policy's `#show?` method returns\n`false`), the macro will respond with `403 Forbidden` and a descriptive error message. If you want \nto override the error handling logic, you can remove the `fail_fast` option and instead implement \nyour own `failure` step.\n\nThe macro accepts the following options, which can be defined on the operation or at runtime:\n\n- `policy.context`: the context to use for the policy (optional, `current_user` is used if not\n  provided).\n\n### Ordering\n\n**Used in:** Index\n\nAs the name suggests, the `Ordering` macro allows you to easily implement default and user-defined\nordering.\n\nHere's an example:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Index \u003c Pragma::Operation::Base\n          # This step can be done by Classes if you want.\n          self['model.class'] = ::Article\n\n          self['ordering.default_column'] = :published_at\n          self['ordering.default_direction'] = :desc\n          self['ordering.columns'] = %i[title published_at updated_at]\n\n          step :model!\n\n          # This will override `model` with the ordered relation.\n          step Pragma::Macro::Ordering(), fail_fast: true\n\n          step :respond!\n\n          def model!(options)\n            options['model'] = options['model.class'].all\n          end\n          \n          def respond!(options)\n            options['result.response'] = Response::Ok.new(\n              entity: options['model']\n            )\n          end\n        end\n      end\n    end\n  end\nend\n```\n\nIf the user provides an invalid order column or direction, the macro will respond with `422 Unprocessable Entity`\nand a descriptive error message. If you wish to implement your own error handling logic, you can\nremove the `fail_fast` option and implement your own `failure` step.\n\nThe macro accepts the following options, which can be defined on the operation or at runtime:\n\n- `ordering.columns`: an array of columns the user can order by.\n- `ordering.default_column`: the default column to order by (default: `created_at`).\n- `ordering.default_direction`: the default direction to order by (default: `desc`).\n- `ordering.column_param`: the name of the parameter which will contain the order column.\n- `ordering.direction_param`: the name of the parameter which will contain the order direction.\n\n### Pagination\n\n**Used in:** Index\n\nThe `Pagination` macro is responsible for paginating collections of records through \n[will_paginate](https://github.com/mislav/will_paginate). It also allows your users to set the \nnumber of records per page.\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Index \u003c Pragma::Operation::Base\n          # This step can be done by Classes if you want.\n          self['model.class'] = ::Article\n\n          step :model!\n\n          # This will override `model` with the paginated relation.\n          step Pragma::Macro::Pagination(), fail_fast: true\n\n          step :respond!\n\n          def model!(options)\n            options['model'] = options['model.class'].all\n          end\n          \n          def respond!(options)\n            options['result.response'] = Response::Ok.new(\n              entity: options['model']\n            )\n          end\n        end\n      end\n    end\n  end\nend\n```\n\nIn the example above, if the page or per-page number fail validation, the macro will respond with\n`422 Unprocessable Entity` and a descriptive error message. If you wish to implement your own error \nhandling logic, you can remove the `fail_fast` option and implement your own `failure` step.\n\nThe macro accepts the following options, which can be defined on the operation or at runtime:\n\n- `pagination.page_param`: the parameter that will contain the page number.\n- `pagination.per_page_param`: the parameter that will contain the number of items to include in each page.\n- `pagination.default_per_page`: the default number of items per page.\n- `pagination.max_per_page`: the max number of items per page.\n\nThis macro is best used in conjunction with the [Collection](https://github.com/pragmarb/pragma-decorator#collection) \nand [Pagination](https://github.com/pragmarb/pragma-decorator#pagination) modules of \n[Pragma::Decorator](https://github.com/pragmarb/pragma-decorator), which will expose all the \npagination metadata.\n\n### Decorator\n\n**Used in:** Index, Show, Create, Update\n\nThe `Decorator` macro uses one of your decorators to decorate the model. If you are using \n[expansion](https://github.com/pragmarb/pragma-decorator#associations), it will also make sure that\nthe expansion parameter is valid.\n\nExample usage:\n\n```ruby\nmodule API\n  module V1\n    module Article\n      module Operation\n        class Show \u003c Pragma::Operation::Base\n          # This step can be done by Classes if you want.\n          self['decorator.instance.class'] = Decorator::Instance\n          \n          step :model!\n          step Pragma::Macro::Decorator(), fail_fast: true\n          step :respond!\n          \n          def model!(params:, **)\n            options['model'] = ::Article.find(params[:id])\n          end\n          \n          def respond!(options)\n            # Pragma does this for you in the default operations.\n            options['result.response'] = Response::Ok.new(\n              entity: options['result.decorator.instance']\n            )\n          end\n        end\n      end\n    end\n  end\nend\n```\n\nThe macro accepts the following options, which can be defined on the operation or at runtime:\n\n- `expand.enabled`: whether associations can be expanded.\n- `expand.limit`: how many associations can be expanded at once.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmarb%2Fpragma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpragmarb%2Fpragma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmarb%2Fpragma/lists"}