Sometime last year, I wrote an article about advanced Stack techniques. We discussed one hypothetical case where a library had a bug. The solution here was to fork the repository and use a Github location in
I recently came across an easy example of doing this and thought I'd share it. We can see how to incorporate the change without stressing about its complexity. Even if you're only a beginner, this is a good skill to learn now!
You'll need some background on Stack first though! If you've never used Stack before, take a look at our Stack mini-course to learn more!
I've been doing a bit of work lately with the
persistent-migration library. This library lets us set up manual migrations for a Persistent schema we've set up through template Haskell. See this article for more on that topic.
The migrations library has a couple functions that let us run some SQL operations. They live in the
SqlPersistT monad, as we would expect. However, something's a little off about them:
getMigration :: MigrateSettings -> Migration -> SqlPersistT IO [MigrateSql] runMigration :: MigrateSettings -> Migration -> SqlPersistT IO ()
We can see that these functions restrict us to using
SqlPersistT on top of the normal
IO monad. But in most cases with Persistent, I use the
withPostgresqlConn function. This adds a
MonadLogger constraint. Thus
IO doesn't cut it. Most functions have a type signature looking like this:
databaseOp :: SqlPersistT (LoggingT IO) a
So we can't interoperate as easily as we'd like between our normal database operations, and these migration functions. The solution is that the migration functions should be more general in what monads they can use. This will be an easy fix, as we'll see. But first, we have to find a way to get our own version of the code.
persistent-migration library is on Github here. So we can make a fork of the repository, and clone that to our machine:
git clone https://github.com/jhb563/peristent-migration
Now we'll follow the build instructions in the
Developing.md file to get set up. Some of the tests fail on my machine, but we'll ignore that for this article.
Making Our Code Fixes
The code fixes turn out to be very easy. We can go into the relevant module and change the type signatures so they are more general:
module Database.Persistent.Migration.Postgres where ... getMigration :: (MonadIO m) => MigrateSettings -> Migration -> SqlPersistT m [MigrateSql] runMigration :: (MonadIO m) => MigrateSettings -> Migration -> SqlPersistT m ()
Even in the worst case, the only changes we would need to make here would be to add
liftIO calls in various places. But it turns out that this change doesn't break anything! The library still builds, and all the tests that were passing before still pass.
So now we can commit this change to our fork and push it to the repository.
Incorporating the Fix
Now we have to use our own fork as an alternative to the version of the library on Hackage. Before, the
extra-deps section in ourstack.yaml` looked like this:
extra-deps: - persistent-migration-0.1.0
This indicates we would grab the code from Hackage. But now we can use an alternative package format to reference our Github repository. Here's what we change it to:
extra-deps: - git: https://github.com/jhb563/persistent-migration.git commit: 9f9c5035efe
And now we've got our own fork as a dependency for our project! We can write code like so:
doMigrations :: SqlPersistT (LoggingT IO) a doMigrations = do runMigration defaultSettings migration ...
And everything works! Our code builds!
Now, an approach like this can lead to some possible issues. We're now disconnected from the original repository. So if there was a new release, we'd have to do a bit more work to pull those changes into our own repository. Still, this isn't too difficult. One solution to this is to submit a pull request with our changes. If it gets accepted, they'll be in the next release! Then we can go back to using the version on Hackage!
In this article, we did a quick overview of how to make our own changes to libraries. We cloned the repository, made a code change, and added our fork as a dependency. Obviously, most of the changes you'll want to make aren't as simple as this one was. But it's good to use an example where all we're doing is tackling the issue of getting the code into our code base!