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.
  • 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.

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, and that they can edit/delete their own articles only.

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 contracts are empty, which means they will just inherit the behavior of Base.

Operations

The last step in creating our resource is defining some operations for it. Luckily, the pragma gem comes with default CRUD operations that we can simply inherit from to get all the functionality we need. In order to implement the Article API endpoints, here's all we need to do:

# app/resources/api/v1/article/operation/index.rb
module API
  module V1
    module Article
      module Operation
        class Index < Pragma::Operation::Index
        end
      end
    end
  end
end

# app/resources/api/v1/article/operation/show.rb
module API
  module V1
    module Article
      module Operation
        class Show < Pragma::Operation::Show
        end
      end
    end
  end
end

# app/resources/api/v1/article/operation/create.rb
module API
  module V1
    module Article
      module Operation
        class Create < Pragma::Operation::Create
        end
      end
    end
  end
end

# app/resources/api/v1/article/operation/update.rb
module API
  module V1
    module Article
      module Operation
        class Update < Pragma::Operation::Update
        end
      end
    end
  end
end

# app/resources/api/v1/article/operation/destroy.rb
module API
  module V1
    module Article
      module Operation
        class Destroy < Pragma::Operation::Destroy
        end
      end
    end
  end
end

These operations are very powerful: they can be customized to suit your needs and they can be extended with your own business/presentation logic. To find out more, we encourage you to read their source code and the rest of the guides in this wiki.

But wait, aren't we forgetting something? Our Article model has an author property which must be filled with the user creating the aticle in order for authorization to work properly. In order to do that, we can add a new step to the Create operation:

# app/resources/api/v1/article/operation/create.rb
module API
  module V1
    module Article
      module Operation
        class Create < Pragma::Operation::Create
          step :set_author!, after: 'contract.default.validate'

          def set_author!(model:, current_user:, **)
            model.author = current_user
          end
        end
      end
    end
  end
end

There is a lot going on here, but basically what we're doing is defining a new step for the operation that will run after the contract validates successfully (and before the record is saved). This step will set the model's author property to the value of current_user.

The Operation API is actually very powerful and a big shift in how developers usually reason about business logic, so you're encouraged to read more about it in the Pragma::Operation documentation and the rest of these guides.

If you're wondering how current_user is passed to the operation, just read below!

Integrating with Rails

Our resource is now ready, but how do we expose it in our Rails app and allow people to make actual HTTP requests?

In order to do that, you need to install the pragma-rails gem, which can replace pragma in your Gemfile:

# before
gem 'pragma'

# after
gem 'pragma-rails'

Once the gem is installed, all you need to do is create a controller for the API resource:

# app/controllers/api/v1/articles_controller.rb
module API
  module V1
    class ArticlesController < ApplicationController
      include Pragma::Rails::ResourceController

      before_action :authenticate_user, except: %i(index show)

      private

      def authenticate_user
        unless current_user
          head :unauthorized
          return false
        end
      end

      def current_user
        User.find_by(auth_token: request.headers['X-Auth-Token'])
      end
    end
  end
end

The Pragma::Rails::ResourceController module maps the controller's actions to the respective Pragma operations, taking care of forwarding any input from the HTTP requests and responding with the result of the operation.

Note that we're also defining some very simple (and insecure!) authentication logic for the sake of this example. Pragma assumes you have a current_user method in your controller that returns a user which will be forward to the operation, so we need to define this method in our controller. You can read more about this in the Pragma::Rails documentation.

Finally, you need to define the routes for your new controller:

# config/routes.rb
Rails.application.routes.draw do
  # ...

  namespace :api do
    namespace :v1 do
      resources :articles, only: %i(index show create update destroy)
    end
  end
end

That's it! You can now boot your Rails server and start playing with your new API!

What's next?

Congratulations! Now you have your first fully working API built with Pragma. There's a lot more ground to cover, but before continuing with the guides it's strongly recommended that you read the documentation of the individual components we have used in this guide, to get a sense of how powerful they are and how it all fits together: