Using Hanami with a legacy DB

April 27, 2018
mssql hanami postgresql

I am currently working with a legacy DB in MSSQL and at the same time preparing a future migration to PostgreSQL.

The table has the following structure in MSSQL

Table name Column name Column type
CatGeneral
CatalogID varchar(20)
ItemID varchar(80)
ItemDescription nvarchar(255)
transactionDate datetime
lastUpdateDate datetime
userId varchar(3)
status varchar(3)
statusWeb varchar(1)

and the following structure in PostgreSQL

Table name Column name Column type
catgeneral
catalogid character varying(20)
itemid character varying(80)
itemdescription character varying(255)
transactiondate timestamp without time zone
lastupdatedate timestamp without time zone
userid character varying(3)
status character varying(3)
statusweb character varying(1)

As you can realize the table and column names are not in the format normally used in Rails or Hanami apps.

The first step in Hanami is create a model

$ hanami generate model CatalogItem

If I open a hanami console the follwing error appears:

$ hanami c
[ROM::Relation[CatalogItems]] failed to infer schema. Make sure tables exist before ROM container is set up. This may also happen when your migration tasks load ROM container, which is not needed for migrations as only the connection is required (schema parsing returned no columns, table "catalog_items" probably doesn't exist)

Hanami expects a table named catalog_items but our table name is CatGeneral. To tell Hanami to use this table name we have to modify CatalogItemRepository class as follows

class CatalogItemRepository < Hanami::Repository
  self.relation = :catgeneral
end

We can now open the console without any errors and create a new instance of the repository:

$ hanami c
2.5.1 :001 > repo = CatalogItemRepository.new
 => #<CatalogItemRepository relations=[:catgeneral]>
2.5.1 :002 >

And we can get the first record:

2.5.1 :004 > ap repo.first
[medik] [INFO] [2018-04-28 08:40:36 -0500] (0.000756s) SELECT "catalogid", "itemid", "itemdescription", "transactiondate", "lastupdatedate", "userid", "status", "statusweb" FROM "catgeneral" ORDER BY "catgeneral"."catalogid", "catgeneral"."itemid" LIMIT 1
{
          :catalogid => "ALTA_POR",
             :itemid => "0",
    :itemdescription => "Alta por:",
    :transactiondate => 2018-03-18 20:22:45 UTC,
     :lastupdatedate => 2018-03-18 20:22:45 UTC,
             :userid => "X",
             :status => "A",
          :statusweb => "A"
}
 => nil

Notice that we are using the excellent awesome_print gem to display the record using ap repo.first. It looks great in your console.

However the attribute names are not in snake_case. To fix this we use a schema mapping in the repository.

class CatalogItemRepository < Hanami::Repository
  self.relation = :catgeneral

  mapping do
    attribute :catalog_id, from: :catalogid
    attribute :item_id, from: :itemid
    attribute :item_description, from: :itemdescription
    attribute :transaction_date, from: :transactiondate
    attribute :lastupdate_date, from: :lastupdatedate
    attribute :user_id, from: :userid
    attribute :status, from: :status
    attribute :status_web, from: :statusweb
  end
end

Now the attribute names are nicer.

2.5.1 :002 > ap repo.first
[medik] [INFO] [2018-04-28 08:49:14 -0500] (0.001329s) SELECT "catalogid", "itemid", "itemdescription", "transactiondate", "lastupdatedate", "userid", "status", "statusweb" FROM "catgeneral" ORDER BY "catgeneral"."catalogid", "catgeneral"."itemid" LIMIT 1
{
          :catalog_id => "ALTA_POR",
             :item_id => "0",
    :item_description => "Alta por:",
    :transaction_date => 2018-03-18 20:22:45 UTC,
     :lastupdate_date => 2018-03-18 20:22:45 UTC,
             :user_id => "X",
              :status => "A",
          :status_web => "A"
}
 => nil

It is interesting to notice that we have to include a maping for all the columns in our table even though there is no need to change the name, as is the case of column status. Not including all the columns causes an error when loading the console.

Hanami expects the primary key to be an integer, however in this case it is a string so we need to provide a custom schema in our entity class.

class CatalogItem < Hanami::Entity
  attributes do
    attribute :item_id, Types::String
  end
end

However this has a side effect, only the declared attributes are part of our entity class.

2.5.1 :002 > ap repo.first
[medik] [INFO] [2018-04-29 14:58:38 -0500] (0.001380s) SELECT "catalogid", "itemid", "itemdescription", "transactiondate", "lastupdatedate", "userid", "status", "statusweb" FROM "catgeneral" ORDER BY "catgeneral"."catalogid", "catgeneral"."itemid" LIMIT 1
{
    :item_id => "0"
}
 => nil

So we have to declare all the required attributes again.

Since this is a legacy database, this behavior allows us to select which attributes will be part of our Entity leaving out columns that are not used anymore.

I have to investigate the following: