Rewriting to Haskell–Standing on the shoulders of Rails

Posted on March 30, 2020 by Riccardo

Rewriting to Haskell (Series)

Rewriting to Haskell–IntroRewriting to Haskell–Project SetupRewriting to Haskell–DeploymentRewriting to Haskell–Automatic FormattingRewriting to Haskell–ConfigurationRewriting to Haskell–Standing on the shoulders of RailsRewriting to Haskell–Making GHC More NitpickyRewriting to Haskell–TestingRewriting to Haskell–LintingRewriting to Haskell–Parsing Query ParamsRewriting to Haskell–Parsing Query Params, AgainRewriting 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 😎


It's one of the selected few I follow every week – Mateusz

Tired of RELEARNING webdev stuff?

  • A 100+ page book with the best links I curated over the years
  • An email once a week full of timeless software wisdom
  • Your recommended weekly dose of pink
  • Try before you buy? Check the archives.