Building a Rails API with Pragma (Part 1)

Recently I was tasked with leveling up on a not so popular gem, or let me say a meta-gem called Pragma. Calling Pragma a gem would be an understatement as it does more than just a gem, and depends on several other gems. Pragma can be used to build a complete API within a very short time, it’s described on its documentation as:

An expressive, opinionated ecosystem for building beautiful RESTful APIs with Ruby. It creates a solid foundation for your API so that you can focus on the stuff that matters.

In this tutorial, I’m going to show you how you can get started with Pragma. I believe this tutorial will save you a lot of headache which I had while trying to get things done with Pragma, as I couldn’t get enough resources on this gem apart from its simple and basic documentation.

First and foremost, I would like you to go through this tutorial by Timi Ajiboye. It’s the fundamental to really understand what we will be dealing with here.

We are going to be building a very simple application called Mini-Mart.This is going to be a simple e-commerce API that will have product, user, order and review resources. Kindly note that we may not complete the app in this post, as I always try to avoid my posts from getting too long, therefore this may get split into parts.

To create an app with pragma, we just need to clone pragma’s starter repository.

git clone https://github.com/pragmarb/pragma-rails-starter.git mini-mart

This would create a Pragma app ready to go into the mini-mart directory. cd mini-mart to enter the app’s directory and take a look at the files generated. If you go through the README file, you would see that we have to run the following commands to setup our app and the database.

cp config/database.example.yml config/database.yml
cp config/application.example.yml config/application.yml

These would automatically copy the contents from the examples configuration to our application and database configuration files. Note that you might have to tweak parts of these files so as to fit your development setting, especially the database.yml file. I changed the username, password, and database for both the development and test environments in my database.yml file to look exactly like this:

default: &default
adapter: postgresql
pool: 5
timeout: 5000
development:
<<: *default
host: localhost
username: ''
password: ''
database: mini_mart_development
test:
<<: *default
host: localhost
username: ''
password: ''
database: mini_mart_test
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>

As you can see, my postgres has empty as both username and password, kindly enter yours accordingly. I have also decided to rename the databases to mini_mart_development and mini_mart_test instead of api_development and api_test respectively.

Now we can safely run rake db:setup to have our database created. Let’s first create the products table, we need to generate a Product model for that.

rails g model Product name:string description:text number_in_store:integer
rails db:migrate

Running these two commands would create the products table in our database, having the columns id, name, description, number_in_store, created_at and updated_at. We should not worry about having any associated tables for now, as we can easily create a migration for that when the need arises.

If you’ve gone through the tutorial I mentioned earlier, you should now know that Pragma pulls in the following pieces:

To reiterate what you should have read in the tutorial; Decorator handles your json presentation, Contract handles validation, Policy deals with authorization while Operation handles the main business logic. It’s also worth mentioning that Pragma was built on Trailblazer, and you should have also seen the difference between Pragma and Trailblazer in the first tutorial. So if you want to dig deeper on any of the parts mentioned above, it would make sense to check it out on Trailblazer.

Let’s generate the resource for the Product model we created.

rails g pragma:resource product

This command can be seen as the rails g scaffold for Pragma. It will automatically generate a product folder containing all the necessary files, and place it inside the resources folder. It also creates the necessary routes and spec files as well. Your folder structure should now look similar to this:

The product resource generated inside the app/resources directory

Right now we already have all endpoints for product in place, we just need to do some tweaking to make it work effectively. Run rails routes to see all your present available routes, and try and access some of them with postman, you would get some funny responses.

Let’s start by creating some real products, to do that, we need to put some validation in place. Open the base.rb file inside the contract folder and let’s put some validation in there.

# app/resources/api/product/contract/base.rb
module API
module V1
module Product
module Contract
class Base < Pragma::Contract::Base
property :name
property :description
property :number_in_store
          validation do
required(:name).filled
required(:description).filled
required(:number).filled
end
end
end
end
end
end

The property method allows us to specify different columns we would like to manipulate, and the validation takes in a block which allows us to specify the kind of validation we want to put on each attribute. Since Pragma::Contract was built on Reform, you are strongly advised to check its documentation for all the available methods. This basic validation we’ve written will make sure that we can’t create a product without specifying all the three attributes.

