For a recent project I had to build up a backend as an API using Rails. I’ve learned a lot doing it, but coming up with it has been a process of reading several pages, tutorials, blogs, official documentation, and more… So I’ve decided to summarize what I’ve done for my project in an attempt to documentate the whole process for future references and let others know from my experience, and why not? Get a feedback.
The problem
The API comes as a proposal for the final project of Software Engineering. This is about a system able to allow real time tracking to implement an Agri-Food Traceability Application. The idea is to gather as much as possible information about agri-food products from their inception to the hands of the final consumer.
The dynamic is as follows, a producer has a series of products they made. During the process everything is logged into a CropLog
, thus allowing to know how the making process was. The producer sells their goods to warehouses that buy from them and/or other producers. Then a warehouse can sell to smaller warehouses, until the product reaches the final user hands.
The idea is to offer the final user all the information about the product through a simple website.
Preliminars
For starters these are the versions of ruby and Rails at the time of writing this post:
1$ ruby -v && rails -v
2ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]
3rails 5.1.0.rc1
Then I’ve started my API generating a new Rails app as an api (--api
) without default testing (-T
). As you can see the DB is PostgreSQL:
1$ rails new shimizusuntou -d postgresql --api -T
About the gems
Now, let’s talk about the gems I’ve installed to get the project working:
- rack-cors is a Rack Middleware for handling Cross-Origin Resource Sharing (CORS), which makes cross-origin AJAX possible.
- rack-attack is a Rack middleware for blocking & throttling
- active_model_serializers is a gem that helps you define the final structure of your json responses.
- has-scope is a Map incoming controller parameters to named scopes in your resources
- rqrcode_png Uses rQRCode and chunky_png to produce .png images of QR codes in pure Ruby
- apipie-Rails is a Ruby on Rails API documentation tool.
- rspec-Rails is a testing framework for Rails 3.x, 4.x and 5.0.
- hirb is A mini view framework for console/irb that’s easy to use, even while under its influence. Console goodies include a no-wrap table, auto-pager, tree and menu.
- factory_girl_rails is a fixtures replacement with a straightforward definition syntax.
- shoulda-matchers is a Collection of testing matchers extracted from Shoulda http://matchers.shoulda.io
- faker is A library for generating fake data such as names, addresses, and phone numbers.
- database_cleaner provides Strategies for cleaning databases in Ruby. Can be used to ensure a clean state for testing.
Initial configurations
Now, let’s review how this gems are setup in the Gemfile:
1source 'https://rubygems.org'
2
3git_source(:github) do |repo_name|
4 repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 "https://github.com/#{repo_name}.git"
6end
7
8
9gem 'Rails', '~> 5.1.0'
10gem 'pg', '~> 0.18'
11gem 'puma', '~> 3.7'
12# Use ActiveModel has_secure_password
13# gem 'bcrypt', '~> 3.1.7'
14gem 'rack-cors'
15gem 'rack-attack'
16gem 'active_model_serializers'
17gem 'will_paginate', '~> 3.1.0'
18gem 'has_scope'
19gem 'rqrcode_png'
20
21gem 'apipie-Rails', '~> 0.5.0'
22
23group :development, :test do
24 # Call 'byebug' anywhere in the code to stop execution and get a debugger console
25 gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
26 gem 'rspec-Rails', '~> 3.5'
27 gem 'hirb'
28end
29
30group :test do
31 gem 'factory_girl_rails', '~> 4.0'
32 gem 'shoulda-matchers', '~> 3.1'
33 gem 'faker'
34 gem 'database_cleaner'
35end
36
37group :development do
38 gem 'listen', '>= 3.0.5', '< 3.2'
39 gem 'spring'
40 gem 'spring-watcher-listen', '~> 2.0.0'
41end
42
43gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
The first step we have take is to initialize the rspec configuration, generating three files .rspec
, spec/spec_helper.rb
, spec/rails_helper.rb
1$ rails generate rspec:install
Now, in order to have our rspec tests working we have to provide a way to build dummy objects right away from fake data, but a kind of data that represents very well how our real object will look like. For that we have to setup all the support for factories. First we have to create the folder where our factories will live.
1$ mkdir spec/factories
In order to have rspec understanding how to use the factories we have to modify the spec/rails_helper.rb
file.
1require 'database_cleaner'
2
3Shoulda::Matchers.configure do |config|
4 config.integrate do |with|
5 with.test_framework :rspec
6 with.library :Rails
7 end
8end
9
10RSpec.configure do |config|
11 config.include FactoryGirl::Syntax::Methods
12 config.include RequestSpecHelper, type: :request
13 config.before(:suite) do
14 DatabaseCleaner.clean_with(:truncation)
15 DatabaseCleaner.strategy = :transaction
16 end
17 config.around(:each) do |example|
18 DatabaseCleaner.cleaning do
19 example.run
20 end
21 end
22end
That’s the setup to use rspec and how the testing suite should interact with the testing database.
The model
First I have to talk about my idea of what the model is in the DB:
This is how the actual model is visualized in Rails:
First I want to talk about the simple models:
Product
: information about a product.Container
: information about the containers for a product (basket, pack, etc).Crop
: general information about a crop, has a relationship toProduct
,Container
.CropLog
: details for aCrop
.Route
: binding between twoPlace
s.RouteLog
: details for a specificRoute
, where the humidity, temperature, latitude and longitude are registered
Now it’s time to talk about the more complex models:
First, I have the Package
model. This one tells how many Container
s of certain Product
are shipped from one Place
to another through a Route
. A Package
can have another Package
as parent meaning that the original one has been divided into smaller amounts.
As it can be seen from both pictures there is a model User
that is meant to hold users information, however the User
model is never used as it is but I distinguish between two types of users: Producer
and Warehouse
. Thus, making an inheritance relationship between them.
Finally, the most complex model is Place
. They belong to, either a Produce
or a Warehouse
, so the model has to be defined as a polymorphic relationship to those. They also have many origins or destination in a Route
.
I don’t pretend to show all the details of the models, but I’ll point the key parts of each model so a better understanding of what I’ve explained above can be reached. By the time I’m writing this article, I might have modified the code, so all the source of truth should be the official repository at GitHub.
In the next article for this series, I’ll talk about tests and further configurations. Stay tuned!