Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally derive PathMultiPiece for composite keys #1509

Merged
merged 15 commits into from
Oct 3, 2023

Conversation

blujupiter32
Copy link
Contributor

@blujupiter32 blujupiter32 commented Jul 16, 2023

This PR enables PathMultiPiece instance derivation for entities with composite keys using mpsDeriveInstances.

Unlike PathPiece, an instance is not derived automatically. This should avoid the coupling concerns expressed in #649 and #1458. A similar technique could be used for PathPiece etc. in a future (breaking) change to address these issues.


Before submitting your PR, check that you've:

After submitting your PR:

  • Update the Changelog.md file with a link to your PR
  • Bumped the version number if there isn't an (unreleased) on the Changelog
  • Check that CI passes (or if it fails, for reasons unrelated to your change, like CI timeouts)

Copy link
Collaborator

@parsonsmatt parsonsmatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I'm in favor of this change. There's a bit too much special-case logic here.

Currently, there are two ways of deriving instances for keys: the mps setting, and manually.

mkPersist sqlSettings [persistLowerCase|
    Blah
      name String
  |]

instance PathMultiPiece (Key Blah) where ...

I'm OK with introducing features to make manual instances easier - ie exporting the function defaults that are based on PersistEntity a => ..., and a newtype for DerivingVia.

Comment on lines 185 to 200
-- | An auxiliary class to enable 'PathMultiPiece' instance derivation for
-- 'PersistEntity' composite keys. Instances of 'PersistPathMultiPiece' should
-- be derived using the @DeriveAnyClass@ strategy.
--
-- @since 2.14.6.0
class PersistEntity record => PersistPathMultiPiece record where
-- | 'fromPathMultiPiece' wrapper. The default implementation uses
-- 'keyFromValues' and 'fromPathPiece', which are provided by the context.
keyFromPieces :: [Text] -> Maybe (Key record)
keyFromPieces pieces = do
Right key <- keyFromValues <$> mapM fromPathPiece pieces
pure key
-- | 'toPathMultiPiece' wrapper. The default implementation uses
-- 'keyToValues' and 'toPathPiece', which are provided by the context.
keyToPieces :: Key record -> [Text]
keyToPieces = map toPathPiece . keyToValues
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this class required? I think that we should be able to inline the definition directly into the PathMultiPiece (Key record) instance.

Comment on lines 202 to 204
instance PersistPathMultiPiece record => PathMultiPiece (Key record) where
fromPathMultiPiece = keyFromPieces
toPathMultiPiece = keyToPieces
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So PersistPathMultiPiece is used to provide custom behavior to the PathMultiPiece instance. Instead of writing,

mkPersist sqlSettings [persistLowerCase| ... |]

instance PathMultiPiece (Key MyRecord) where
  fromPathMultiPiece = keyFromPieces
  toPathMultiPiece = keyToPieces

We write:

mkPersist sqlSettings [persistLowerCase| ... |]

instance PersistPathMultiPiece MyRecord

I think I am a little hesitant on this approach. The more idiomatic approach is to use a newtype wrapper for use with DerivingVia, like so:

newtype ViaPersistEntity a = ViaPersistEntity (Key a)

instance (PersistEntity a) => PathMultiPiece (Key a) where
  fromPathMultiPiece = ...
  toPathMultiPiece = ....

-- Use site:

mkPersist sqlSettings [persistLowerCase| ... |]

deriving via ViaPersistEntity MyRecord instance PathMultiPiece (Key MyRecord)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced PersistPathMultiPiece with the more idiomatic approach in 950098c.


Primary firstPart secondPart

deriving PathMultiPiece
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usage is definitely something I'd consider a problem - the deriving clause is for the datatype, not the key, and I don't want to start introducing special-case logic to decide if a class is intended for a key or the datatype (or both).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usage, and the Haddock change documenting it here, have been removed in 950098c.

Comment on lines 1786 to 1787
| typeclass == ''PathMultiPiece =
ViaStrategy $ ConT ''ViaPersistEntity `AppT` recordType
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I should have been more clear - I'm not going to accept a PR that has a special-case for how an instance is derived.

Suggested change
| typeclass == ''PathMultiPiece =
ViaStrategy $ ConT ''ViaPersistEntity `AppT` recordType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so regarding your first comment,

I'm OK with introducing features to make manual instances easier - ie exporting the function defaults that are based on PersistEntity a => ..., and a newtype for DerivingVia.

should I revert my changes to Database.Persist.TH and remove CompositeKeyPathMultiPieceSpec? That would leave ViaPersistEntity and its PathMultiPiece instance as the additions from this PR. If those don't seem useful on their own, I'm fine with closing the PR.

I'll keep the rest on my fork as automatic PathMultiPiece derivation is helpful for my specific use case, although I don't think it would be used often if it was added here (no issues have been raised to request it).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that works.

I need to document more about how to hook into the derivation stuff without modifying this function here.

The way that I would approach this is with an additional function that operates on the [UnboundEntityDef] from a thing. So, you have,

$(share [mkPersist sqlSettings, derivePathMultiPiece] ...)

Where derivePathMultiPiece :: [UnboundEntityDef] -> Q [Dec], and you can do something like:

derivePathMultiPiece = 
  concat <$> traverse derivePathMultiPieceFor
  where 
    derivePathMultiPieceFor unboundEntityDef = 
      let keyTypeName = ...
      [d| deriving via (ViaPersistentEntity $(keyTypeName)) instance PathMultiPiece $(keyTypeName) |]

This gives you the same automatic instance generation, without modifying the upstream code.

Copy link
Collaborator

@parsonsmatt parsonsmatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is great 🎉

@parsonsmatt parsonsmatt merged commit 6ec4de1 into yesodweb:master Oct 3, 2023
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants