Pragma — A new way to build Rails REST APIs

Timi Ajiboye
chunks of code*
Published in
6 min readApr 29, 2017
Pragma

I did some work with Alessandro Desantis recently and he introduced me into a very different, but also very reasonable way of making JSON REST APIs with Ruby on Rails.

He did more than introduce me, however, he actually created the gems/framework/architecture that enforces (and greatly eases) this methodology. It’s called Pragma.

From it’s GitHub page, you’ll see that Pragma is somewhat tied to Trailblazer — or at least, parts of Trailblazer.

What is Trailblazer you ask?

That’s the exact same question I asked myself when I was tasked with understanding what the hell was going on.

“Trailblazer and Pragma have different (but similar) places in the Ruby world: Trailblazer is an architecture for building all kinds of web applications in an intelligent, rational way, while Pragma is an architecture for building JSON APIs. We have shamelessly taken all of the flexibility and awesomeness from the Trailblazer project and restricted it to a narrow field of work, providing tools, helpers and integrations that could never be part of Trailblazer due to their specificity.”

— Pragma README.md

Because of the above quoted paragraph, I have decided that the best way to understand what Pragma is, how it works and how to use it would be to talk about it like Trailblazer doesn’t exist. At least for starters.

And that’s exactly what I’m going to do. This part (1) is going to be a high level explanation of Pragma’s parts, so it can take you less time than it took me to understand it all. 🙂

First and foremost, Pragma’s focus (in my opinion) is separation of concerns. It’s a very modular way of building APIs. You can use some or all parts of it as you see fit.

Secondly, Pragma itself is the tape that holds four other gems together, namely:

High level overview of Pragma

Like I mentioned above, you can use any number of these pieces without hassle, but they work best together.

We’re gonna look at each part, and some snippets of code to get a clearer view of how they work. Don’t worry if you don’t understand how they all come together or the installation process, just focus on what I’m showing you. I’ll explain all the other things in due time.

Module/Folder Structure

Before we get to look at the different parts of Pragma, there’s something that’s worth a mention; how your files should be arranged within your Rails application.

Almost everything Pragma related lives in /app/resources. Within this folder, all your resources (and it’s versions) shall be found/placed.

Folder arrangement

Everything Pragma related concerning the user resource for example, will live in resources/api/v1/user.

This folder structure and nested modules are important to not get wrong, as they’re what helps Pragma automatically tie the different parts together like magic!

Other than file structure, you’ll also need to extend API::V1::ResourceController in your controllers to make this magic complete. (And set up your namespaced routes as usual)

module API
module V1
class PostsController < API::V1::ResourceController
end
end
end

As we go on, I’ll expand a bit on the specific file placement for each of the parts.

Pragma::Policy

module API
module V1
module Post
class Policy < Pragma::Policy::Base
def self.accessible_by(user:, scope:)
if user.role == :admin
scope.all
else
scope.where(user_id: user.id)
end
end

def
show?
true
end

def
create?
user.role == :admin
end

def
update?
user.role == :admin
end

def
destroy?
user.role == :admin
end
end
end
end
end

Take a look at the above code that we’ve put in /app/resources/api/v1/post/policy.rb, (also pay attention to the nested modules, they correspond to the folder structure)

All the “instance” methods (show?, create?, update? and destroy?) return a boolean; true or false. You can override this methods and return whatever boolean you want to allow/prevent a user from CRUD-ing a resource in any manner or form you like.

In the example above, every user would be allowed to request the show action for the Post resource. But only users with role == :admin will be able to create, update and destroy.

The class method self.accessible_by(user:, scope:), handles which records a certain user can actually access. This is why the scope is passed as the second argument. As you can see in the snippet above, only admins can access all Posts but other people can only access Posts they own.

Pretty straightforward isn’t it?

Pragma::Contract

This is used validate & coerce incoming request parameters so that they are exactly what the API expects them to be. Contracts (Form Objects) generally are not at all a new concept, and you can gain a lot by doing some research on your own about the pattern.

Though, as you go deeper into the use of Pragma::Contract, you’ll find it’s use can be very broad and in my opinion, it’s the most powerful aspect of Pragma. But so as not to confuse you, we’ll stick to validation and treat the rest in a separate article.

module API
module V1
module Post
module Contract
class Create < Pragma::Contract::Base
property :title, type: coercible(:string)
property :body, type: coercible(:string)
property :published_at, type: form(:date)
end
end
end
end
end

This is an example of a contract. You specify properties that you expect to be within the request body and what they should be coerced to. This particular contract will be used to validate the create action (as you can see from it’s class name). You can have

You can add validation to this, as such:

module API
module V1
module Post
module Contract
class Create < Pragma::Contract::Base
property :title
property :type
property :text

validation do
required(:title).filled
required(:type).value(included_in?: Post::POST_TYPES)
required(:text).value(:str?)
end
end
end
end
end
end

Pragma::Contract’s validation & coercion is based on dry-validation, so you should definitely read it’s documentation to discover all the cool validation things you can do with Pragma::Contract. I spent a LOT, a LOT of time reading that page to really understand what was going on and what I could do with it.

By default, once the incoming request body doesn’t match what the contract expects, your API responds with a 422 and a body containing details about which contracts weren’t respected.

Furthermore, you can customize the messages it responds with and even create your own custom validation rules.

The above contract would live in /app/resources/api/v1/post/contract/create.rb and the contract for say, update would be /app/resources/api/v1/post/contract/update.rb.

Pragma::Operation

This is where the actual persistence or functionality (business logic) occurs. Pragma::Operation comes with built in default CRUD operations.

In /app/resources/api/v1/post/operation/create.rb, all you’ll need to do is:

module API
module V1
module Post
module Operation
class Create < Pragma::Operation::Create

end
end
end
end
end

By extending Pragma::Operation::Create (or Pragma::Operation::Update, Pragma::Operation::Index, and so on), you automatically get the requisite default behaviour of those operations which saves a lot of time.

But if you want something custom, well, this is the place to do that. You can specify which contract should be validated and what should happen if the contract is respected or not, and determine what should the API should respond_with. In this case, we extend Pragma::Operation::Create.

We’ll go deeper into Pragma::Operation in a separate article dedicated to it.

Pragma::Decorator

If you’ve ever used ActiveModel::Serializer, you’ll sort of already understand what Pragma::Decorator does. It’s like the view layer for your JSON API.

module API
module V1
module Post
module Decorator
class Resource < Pragma::Decorator::Base
feature Pragma::Decorator::Association
property :title
property :type
property :text
belongs_to :user, expandable: true
end
end
end
end
end

It works with associations and you can have (nested) expandable objects in your response.

Summary

I think from the above, (and the diagram I drew) you should have gotten a very good idea of how Pragma works and what it does. You should also immediately see how it brings order to the codebase and the benefits of it’s separation of concerns.

--

--

Timi Ajiboye
chunks of code*

I make stuff, mostly things that work on computers. Building Gandalf.