How to reuse IHP classes in a IHP script?

Viewed 174

With IHP (the haskell web framework) I created a web application. Now I want to create a IHP Script to load some external data into my database. However I'm getting a lot of import conflicts from the Prelude, but not the types I expected.

#!/usr/bin/env run-script
module Application.Script.DataLoader where

import Application.Script.Prelude      hiding (decode, pack, (.:))
import qualified Data.ByteString.Lazy  as     BL
import Data.Csv
import Data.Text                              (pack)
import qualified Data.Vector           as     V
import Control.Monad                          (mzero)

instance FromNamedRecord Product where
    parseNamedRecord r = Product def <$> r .: "title" <*> r .: "price" <*> r .: "category" <*> pure def

run :: Script
run = do
    csvData <- BL.readFile "~/tender/data/Boiler-en-kookkraan_Boiler.csv"
    case decodeByName csvData of
        Left err -> putStrLn $ pack err
        Right (_, v) -> V.forM_ v $ \ p ->
            putStrLn $ (get #title p) ++ ", " ++ show (get #price p) ++ " euro"

Where my Product schema looks like this:

CREATE TABLE products (
    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
    title TEXT NOT NULL,
    price DOUBLE PRECISION NOT NULL,
    category TEXT NOT NULL
);

Is there a way to use the types I created as a Data object to e.g. read my csv to?

[Updated output]

Application/Script/DataLoader.hs:12:26: error:
    • Couldn't match type ‘MetaBag -> Product' a1’
                     with ‘Product' (QueryBuilder ProjectProduct)’
      Expected type: Parser Product
        Actual type: Parser (MetaBag -> Product' a1)
    • In the expression:
        Product def <$> r .: "title" <*> r .: "price" <*> r .: "category"
          <*> pure def
      In an equation for ‘parseNamedRecord’:
          parseNamedRecord r
            = Product def <$> r .: "title" <*> r .: "price" <*> r .: "category"
                <*> pure def
      In the instance declaration for ‘FromNamedRecord Product’
   |
12 |     parseNamedRecord r = Product def <$> r .: "title" <*> r .: "price" <*> r .: "category" <*> pure def
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Solved, all credits to the help of @mpscholten]

#!/usr/bin/env run-script
module Application.Script.DataLoader where

import Application.Script.Prelude      hiding (decode, pack, (.:))
import qualified Data.ByteString.Lazy  as     BL
import Data.Csv
import Data.Text                              (pack)
import qualified Data.Vector           as     V
import Control.Monad                          (mzero)

parseProduct :: NamedRecord -> Parser Product
parseProduct r = do
  title <- r .: "title"
  price <- r .: "price"
  category <- r .: "category"

  newRecord @Product
    |> set #title title
    |> set #price price
    |> set #category category
    |> pure

run :: Script
run = do
    csvData <- BL.readFile "data/Boiler-en-kookkraan_Boiler.csv"
    case decodeByNameWithP parseProduct defaultDecodeOptions csvData of
        Left err -> putStrLn $ pack err
        Right (_, v) -> V.forM_ v $ \ p ->
            putStrLn $ (get #title p) ++ ", " ++ show (get #price p) ++ " euro"
2 Answers

Can you maybe share what errors you get exactly? Your desired product type should be in Generated.Types which intern is loaded by Application.Script.Prelude.

I think you might have two models that both have the field title. In haskell fields are functions and they may not be used twice.

Inside the FromNamedRecord instance you are missing two fields: id and meta. The id field is the first field of the record. The meta field is a hidden field used by IHP to keep track of validation errors. It's always the last field of a record.

The easiest way to solve this is to use newRecord and write out the code in a more explicit way:

instance FromNamedRecord Product where
    parseNamedRecord r = do
        title <- r .: "title"
        price <- r .: "price"
        category <- r .: "category"

        newRecord @Product
            |> set #title title
            |> set #price price
            |> set #category category
            |> pure

For the error "Ambiguous occurrence ‘title’" try to use the get function instead of using the normal haskell accessor function:

putStrLn $ (get #title p) ++ ", " ++ show (get #price p) ++ " euro"
Related