README.md in pragma-operation-1.6.3 vs README.md in pragma-operation-2.0.0

- old
+ new

@@ -5,11 +5,11 @@ [![Code Climate](https://img.shields.io/codeclimate/github/pragmarb/pragma-operation.svg?maxAge=3600&style=flat-square)](https://codeclimate.com/github/pragmarb/pragma-operation) [![Coveralls](https://img.shields.io/coveralls/pragmarb/pragma-operation.svg?maxAge=3600&style=flat-square)](https://coveralls.io/github/pragmarb/pragma-operation) Operations encapsulate the business logic of your JSON API. -They are built on top of the awesome [Interactor](https://github.com/collectiveidea/interactor) gem. +They are built on top of the [Trailblazer::Operation](https://github.com/trailblazer/trailblazer-operation) gem. ## Installation Add this line to your application's Gemfile: @@ -29,11 +29,172 @@ $ gem install pragma-operation ``` ## Usage -All documentation is in the [doc](https://github.com/pragmarb/pragma-operation/tree/master/doc) -folder. +Let's build your first operation! + +```ruby +module API + module V1 + module Article + class Show < Pragma::Operation::Base + step :find! + failure :handle_not_found!, fail_fast: true + step :authorize! + failure :handle_unauthorized! + step :respond! + + def find!(params:, **options) + options['model'] = ::Article.find_by(id: params[:id]) + end + + def handle_not_found!(options) + options['result.response'] = Pragma::Operation::Response::NotFound.new + false + end + + def authorize!(options) + options['result.authorization'] = options['model'].published? || + options['model'].author == options['current_user'] + end + + def handle_unauthorized!(options) + options['result.response'] = Pragma::Operation::Response::Forbidden.new( + entity: Error.new( + error_type: :forbidden, + error_message: 'You can only access an article if published or authored by you.' + ) + ) + end + + def respond!(options) + options['result.response'] = Pragma::Operation::Response::Ok.new( + entity: options['model'].as_json + ) + end + end + end + end +end +``` + +Yes, I know. This does not make any sense yet. Before continuing, I encourage you to read (and +understand!) the documentation of [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/index.html). +Pragma::Operation is simply an extension of its TRB counterpart. For the rest of this guide, we will +assume you have a good understanding of TRB concepts like flow control and macros. + +### Response basics + +The only requirement for a Pragma operation is that it sets a `result.response` key in the options +hash by the end of its execution. This is a `Pragma::Operation::Response` object that will be used +by [pragma-rails](https://github.com/pragmarb/pragma-rails) or another integration to respond with +the proper HTTP information. + +Responses have, just as you'd expect, a status, headers and body. You can manipulate them by using +the `status`, `headers` and `entity` parameters of the initializer: + +```ruby +response = Pragma::Operation::Response.new( + status: 201, + headers: { + 'X-Api-Custom' => 'Value' + }, + entity: my_model +) +``` + +You can also set these properties through their accessors after instantiating the response: + +```ruby +# You can set the status as a symbol: +response.status = :created + +# You can set it as an HTTP status code: +response.status = 201 + +# You can manipulate headers: +response.headers['X-Api-Custom'] = 'Value' + +# You can manipulate the entity: +response.entity = my_model + +# The entity can be any object responding to #to_json: +response.entity = { + foo: :bar +} +``` + +### Decorating entities + +The response class also has support for Pragma [decorators](https://github.com/pragmarb/pragma-decorator). + +If you use decorators, you can set a decorator as the entity or you can use the `#decorate_with` +convenience method to decorate the existing entity: + +```ruby +response.entity = ArticleDecorator.new(article) + +# This is equivalent to the above: +response.entity = article +response.decorate_with(ArticleDecorator) # returns the response itself for chaining +``` + +### Errors + +Pragma::Operation ships with an `Error` data structure that's simply the recommended way to present +your errors. You can build your custom error by creating a new instance of it and specify a +machine-readable error type and a human-readable error message: + +```ruby +error = Pragma::Operation::Error.new( + error_type: :invalid_date, + error_message: 'You have specified an invalid date in your request.' +) + +error.as_json # => {:error_type=>:invalid_date, :error_message=>"You have specified an invalid date in your request.", :meta=>{}} +error.to_json # => {"error_type":"invalid_date","error_message":"You have specified an invalid date in your request.","meta":{}} +``` + +Do you see that `meta` property in the JSON representation of the error? You can use it to include +additional metadata about the error. This is especially useful, for instance, with validation errors +as you can include the exact fields and validation messages (which is exactly what Pragma does by +default, by the way): + +```ruby +error = Pragma::Operation::Error.new( + error_type: :invalid_date, + error_message: 'You have specified an invalid date in your request.', + meta: { + expected_format: 'YYYY-MM-DD' + } +) + +error.as_json # => {:error_type=>:invalid_date, :error_message=>"You have specified an invalid date in your request.", :meta=>{:expected_format=>"YYYY-MM-DD"}} +error.to_json # => {"error_type":"invalid_date","error_message":"You have specified an invalid date in your request.","meta":{"expected_format":"YYYY-MM-DD"}} +``` + +If you don't want to go with this format, you are free to implement your own error class, but it is +not recommended, as the [built-in macros](https://github.com/pragmarb/pragma/tree/master/lib/pragma/operation/macro) +will use `Pragma::Operation::Error`. + +### Built-in responses + +Last but not least, as you have seen in the example operation, Pragma provides some +[built-in responses](https://github.com/pragmarb/pragma-operation/tree/master/lib/pragma/operation/response) +for common status codes and bodies. Some of these only have a status code while others (the error +responses) also have a default entity attached to them. For instance, you can use `Pragma::Operation::Response::Forbidden` +without specifying your own error type and message: + +```ruby +response = Pragma::Operation::Response::Forbidden.new + +response.status # => 403 +response.entity.to_json # => {"error_type":"forbidden","error_message":"You are not authorized to access the requested resource.","meta":{}} +``` + +The built-in responses are not meant to be comprehensive and you will most likely have to implement +your own. If you write some that you think could be useful, feel free to open a PR! ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma-operation.