How to build a Ruby open-source application

Lucas Hourquebie
Unagi
Published in
8 min readNov 10, 2021

In this article, I will tell you how we’ve adapted a Ruby on Rails application in order to share it as free software with the community, along with all the decisions and changes we considered that could be helpful for your project, whether it’s open-source or not.

Illustration by Victoria Chepkasova from Ouch!

About the project

AFIP is the Argentinian entity that regulates the national collection and billing. Companies that want to have an internal billing system can use their web services. The downside is that these WSs are often confusing because they’re not very well documented. For this reason, we created afip-invoices, an API on Ruby on Rails that any developer can integrate with the entity in a friendlier way.

Rubocop: a matter of style

When it comes to sharing code with the community, and even within an organization, it’s important to use a common language when programming.

But if we all think and program differently, why do we have to write in the same way? Well, we don’t really have to write in the same way but rather follow a certain set of rules to maintain harmony in the way we code, always looking for expressiveness and maintainability.

This is where style guides come in. Particularly for Ruby, we have a very comprehensive style guide, which is widely used in the community. A style guide is a set of rules used to write code.

Fortunately for Ruby’s world, there’s RuboCop. RuboCop is a gem that analyzes our code and marks offenses based on whether the style guide determines it’s correct or not, such as the length of the methods, the use of conditionals, the complexity of the branches, and more. Since we don’t all work in the same way, RuboCop allows us to adjust the rules through a simple configuration file, specifying how strict they can be, or even if we want to turn them off for some files or blocks of code.

As an example, this is the configuration file we wrote for our API. It simply overwrites those rules that we want to modify.

Secrets v. environment variables

An important aspect of a project is the organization of confidential or environmental values. Particularly in the case of this API, which has administrator access by token, it was important to store this encrypted value. In addition, several values depend on the environment, so for example in a local or staging environment the AFIP test web services are consumed, but in production, AFIP’s own production environments are used.

Originally, this project used to have credential management by environment using the functionalities that were implemented by default in Rails (previously called secrets, and now credentials). The problem with this approach is that we would have to share the master file to decrypt, as Rails indicates in its documentation, or indicate which credentials to store after creating such a file.

While the latter is not wrong, we prefer to move to a solution based on environment variables. Thanks to this approach and using the Figaro gem, we defined a sample file indicating which environment variables should be set. In this way, each developer only needs to define their own configuration file based on their environment.

Deployment with Capistrano

At Unagi, we use Capistrano very frequently, a gem that automates all the deployment and connection processes with different server environments. Also, this is one of the most used gems when it comes to this type of task for Ruby on Rails, so we decided to include it as part of the repository.

To achieve this, we connected Capistrano with Figaro, so the necessary configuration for the connection with the server is delegated to the developer through environment variables, as described in the project’s README.

Therefore, we decided to configure two instances in a way that is flexible enough for the system to be launched in both staging and production environments. The Capistrano deployment configuration can be found in this file, while the parameters of each environment are located in this directory. Of course, it’s not necessary to use them both, but if you want to do so, you could add as many environments as you need.

Regarding the app server, we opted for the most recent version of Puma at the time of implementation, which is 5.4.0. Unlike previous versions that we had been using, this one uses systemd as support for managing the server daemon, so we had to adapt our deployment processes to support this change.

Postman and the wiki as documentation sources

The documentation hosting was a matter of discussion during the planning stage prior to the release of the open-source version of our API. After analyzing some alternatives, we decided to use Postman.

Postman is a tool that allows you to record, collect and execute different types of HTTP requests. It is widely used by the community, and you can easily save and share collections. At the same time, it has capabilities to change and manage environment variables, which is very useful to parameterize information such as the host or the API access token, using the same endpoints that we have stored.

Execution of HTTP request from a collection in Postman.

Also, Postman offers a graphical interface called Documenter that organizes the different endpoints into categories, and it allows storing responses for each of them. This is useful to show the different types of responses you can get. In our case, we share in this link a web version of the collection. We’ve also attached it as a part of the repository in this directory (along with an example of the environment variables to use in Postman).

