{"id":20252747,"url":"https://github.com/pragmarb/pragma-operation","last_synced_at":"2025-04-10T23:23:49.686Z","repository":{"id":56888632,"uuid":"73838708","full_name":"pragmarb/pragma-operation","owner":"pragmarb","description":"Endpoint encapsulation that doesn't get in the way.","archived":false,"fork":false,"pushed_at":"2020-01-08T22:33:50.000Z","size":129,"stargazers_count":4,"open_issues_count":2,"forks_count":2,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-24T20:11:31.722Z","etag":null,"topics":["api","ecosystem","pragma","ruby","ruby-on-rails","trailblazer"],"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/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:32:50.000Z","updated_at":"2020-01-08T22:33:52.000Z","dependencies_parsed_at":"2022-08-20T23:40:31.619Z","dependency_job_id":null,"html_url":"https://github.com/pragmarb/pragma-operation","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma-operation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma-operation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma-operation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmarb%2Fpragma-operation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pragmarb","download_url":"https://codeload.github.com/pragmarb/pragma-operation/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248055271,"owners_count":21040151,"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-11-14T10:19:16.772Z","updated_at":"2025-04-10T23:23:49.635Z","avatar_url":"https://github.com/pragmarb.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pragma::Operation\n\n[![Build Status](https://travis-ci.org/pragmarb/pragma-operation.svg?branch=master)](https://travis-ci.org/pragmarb/pragma-operation)\n[![Coverage Status](https://coveralls.io/repos/github/pragmarb/pragma-operation/badge.svg?branch=master)](https://coveralls.io/github/pragmarb/pragma-operation?branch=master)\n[![Maintainability](https://api.codeclimate.com/v1/badges/e51e8d7489eb72ab97ba/maintainability)](https://codeclimate.com/github/pragmarb/pragma-operation/maintainability)\n\nOperations encapsulate the business logic of your JSON API.\n\nThey are built on top of the [Trailblazer::Operation](https://github.com/trailblazer/trailblazer-operation) gem.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'pragma-operation'\n```\n\nAnd then execute:\n\n```console\n$ bundle\n```\n\nOr install it yourself as:\n\n```console\n$ gem install pragma-operation\n```\n\n## Usage\n\nLet's build your first operation!\n\n```ruby\nmodule API\n  module V1\n    module Article\n      class Show \u003c Pragma::Operation::Base\n        step :find!\n        failure :handle_not_found!, fail_fast: true\n        step :authorize!\n        failure :handle_unauthorized!\n        step :respond!\n\n        def find!(params:, **options)\n          options['model'] = ::Article.find_by(id: params[:id])\n        end\n\n        def handle_not_found!(options)\n          options['result.response'] = Pragma::Operation::Response::NotFound.new\n          false\n        end\n\n        def authorize!(options)\n          options['result.authorization'] = options['model'].published? || \n            options['model'].author == options['current_user']\n        end\n\n        def handle_unauthorized!(options)\n          options['result.response'] = Pragma::Operation::Response::Forbidden.new(\n            entity: Error.new(\n              error_type: :forbidden,\n              error_message: 'You can only access an article if published or authored by you.'\n            )\n          )\n        end\n  \n        def respond!(options)\n          options['result.response'] = Pragma::Operation::Response::Ok.new(\n            entity: options['model'].as_json\n          )\n        end\n      end\n    end\n  end\nend\n```\n\nYes, I know. This does not make any sense yet. Before continuing, I encourage you to read (and\nunderstand!) the documentation of [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/index.html).\nPragma::Operation is simply an extension of its TRB counterpart. For the rest of this guide, we will\nassume you have a good understanding of TRB concepts like flow control and macros.\n\n### Response basics\n\nThe only requirement for a Pragma operation is that it sets a `result.response` key in the options\nhash by the end of its execution. This is a `Pragma::Operation::Response` object that will be used\nby [pragma-rails](https://github.com/pragmarb/pragma-rails) or another integration to respond with\nthe proper HTTP information.\n\nResponses have, just as you'd expect, a status, headers and body. You can manipulate them by using\nthe `status`, `headers` and `entity` parameters of the initializer:\n\n```ruby\nresponse = Pragma::Operation::Response.new(\n  status: 201,\n  headers: {\n    'X-Api-Custom' =\u003e 'Value'\n  },\n  entity: my_model\n)\n```\n\nYou can also set these properties through their accessors after instantiating the response:\n\n```ruby\n# You can set the status as a symbol:\nresponse.status = :created\n\n# You can set it as an HTTP status code:\nresponse.status = 201\n\n# You can manipulate headers:\nresponse.headers['X-Api-Custom'] = 'Value'\n\n# You can manipulate the entity:\nresponse.entity = my_model\n\n# The entity can be any object responding to #to_json:\nresponse.entity = {\n  foo: :bar\n}\n```\n\n### Decorating entities\n\nThe response class also has support for Pragma [decorators](https://github.com/pragmarb/pragma-decorator).\n\nIf you use decorators, you can set a decorator as the entity or you can use the `#decorate_with`\nconvenience method to decorate the existing entity:\n\n```ruby\nresponse.entity = ArticleDecorator.new(article)\n\n# This is equivalent to the above:\nresponse.entity = article\nresponse.decorate_with(ArticleDecorator) # returns the response itself for chaining\n```\n\n### Errors\n\nPragma::Operation ships with an `Error` data structure that's simply the recommended way to present\nyour errors. You can build your custom error by creating a new instance of it and specify a \nmachine-readable error type and a human-readable error message:\n\n```ruby\nerror = Pragma::Operation::Error.new(\n  error_type: :invalid_date,\n  error_message: 'You have specified an invalid date in your request.'\n)\n\nerror.as_json # =\u003e {:error_type=\u003e:invalid_date, :error_message=\u003e\"You have specified an invalid date in your request.\", :meta=\u003e{}}\nerror.to_json # =\u003e {\"error_type\":\"invalid_date\",\"error_message\":\"You have specified an invalid date in your request.\",\"meta\":{}} \n```\n\nDo you see that `meta` property in the JSON representation of the error? You can use it to include\nadditional metadata about the error. This is especially useful, for instance, with validation errors\nas you can include the exact fields and validation messages (which is exactly what Pragma does by\ndefault, by the way):\n\n```ruby\nerror = Pragma::Operation::Error.new(\n  error_type: :invalid_date,\n  error_message: 'You have specified an invalid date in your request.',\n  meta: {\n    expected_format: 'YYYY-MM-DD'\n  }\n)\n\nerror.as_json # =\u003e {:error_type=\u003e:invalid_date, :error_message=\u003e\"You have specified an invalid date in your request.\", :meta=\u003e{:expected_format=\u003e\"YYYY-MM-DD\"}}\nerror.to_json # =\u003e {\"error_type\":\"invalid_date\",\"error_message\":\"You have specified an invalid date in your request.\",\"meta\":{\"expected_format\":\"YYYY-MM-DD\"}}\n```\n\nIf you don't want to go with this format, you are free to implement your own error class, but it is\nnot recommended, as the [built-in macros](https://github.com/pragmarb/pragma/tree/master/lib/pragma/operation/macro) \nwill use `Pragma::Operation::Error`.\n\n### Built-in responses\n\nLast but not least, as you have seen in the example operation, Pragma provides some \n[built-in responses](https://github.com/pragmarb/pragma-operation/tree/master/lib/pragma/operation/response) \nfor common status codes and bodies. Some of these only have a status code while others (the error\nresponses) also have a default entity attached to them. For instance, you can use `Pragma::Operation::Response::Forbidden`\nwithout specifying your own error type and message:\n\n```ruby\nresponse = Pragma::Operation::Response::Forbidden.new\n\nresponse.status # =\u003e 403\nresponse.entity.to_json # =\u003e {\"error_type\":\"forbidden\",\"error_message\":\"You are not authorized to access the requested resource.\",\"meta\":{}}\n```\n\nThe built-in responses are not meant to be comprehensive and you will most likely have to implement\nyour own. If you write some that you think could be useful, feel free to open a PR!\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma-operation.\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-operation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpragmarb%2Fpragma-operation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmarb%2Fpragma-operation/lists"}