otherwise, fork nixpkgs and append the version required
I believe that^ is the brute force kinda-always-works option. However, I still don’t have enough experience to know if it’s the recommended option. There might be some method of using overlays or package arguments. Even if overlays and package arguments were used though, forking from an old nixpkgs means all the upstream dependencies for ruby worked for 2.6.8, so they probably also work for 2.6.9. If you use a new version of nixpkgs, and then overlay/add-args to get to 2.6.9 the upstream dependencies might be incompatible. Then you’d have to manually overlay/add-args for different versions of each of the upstream versions until everything worked. Which can be a very very painful process.
On the flip side, you can have the reverse problem that the forked-nixpkgs includes old dependencies that mess with, for example, a modern version of python in the same nix-shell as the old ruby 2.6.9
how do the patchSets work?
I’m not sure. The closest thing I know is that, instead of modifying the source repositories, nixpkgs maintainers often clone the source repo, make changes, save the changes as a .patch file. and Then they’ll have the .nix file download the code, apply the patch file to the downloaded code, and then try building the package. So my guess is patchSets are some kind of tool built around that idea.