Skip to content

Your first Pragma API

Alessandro Desantis edited this page Jul 28, 2018 · 7 revisions

Now that you understand the concepts behind Pragma, it's time to start building something!

In this guide, we are going to implement a basic API with Pragma for managing articles in a blog. Here are our business requirements:

  • An article has an author, title, body and a "published" boolean flag.
  • The blog has multiple users.
  • Users can be writers or admins.
  • All users can view all published articles.
  • Writers can see articles they have authored, even if not published.
  • Writers can only edit and delete their own articles.
  • Admins can see all articles.
  • Admins can edit and delete all articles.

We will first implement the API resource with plain Pragma and then see how we can expose it in a Rails application with Pragma::Rails. For the purpose of this guide, we will assume you already have a working Rails application. If you don't, you can use pragma-rails-starter.

Installation

The first step is installing Pragma in your application. Luckily, this is pretty simple! Just add the following to your Gemfile:

gem 'pragma'

This will install the main pragma gem, which wraps the core components and provides default CRUD operations.

The gem doesn't require any configuration, so you're good to go!

File and class structure

Now, we are going to create the directory structure for our API resource.

In a Rails application, Pragma resources are usually stored in app/resources, but you can put them wherever you want as long as you can access them from a controller.

In general, Pragma doesn't care about the directory structure of your code, but it is very sensitive to the module/class hierarchy: the default CRUD operations assume a very specific set of module/class names and will not work if you deviate from the recommended structure (unless you reconfigure them, of course).

For this guide, let's go with the default directory and file structure, which is the following:

app/resources/api/v1/article
├── contract
│   ├── base.rb
│   ├── create.rb
│   └── update.rb
├── decorator
│   ├── collection.rb
│   └── instance.rb
├── operation
│   ├── create.rb
│   ├── destroy.rb
│   ├── index.rb
│   ├── show.rb
│   └── update.rb
└── policy.rb

As you can see, all resources are versioned, with the first version of your API having the API::V1 namespace. How you manage versioning is up to you, but usually you should only bump your version number when you introduce breaking changes to a published and adopted API. There are alternative approaches (see Pragma::Migration), but for now we're going to stick to the default scheme.

For the time being you can leave all files blank, we're going to fill them later.

Policy

The first component we're going to configure for the Article resource is the policy. As mentioned in the introduction, policies authorize operations that users want to perform on your API resources.

As far as authorization goes, we said that all users can see all articles, writers can edit/delete their own articles only, and admins have full control over all articles. We're going to assume your app has a User model with a role attribute which is either writer or admin.

Let's open our policy and enter the following (no worries, we're going to explain all of this):

# app/resources/api/v1/article/policy.rb
module API
  module V1
    module Article
      class Policy < Pragma::Policy::Base
        class Scope < Pragma::Policy::Scope
          def resolve
            filtered_scope = scope.where(published: true)
            filtered_scope = filtered_scope.or(scope.where(author: user)) if user
            filtered_scope
          end
        end

        def show?
          record.published? || record.author == user
        end

        def create?
          true
        end

        def update?
          record.author == user
        end

        def destroy?
          record.author == user
        end
      end
    end
  end
end

As you can see, the first thing we do is define an API::V1::Article::Policy::Scope class that implements a single resolve method. The scope is a special class in our policy whose job is to filter the collection of records returned by the Index operation. When a user requests the list of articles, Pragma will first load all the articles in the list, then pass that collection to the scope of your policy to retrieve only the articles accessible by the current user. Our scope in this case only returns articles that are published or belong to the current user, if the user is authenticated.

After the scope, we are defining the policies for authorizing the Show, Create, Update and Destroy operations. These should be pretty straightforward, so we won't go into the details.

You might be wondering why the create? policy simply returns true without checking whether the user is authenticated. This is because policies are only concerned with authorization, not authentication. In other words, you should never have to return false early in a policy when user is nil, since that's an operation's concern.

If this looks very similar to Pundit, it's because we used it as the inspiration for the syntax and functionality of Pragma::Policy. In fact, until not long ago, Pragma::Policy used to be just an extension of Pundit!

Decorators

Now that we have our policy, let's take care of our decorators.

As you can see from the file structure, each resource has two decorators by default: a collection decorator and an instance decorator. As you can imagine, the instance decorator converts a single article to JSON, while the collection decorator works on collections. This distinction is necessary because collections will usually include additional metadata about pagination and so on.

This is what our instance decorator for the Article resource might look like:

# app/resources/api/v1/article/decorator/instance.rb
module API
  module V1
    module Article
      module Decorator
        class Instance < Pragma::Decorator::Base
          include Pragma::Decorator::Type

          property :id
          property :title
          property :body
          property :published
        end
      end
    end
  end
end

This should be pretty simple to understand: the Pragma::Decorator::Type module is a small mixin that will add a type property to your resource's JSON representation. This property contains a machine-readable name for the resource type, so that clients can easily understand what kind of resource they're dealing with. In this case, type will be article.

The property definitions simply tell the decorator that the id, title, body and published property should all be exposed to the API clients.

Now, let's define our collection decorator:

# app/resources/api/v1/article/decorator/collection.rb
module API
  module V1
    module Article
      module Decorator
        class Collection < Pragma::Decorator::Base
          include Pragma::Decorator::Type
          include Pragma::Decorator::Collection
          include Pragma::Decorator::Pagination

          decorate_with Instance
        end
      end
    end
  end
end

There's a bit more stuff going on here.

First of all, we're requiring the same Type module as in the instance decorator, but this time type will be list (a language-agnostic version of array).

Then, we're also including Collection and Pagination, which will, respectively, wrap the collection's entries in a data property and add pagination metadata at the root level.

Finally, we're telling the decorator that our instance decorator is called Instance and should be used to decorate our collection's entries.

Contracts

Let's move on to the contracts now. As you see, there are three contracts in your usual resource: the Create and Update contracts are used in the respective operations and they both inherit from the Base contract which contains shared properties and validation logic.

Our Article resource is pretty simple, so we're going to define all three contracts together and then explain them briefly:

# app/resources/api/v1/article/contract/base.rb
module API
  module V1
    module Article
      module Contract
        class Base < Pragma::Contract::Base
          property :title, type: coercible(:string)
          property :body, type: coercible(:string)
          property :published, type: form(:bool)

          validation do
            required(:title).filled
            required(:body).filled
          end
        end
      end
    end
  end
end

# app/resources/api/v1/article/contract/create.rb
module API
  module V1
    module Article
      module Contract
        class Create < Base
        end
      end
    end
  end
end

# app/resources/api/v1/article/contract/update.rb
module API
  module V1
    module Article
      module Contract
        class Update < Base
        end
      end
    end
  end
end

THe Base contract only defines three properties: title, body and published. The type option defines the type of the property for coercion. You can see that title and body are strings while published is a boolean. There are many different types, you can see them all in the Dry::Types documentation (Pragma::Contract uses Dry::Types under the hood).

The validation block defines the validation rules that will be applied to the properties. In this case, we're expecting title and body to be filled. This syntax comes from Dry::Validation and is very powerful, allowing you to define complex validation logic in a highly maintainable way.

The Create and Update contract are empty, which means they will just inherit the behavior of Base.

Operations

Integrating with Rails

Additional documentation

Clone this wiki locally