Today I extracted a separate gem for the core Tic Tac Toe game logic. Up to now, the game itself with all its logic and collaborators was inside one project together with the terminal UI as well as the Qt UI.
Now there is a gem with the core logic, and that gem is used by the existing project that contains the different UI implementations only.
Creating the gem itself was relatively easy. Just executing
bundle gem tictactoe and moving the existing files into the appropriate folders. Adjust
tictactoe.gemspec with some description values. Done. Well… almost. It also forced me to finally use a proper namespace/module hierarchy for the game. Which is a good thing anyway, as I clearly should have done that earlier. There was just no real need for it yet.
Having the overall game(s) now split into two parts lead to another issue: shared examples were not directly available anymore for the UI project.
There are some ways to configure the gem accordingly. I came up with three possible ways:
1. Put everything needed under
This could be done relatively easy. But it felt wrong placing test specific classes into
After the gem would have been required in another project, the difference is not noticeable, whether the exported artifacts were placed in
lib from the beginning or not.
.gemspec file would then just include the regular
Gem::Specification.new do |spec| # ... other settings left out for brevity spec.require_paths = ["lib"] end
But the very idea of placing code from
lib scared me enough to not do it this way.
2. Export whole the
Another way would be to just include the
spec directory in the
require_path setting of the gem.
Gem::Specification.new do |spec| # ... other settings left out for brevity spec.require_paths = ["lib", "spec"] end
The drawback of this is, that all test files and classes from the
spec directory will be available for every client of the gem.
Even when you ignore the fact that this would increase the total file size of your gem, it also just pushes unnecessary code to every client of the gem.
3. Export parts of
The third option includes adding just enough additional files to
require_paths so that clients of the gem get what they’re really interested in.
Gem::Specification.new do |spec| # ... other settings left out for brevity spec.require_paths = ["lib", "spec/helpers"] end
I placed the shared examples and helper class in
To keep the namespacing behaviour intact the
tictactoe subdirectory is needed.
spec/helpers now, everything below that should have the proper namespace to not pollute any client’s load path.
This makes it possible that, as a client of the gem, you do not need to be aware that these files are loaded from a directory that is not the gem’s
lib directory originally.
It’s sufficient to require the shared examples like this:
So for any client the world is good now.
There is one downside though. In the specs for the gem itself, the path for
require can not be written like in the example above. In the specs of the gem, you need to be aware that the file comes from a directory called
require calls in the gem’s spec_helper show that:
require 'helpers/tictactoe/board_helper' require 'helpers/tictactoe/shared_examples'
You could probably get around that with some
$LOAD_PATH adjustments, but for the moment it didn’t felt so bad to leave it as it is. It’s a relatively focussed place where the path is specified like that – only in the
spec_helper. If I find myself having the need to specify that path in multiple places in the future, I might change the path handling then.