Now let’s move to the decorator, there are two files inside this folder; collection.rb and instance.rb. The instance.rb file handles presentation for a single record while collection.rb handles for a collection of records. Let’s put the following in the instance.rb.

# app/resources/api/product/decorator/instance.rb
module API
module V1
module Product
module Decorator
class Instance < Pragma::Decorator::Base
feature Pragma::Decorator::Type
feature Pragma::Decorator::Timestamp
          property :id
property :name
property :description
property :number
          timestamp :created_at
timestamp :updated_at
end
end
end
end
end

Here we are featuring the timestamp helper from Pragma::Decorator, which helps us to convert datetime object of created_at and updated_at to UNIX timestamp which is the preferred way of presenting times in an API. We also use property to list the attributes we would like to display in the json response. You can dig deeper on this through the Pragma::Decorator’s README.

Don’t forget that I said instance.rb is for presenting a single record, but it is referenced by default from the collection.rb, so you may not need to edit the collection.rb. Though you can open the file to see what’s going on there.

At this point we should be able to create a new product. Go to postman and send a POST request to this route http://localhost:3000/api/v1/products/ with the necessary request body. You should obviously get a response message that says

You are not authorized to access the requested resource.

This is because we’ve not authorized any user to be able to create product. So open the policy.rb file and change the create? method to the following:

# app/resources/api/product/policy.rb
def create?
true
end

This means we have given access to any user to create product. Test with postman again and everything should work fine. Send more requests so that we can have more products in our database.

Let’s assume that we only want to have products of unique names on our products table. That is. we do not want to have multiple products having the same name. In ActiveModel, simply specifying unique: true on the attribute name would solve this problem for us. But here in Pragma::Contract, it requires more codes and I have not been able to get that to work. My work around for now, is to specify a step in the create operation that checks the records against the new one, and write a response message into the response object.

Inside the create.rb file of operation, let’s put the following:

# app/resources/api/product/operation/create.rb
module API
module V1
module Product
module Operation
class Create < Pragma::Operation::Create
step :unique?, before: 'persist.save'
failure :log_error!
          def unique?(options, params:, **)
::Product.find_by(name: params["name"]).nil?
end
          def log_error!(options)
options['result.response'] =
Pragma::Operation::Response.new( status: 409,
entity: {
message: "Record already exists"
}
)
end
end
end
end
end
end

We have basically two methods inside this class. The unique? method that checks our products table against the new record and then return true or false. The log_error! method that adds our error message into the result.response object, instantiating the Response class from Pragma::Operation makes that possible. Kindly note that every method in operation files takes options as parameter, and maybe with some other optional parameters. The options is so important as it contains some keys as related to every request being made.

Pragma::Operation provides us with some methods such as step, failure and success that we can use to control the execution of the methods written. This is another beautiful part of Pragma that allows us to control codes’ execution without using If-condition. We specified the unique? step, and also specified a before-hook that this should be executed before saving the record into the database. What the log_error! failure means is that once the unique? step fails, the execution should come to log_error!. If step had been used for this, the execution won’t bother coming to log_error! if unique? fails.

Now try sending a request to create a new product using an already existing name, and you should get an appropriate error message. I will also strongly advise that you check documentation on Pragma::Operation and that of Trailblazer to have a better understanding, as this is the heart of Pragma, and you must know your way around it.

Let’s now grant access to get all the products in our database, open policy.rb and change the resolve method inside the Scope class to the following:

def resolve
scope.all
end

This gives us access to all the product records in our application. If you send a GET request to http://localhost:3000/api/v1/products, you should have a response containing all your products.

Also if you want to get a particular product, sending a GET request to http://localhost:3000/api/v1/products/id_of_the_product will return the product, but we will also have to grant access for this by specifying true in the show? method inside policy.rb.

def show?
true
end

You should have gotten the flow by now, if you want to be able to update and delete a product, you will also have to tweak both update and destroy methods to return true respectively.

Looking at what we have right now, the way we have exposed our app by specifying true as return values of all our authorization methods is not actually ideal. We will need to protect some parts by the time we have the user resource and put authentication in place, which we are going to be doing in the next part of this tutorial.

If you have any suggestion/comment /question as regards this tutorial, kindly post as comment below.