I need to order pages (defined by user, drag and drop), something like ("Hello", order 1), ("Bye", order 2) And then insert, let's say ("Good", order 1), so the collection would change to ("Good", order 1), ("Hello", order 2), ("Bye", order 3).
Any ideas how to achieve this with IHP/Postgres?
I have looked at
https://begriffs.com/posts/2018-03-20-user-defined-order.html
Are those the best solutions we have to date?
CodePudding user response:
I usually use the integer position column approach. Here's an example code from a real world IHP app:
In this case we have offers column with a position integer column, like this:
CREATE TABLE offers (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
name TEXT NOT NULL,
"location" TEXT NOT NULL,
contact TEXT NOT NULL,
description TEXT NOT NULL,
"position" INT NOT NULL
);
The controller looks like this:
module Web.Controller.Offers where
import Web.Controller.Prelude
import Web.View.Offers.Edit
import Web.View.Offers.Show
import Web.View.Offers.New
import Web.View.Offers.Index
import qualified Application.Offer as Offer
import qualified Control.Monad.State.Lazy as State
instance Controller OffersController where
beforeAction = ensureIsUser
action NewOfferAction = do
let backField :: Offer = newRecord
render NewView { .. }
action OffersAction = do
backFields <- query @Offer |> orderBy #position |> fetch
render IndexView { .. }
action ShowOfferAction { .. } = do
backField <- fetch backFieldId
render ShowView { .. }
action EditOfferAction { .. } = do
backField <- fetch backFieldId
render EditView { .. }
action UpdateOfferAction { .. } = do
backField <- fetch backFieldId
backField
|> buildOffer
|> ifValid \case
Left backField -> render EditView { .. }
Right backField -> do
backField <- updateRecord backField
setSuccessMessage "Offer updated"
redirectTo EditOfferAction { .. }
action CreateOfferAction = do
nextPosition <- Offer.nextPosition
newRecord @Offer
|> buildOffer
|> ifValid \case
Left backField -> render NewView { .. }
Right backField -> do
backField <- backField
|> createRecord
setSuccessMessage "Angebot erstellt"
redirectTo OffersAction
action DeleteOfferAction { .. } = do
backField <- fetch backFieldId
deleteRecord backField
setSuccessMessage "Deleted Offer successfully"
redirectTo OffersAction
action OfferMoveUpAction { .. } = do
backField <- fetch backFieldId
prevOffer <- backField |> Offer.prevOffer
case prevOffer of
Just prevOffer -> do
let backFieldPosition = get #position backField
let prevOfferPosition = get #position prevOffer
updateRecord (backField |> set #position prevOfferPosition)
updateRecord (prevOffer |> set #position backFieldPosition)
return ()
Nothing -> return ()
redirectTo OffersAction
action OfferMoveDownAction { .. } = do
backField <- fetch backFieldId
nextOffer <- backField |> Offer.nextOffer
case nextOffer of
Just nextOffer -> do
let backFieldPosition = get #position backField
let nextOfferPosition = get #position nextOffer
updateRecord (backField |> set #position nextOfferPosition)
updateRecord (nextOffer |> set #position backFieldPosition)
return ()
Nothing -> return ()
redirectTo OffersAction
buildOffer :: _ => backField -> backField
buildOffer backField =
backField
|> fill @'["name", "description", "position", "location", "contact"]
|> validateField #name nonEmpty
Some helper functions used from the controller are defined inside another module (but you could just put them into the controller if you want):
module Application.Offer (nextPosition, prevOffer, nextOffer) where
import IHP.Prelude
import IHP.ModelSupport
import IHP.QueryBuilder
import Generated.Types
import qualified Database.PostgreSQL.Simple as PG
instance DefaultScope "offers" where
defaultScope = orderBy #position
nextPosition :: (?modelContext :: ModelContext) => IO Int
nextPosition = sqlQueryScalar "SELECT COUNT(*) FROM offers" ()
prevOffer :: (?modelContext :: ModelContext) => Offer -> IO (Maybe Offer)
prevOffer offer = do
results <- sqlQuery "SELECT * FROM offers WHERE position < ? ORDER BY position DESC LIMIT 1" (PG.Only (get #position offer))
return $ headMay results
nextOffer :: (?modelContext :: ModelContext) => Offer -> IO (Maybe Offer)
nextOffer offer = do
results <- sqlQuery "SELECT * FROM offers WHERE position > ? ORDER BY position ASC LIMIT 1" (PG.Only (get #position offer))
return $ headMay results
