lpsmith / postgresql-simple

Mid-level client library for accessing PostgreSQL from Haskell
Other
206 stars 71 forks source link

How to handle relationships between types / tables #139

Closed simg closed 9 years ago

simg commented 9 years ago

How would I approach persisting a data structure like:

Data Order = Order {
  orderId :: Int,
  orderTotal :: Double,
  orderItems :: [OrderItem]
}

Data OrderItem = OrderItem {
  itemOrderId :: Int,
  itemDescription :: Text,
  itemPrice :: Double,
}

which corresponds to a table structure something like:

CREATE TABLE orders
   ( id   INT  NOT NULL
   , total  NUMERIC NOT NULL
   );

CREATE TABLE order_items
   ( order_id INT  NOT NULL REFERENCES orders(id)
   , line_no INT NOT NULL
   , description   TEXT NOT NULL
  , price DOUBLE NOT NULL,
CONSTRAINT order_items_pkey PRIMARY KEY (order_id, line_no)
   );

Embedding order items into the orders data type seems to be the logical approach, but I wouldn't want postgres-simple to try to save (or load) the order items field. Am I missing something?

Any suggestions appreciated.

simg commented 9 years ago

Just to help anyone following along after me. What I ended up doing (with help from lpsmith, thx) is modifying the fromRow instance so that the orderItems list doesn't get "saved" automatically. Then implmenting my own manual save of the orderItems list.

instance FromRow Order where fromRow = Order <$> field <*> field <*> pure [] 

instance ToRow Order where toRow o = [toField (orderId o), toField (orderTotal o)]

insertOrder :: Order -> Handler App App (Maybe Int)
insertOrder o = do
  res :: [Only Int] <- query "INSERT INTO orders (total) values (?) RETURNING id" (orderTotal o)
  case res of
    [] -> return Nothing
    (id:_)-> do
      insertOrderItems $ fmap (\a -> a { itemOrderID = Just $ fromOnly id}) $ orderItems o
      return (Just $ fromOnly id)

insertOrderItems :: [OrderItem] -> Handler App App ()
insertOrderItems items = do
  executeMany "INSERT INTO order_items (order_id, line_no, description) values (?,?,?)" items
  return ()   

there's undoubtedly a neater way of doing this, but that's what I have so far :)

note: the ToRow instance only as 2 fields. This means theres further work to do if you need to populate the orderItems list from the database. I might post the code, if and when I get that far :)