Exercism Exercises in Ruby
You'll need a recent (2.1+) version of Ruby, but that's it. Minitest ships with the language, so you're all set.
The files for an exercise live in exercises/<slug>
. The slug for an exercise
is a unique nickname composed of a-z (lowercase) and -, e.g. clock
or
atbash-cipher
. Inside its directory, each exercise has:
- a test suite,
<exercise_name>_test.rb
- an example solution,
.meta/solutions/<exercise_name>.rb
where <exercise_name>
is the underscored version of the exercise's slug, e.g.,
clock
or atbash_cipher
.
If the exercise has a test generator, the directory will also contain:
.version
- the test generator,
.meta/generator/<exercise_name>_case.rb
A few exercises use a custom test template:
.meta/generator/test_template.erb
For some, even perhaps many, of the exercises, you will find a reference
to the BookKeeping
module, but this is only included when tests have
been generated; see Generated Test Suites.
This VERSION
number helps make sure exercise solvers and exercise
reviewers know which revision of the test suite they are talking
about, and it theoretically helps avoid reviewer feedback like
"Your solution doesn't make the tests pass," if they are looking
at a different version of the tests than the solver used.
Most exercises can be generated from shared inputs/outputs, called canonical data (see Generated Test Suites below). To find out whether a test has canonical data, check the problem-specifications repo.
Run the tests using rake
, rather than ruby path/to/the_test.rb
. rake
knows
to look for the example solution and to disable skips. Just tell rake
the name
of your problem and you are set:
rake test:clock
To pass arguments to the test command, like -p
for example, you can run
the following:
rake test:clock -- -p
To run a subset of the tests, use a regular expression. For example, if tests exist that are named identical_to_4_places, and identical, then we can run both tests with
rake test:hamming -- -p -n="/identical/"
Note that flags which have an attached value, like above, must take the form
-flag=value
and if value
has spaces -flag="value with spaces"
.
Generated test suites use the bin/generator
cli.
Before using the cli it is recommended you run bundle install
from within
the ruby directory to install/update any required gems.
While many of the exercises which have canonical data already have generators, some do not. To find out whether an exercise has a generator, run
bin/generate -h
In addition to a usage message, the -h
flag lists all exercises with a
generator. If a generator is available for your exercise, you can
- Regenerate the test suite based on updated canonical data
- Make changes to a generated exercise
If not, you will need to implement a new generator
Generated exercises depend on the the shared metadata, which must be cloned to the same directory that contains your clone of the ruby repository:
tree -L 1 ~/code/exercism
├── problem-specifications
└── ruby
From time to time, the canonical data for an exercise's tests changes, and we need to keep the Ruby version's tests synced up. Regenerating these tests is a quick and easy way to help maintain the track and get involved!
If it's your first time cloning/contributing to the repository, you'll
need to install any dependencies via bundle
:
bundle install
Be sure that you're working on the most up-to-date version of the repo. From the root of your copy of the repository:
# Add the exercism repo as upstream if you haven't yet:
git remote add upstream https://github.com/exercism/ruby.git
# Pull down any changes
git fetch upstream
# Merge any upstream changes with your master branch
git checkout master
git merge upstream/master
Depending on your git workflow preferences and the state of your local repo, you may want to do some rebasing. See the rebasing documentation for more information.
The generator also depends on the presence of Exercism's problem-specifications
repository (see the file tree in the section above). Make sure you've got
an up-to-date version of the specifications in a problem-specifications
folder that's in a parallel directory to your local copy of the ruby
repository.
To check which problems have possibly been updated, run:
bin/generate --all
This will autogenerate all of the test cases for which generators exist.
Use git diff
(or your preferred method) to find out which test files
have changed. Some exercises will update because someone updated the
description or other exercise metadata. Others will change because the
actual test suite has changed. If you find that an exercise's test suite
(i.e. the actual tests, not just the line at the test data version number
at the top of the tests) has changed, be sure to use generate
to update
the BookKeeping::VERSION number by running:
bin/generate --update <exercise-slug>
Once everything has been regenerated and updated, you're almost ready to
submit your changes via pull request. Please be sure to only update one
exercise per pull request. Also, please follow the guidelines in the
Pull Requests section, being sure to follow the pattern
of <slug>: Regenerate Tests
, where slug is the slug of the exercise that
your pull request is regenerating.
Do not edit <slug>/<exercise_name>_test.rb
. Any changes you make will
be overwritten when the test suite is regenerated.
There are two reasons why a test suite might change:
- the tests need to change (an incorrect expectation, a missing edge case, etc)
- there might be issues with the style or boilerplate
In the first case, the changes need to be made to the canonical-data.json
file for
the exercise, which lives in the problem-specifications repository.
../problem-specifications/exercises/<slug>/
├── canonical-data.json
├── description.md
└── metadata.yml
This change will need to be submitted as a pull request to the problem-specifications repository. This pull request needs to be merged before you can regenerate the exercise.
Changes that don't have to do directly with the test inputs and outputs should
be made to the exercise's test case generator, discussed
in implementing a new generator, next. Then you
can regenerate the exercise with bin/generate <slug>
.
An exercise's test case generator class produces the code that goes inside
the minitest test_<whatever>
methods. An exercise's generator lives in
exercises/<slug>/.meta/generator/<exercise_name>_case.rb
.
The test case generator is a derived class of ExerciseCase
(in
lib/generator/exercise_case.rb
). ExerciseCase
does most of the work of
extracting the canonical data. The derived class wraps the JSON for a single
test case. The default version looks something like this:
require 'generator/exercise_case'
class <ExerciseName>Case < Generator::ExerciseCase
def workload
# Example workload:
"#{assert} Problem.call(#{input.inspect})"
end
end
where <ExerciseName>
is the CamelCased version of the exercise's slug. This is
important, since the generator script will infer the name of the class from
<slug>
.
This class must provide the methods used by the test
template. A
default template that
most exercises can (and do) use lives in lib/generator/test_template.erb
. The
base class provides methods for the default template for everything except
#workload
.
#workload
generates the code for the body of a test, including the assertion
and any setup required. The base class provides a variety of
assertion and
helper methods.
Beyond that, you can implement any helper methods that you need
as private methods in your derived class. See below for more information
about the intention of #workload
You don't have to do anything other than implement #workload
to use the default
template.
If you really must add additional logic to the view template, you can use a
custom template. Copy lib/generator/test_template.erb
to
.meta/generator/test_template.erb
under your exercise directory and
customize. You may need to create .meta
and/or .meta/generator
.
Prioritize educational value over expert comprehension and make sure that things are clear to people who may not be familiar with Minitest and even Ruby.
Provide the information the student needs to derive the code to pass the test in a clear and consistent manner. Illustrate the purpose of the individual elements of the assertion by using meaningful variable names.
Example output from the workload
method:
detector = Anagram.new('allergy')
anagrams = detector.match(["gallery", "ballerina", "regally", "clergy", "largely", "leading"])
expected = ["gallery", "largely", "regally"]
assert_equal expected, anagrams.sort
We welcome pull requests that provide fixes to existing test suites (missing tests, interesting edge cases, improved APIs), as well as new problems.
If you're unsure, then go ahead and open a GitHub issue, and we'll discuss the change.
Please submit changes to a single problem per pull request unless you're submitting a general change across many of the problems (e.g. formatting).
You can run (some) of the same checks that we run by running the following tool in your terminal:
bin/local-status-check
If you would like to have these run right before you push your commits, you can activate the hook by running this tool in your terminal:
bin/setup-git-hoooks
Thank you so much for contributing! ✨
We have created a minimal set of guidelines for the testing files, which
you can take advantage of by installing the rubocop
gem. It will use
the configuration file located in the root folder, .rubocop.yml
. When
you edit your code, you can simply run rubocop -D
. It will ignore
your example solution, but will gently suggest style for your test code.
The -D
option that is suggested is provided to give you the ability to
easily ignore the Cops that you think should be ignored. This is easily
done by doing # rubocop:disable CopName
, where the CopName
is replaced
appropriately.
For more complete information, see Rubocop.
While lib/generator/exercise_case.rb
provides helper functions as discussed
above, it remains the responsibility of an exercise's generator to interpret its
canonical-data.json data in a stylistically correct manner, e.g. converting
string indices to integer indices.
All exercises must have a README.md file, but should not be created manually. The READMEs are constructed using shared metadata, which lives in the problem-specifications repo.
Use the configlet
tool to generate a README from shared metadata:
- Clone the problem-specifications repo into an adjacent directory.
- Fetch the configlet appropriate for your system:
bin/fetch-configlet
- Generate the readme for a particular exercise:
bin/configlet generate . --only rotational-cipher
For an in-depth discussion of how exercism language tracks and exercises work, please see the contributing guide.
If you're just getting started and looking for a helpful way to get involved, take a look at regenerating the test suites, porting an exercise from another language, or creating an automated test generator.
The Ruby icon is the Vienna.rb logo, and is used with permission. Thanks Floor Dress :)