Rewriting to Haskell–Standing on the shoulders of Rails

Posted on March 30, 2020 by Riccardo.

Rewriting to Haskell (Series)

Rewriting to Haskell–Intro Rewriting to Haskell–Project Setup Rewriting to Haskell–Deployment Rewriting to Haskell–Automatic Formatting Rewriting to Haskell–Configuration Rewriting to Haskell–Standing on the shoulders of Rails Rewriting to Haskell–Making GHC More Nitpicky Rewriting to Haskell–Testing Rewriting to Haskell–Linting Rewriting to Haskell–Parsing Query Params Rewriting to Haskell–Parsing Query Params, Again Rewriting to Haskell–Errors

In the intro we mentioned we wanted to work in “small valuable iterations”. In fact, we know from experience scope creep is a thing and we want to keep our laser focus on the goal. Well, with the exception of the usual Oxford Discussion™ with Alex on functional code design.

When rewriting part of a mature codebase, a good way of breaking down work is to reuse what’s already there. Turns out that with some outside-the-box thinking, we could make Rails our handrail.

We decided to start by rewriting one endpoint, the search one. As a matter of fact, in Stream, posts and comments can be found by submitting a query to the backend.

Rails takes care of authentication and, as said above, we wanted to keep the scope of the iteration small. Thus, we decided to leave it be and just delegate to Servant the search:

diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb
     class SearchController< Api::V1::ApiController
       def index
-        render json: posts, root: 'posts', status: :ok
+        q = { query: query, channel: channel, quantity: quantity, last_id: last_id, comments: comments }.compact
+        response = self.class.get("http://localhost:8080/servant/search", query: q)
+        render json: response, status: :ok

Furthermore, this enables us to reuse all the RSpec tests we have! Though we needed to change one line. In fact, to wipe the database we were running each spec inside a database transaction.

Since now Rails runs in one process and Servant in another, using a transaction would shadow data to the latter. This is because, until a transaction is committed, records are not available outside of it. Therefore, instead of cleaning the database with transactions, we will use truncation:

diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
   config.before(:each) do
-    DatabaseCleaner.strategy = :transaction
+    DatabaseCleaner.strategy = :truncation

Also, in CI now we need to spin up the Servant application before running RSpec:

- run:
    command: DATABASE=stream_test stack --no-terminal exec haskell-exe
    background: true

Et voilà, now we don’t have to write tests in Haskell. We will, we will but we bought ourselves some time at least 😎

Support my work by tweeting this article! 🙏