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.