In addition, we also decided to document some of the most important API workflows using the same wiki as the repository. We thought that the combination between Postman and the GitHub wiki is good enough to explain how to use the API.

Forewarned is forearmed: contribution guide and code of conduct

Like any free software project, we leave the door open for anyone to collaborate. And as any collective process, rules are necessary so that every contribution is handled within a context of order, respect, and commitment.

That is why we decided to write a contribution guide and follow a code of conduct, which was adopted from Contributor Covenant to implement our expected behavior as collaborators in the project.

Also, we created some GitHub issue templates to cover most common cases:

  • Request for new functionality.
  • Bug report.
  • Documentation improvement.

Democratizing the start-up with Docker

We decided to dockerize the application understanding that it’s the easiest way for developers to deploy the application locally without having to install the dependencies, the database engine and to avoid configurations. In addition to defining the Dockerfile, we also wrote the docker-compse.yml to run the application, the database, and Redis for cache handling.

The process only requires the creation of a file for the environment variables, which is very similar to the one we added with Figaro. The whole process is explained in the README.

We use the image ruby: 2.7.4-slim-buster from the official Ruby repositories. We chose the slim version because it contains the minimum packages necessary to run Ruby, which makes it very light. If you want to use Docker in a production environment, it may be necessary to use a full version. On the other hand, we opted for buster because it’s the stable Debian release.

Other advantages of using Docker are:

  • Portability: we avoid problems when running our application on different operating systems or platforms.
  • Efficiency: building an application with Docker is very fast because of how the containers are handled.
  • Isolation: an application that runs on Docker will only use the resources assigned to it.
  • Scalability: it’s very simple to run multiple instances of the application with Docker.

Testing, testing… one, two, three

Our application has high levels of testing and code coverage, and we want to keep it this way over time. That is why we found it necessary to automate the process of inspecting the code for offenses, as well as the execution of the testing suite.This is why we used GitHub Actions, the GitHub service that allows us to describe a workflow to be executed in the event of certain happenings. In our case, we automated the following tasks:

  • Execution of RSpec test suite.
  • Code inspection with Rubocop.
  • Generation of code coverage report based on the result of the testing suite.

The workflow was configured to be executed whenever a pull request is opened, when reviews of pull requests are made, and when a push is detected in the main branch of the repository, which is main.

As for the feedback added to the repository, we configure the README to read these values and show the status and the coverage percentage.

Status badges in repository README on GitHub.

Writing a README thoroughly

Last but not least, we created a README taking into account all the considerations that a developer might need.

On the one hand, we specified the main technologies and the purpose of the project, in addition to adding the corresponding links to the wiki and the information regarding contribution.

On the other hand, we’ve indicated the system start-up guide for Docker (and without it), as well as the aspects to take into account during configuration, such as environment variables. We’ve also added rake tasks to configure certain values in it, which are also documented in this section.

Continuing with the README, we also described the deployment process, along with all the environment variables that need to be configured to use up to two server environments: staging and production. In addition, we described Capistrano’s commands to run an initial deployment and the subsequent ones.

Finally, we briefly explain how Postman collections work and how to import them.

We’ve attached some flow charts explaining some of the most complex processes of the system, in order to give context to those who want to collaborate with the maintenance and improvement of the operating logic.

At Unagi, we’ve been making small contributions to the open-source community while consuming many existing projects. However, it wasn’t until now that we met our goal of publishing our first open-source development in Ruby.

As a team, we are very proud to share this project with the community, since we believe it’s a contribution that many companies will be able to take advantage of in Argentina. At the same time, we hope that the community will actively participate in its development, collaborating both in the implementation of new functionalities, as well as reporting errors and improvements in the documentation.

We believe that this software covers a gap in regards to a complete application that solves the integration with the Argentinian national billing system, and is developed in a highly expressive language that is easily usable by anyone: Ruby.

This is simply the first of many projects in which Unagi will collaborate to promote the free and collaborative use of software in the service of the community🙌. Enjoy it!

--

--

Lucas Hourquebie
Unagi
Writer for

Software engineer, Jedi Knight and Pokémon trainer. He/him.