Ruby Algorithm Documentation with AsciiDoc and Knitr
A functional specification is a high-level design document created before implementation and serves as a true plan for the team.
In this article, Toptal Freelance Ruby Developer Alec Ten Harmsel walks us through prototyping an algorithm and creating a functional specification with the help of AsciiDoc and R for a Ruby on Rails project.
A functional specification is a high-level design document created before implementation and serves as a true plan for the team.
In this article, Toptal Freelance Ruby Developer Alec Ten Harmsel walks us through prototyping an algorithm and creating a functional specification with the help of AsciiDoc and R for a Ruby on Rails project.
Alec ran an autonomous drone team in college before graduating to work on software in banking and construction management
Expertise
Previously At
Every non-trivial project needs documentation, and it needs to be engaging with great explanations, and even illustrations, so that it’s actually read and used. Engineers make software to solve business problems, generally without having extensive experience in that particular business, so documenting the business is just as important as documenting the software.
Documentation can be bolted on after the fact, but I find that writing a functional specification before coding is a big help. A functional specification is a source of truth for every member of the team across disciplines, and forces high-level design before implementation, preventing wasted effort. In this post, I’m going to go through an example project from prototyping to writing a functional specification for an example Ruby on Rails project to describe what the app is supposed to do and be a reference during development. My goal is to show that Ruby projects can use AsciiDoc and R to:
- Easily prototype new calculations, algorithms, etc. using R
- Easily show results from R in your functional specification and other written AsciiDoc documentation
- Tie it into the build process so documentation is always updated with the latest data and algorithms
A functional spec isn’t a replacement for documenting your API with RDoc or YARD–it’s a vital addition that can be used as a reference for all stakeholders (programmers and non-programmers alike) in a project.
Prototype
I like salmon fishing, and I want a way for myself and others to keep track of and share details about the fish they’re catching. It also needs a scoreboard to showcase the top users. Right now there are forums used for this purpose, but structured data with a scoreboard is way cooler.
I could make a prototype in Ruby, but Ruby doesn’t have the best support for graphing, so I’m going to use R. Using R gives me a lot of easy ways to play with the math and visualization, and it’s cross-platform.
# Read in some example data
data <- read.csv("func_spec/example_user_data.csv")
# Compute the score, weighted by base-10 log of number of reports
score <- mean(data$Fish.Caught) * log(length(data$Date)) / log(10)
In just two lines, I read in some example data and calculated a score using my proprietary score. R can do this and show a plot with a vanilla install, while languages like Ruby or Python will require more code and extra packages to be installed.
Documenting the Algorithm
After making my scoring system, I could go straight to code. In fact, I could have skipped prototyping in R and just prototyped directly in my Ruby on Rails app. However, prototyping in R was fast and very close to the underlying math. Here’s how I’ll set up the documentation:
-
kingfisher/
Rakefile
-
doc/
func_spec.Radoc
My example Rails project is called kingfisher, and I’m putting the documentation into a “doc” folder. I’m not going to go over AsciiDoc syntax as the Asciidoctor site has a bunch of great documentation for that, but here’s how to stick a chart into the AsciiDoc with knitr:
//begin.rcode freq_change, fig.asp=0.618, fig.width=12, fig.align="center"
times_fished <- 1:50
plot(times_fished, 5 * log(times_fished) / log(10),
ylab="User score when average 5 catches per trip",
xlab="Number of fishing trips in the past 12 months")
//end.rcode
Stick that into func_spec.Radoc
, run knitr and AsciiDoc, and you’ll get something like this in your documentation:
Hopefully most developers intuitively understand the score would go up logarithmically with the number of times fished; even so, this is a great way to display that just to make sure. Around this chart, I’d have some text explaining why this is desirable so developers who don’t fish can understand what’s going on here. This example is contrived, but I’ve used this type of prototyping and documentation in the past for other projects (e.g. finance, construction estimating) where the results of the calculations may not always be obvious.
Now that the score is prototyped and we have a plot to stick in the documentation, all that’s left is to transform the input AsciiDoc to some sort of output format. I use a few rules in my Rakefile to transform to HTML:
require 'asciidoctor'
require 'pathname'
task docs: %w[doc/func_spec.html]
rule '.adoc' => '.Radoc' do |t|
Dir.chdir(Pathname.new(t.name).dirname) do
sh "R -e 'library(knitr);knit(\"#{Pathname.new(t.source).basename}\", " +
"\"#{Pathname.new(t.name).basename}\")'"
end
end
rule '.html' => '.adoc' do |t|
Dir.chdir(Pathname.new(t.name).dirname) do
Asciidoctor.convert_file Pathname.new(t.source).basename,
backend: :html5,
to_file: true,
safe: :safe
end
end
Now I can run “rake docs” and get the documentation to build and always be up to date, even if I change the underlying data or scoring calculation.
Other Solutions
For anything involving Ruby on Rails, documenting with AsciiDoc is a great choice whether there are custom graphics in the documentation or not. AsciiDoc gives you a Ruby-native way to write in a wonderful markup format. That said, there are other great documentation tools that will be a bit more to manage and tie into your build process for a Rails project.
Sphinx processes reStructuredText (also a nice markup format) and can incorporate output from Matplotlib. For a Python project, this is perfect–Sphinx + Matplotlib give you a Python-native way to produce nice documentation with custom graphics. However, if you’re working on a Rails project, it’s less than desirable to have to manage multiple sets of dependencies for a separate environment just to produce your documentation.
There are tons of other solutions for producing documentation, including programs like doxygen and LaTeX, which may require more or less glue to fit into your build system. If you need a system like LaTeX, use it; if you have a fairly standard Rails project and you just have a bit of math that should be documented, use AsciiDoc and R.
Conclusion
Ruby and AsciiDoc make a great team, and it’s easy to throw R into the mix to make nice visualizations in your documentation. You can throw in any package in the R ecosystem, like ggplot2, to get beautiful charts in your documentation.
The source material for this post can be found at its GitHub repo. As of publication, that repo doesn’t hold a “real” project, but it may in the future!
If you want to talk a step further with Ruby, I recommend learning Metaprogramming with Ruby Metaprogramming Is Even Cooler Than It Sounds.
Understanding the basics
What is AsciiDoc?
AsciiDoc is a markup format similar to Markdown and reStructuredText meant for publishing in HTML, PDF, or other formats.
What is a functional specification?
A functional specification is a document that describes how a project should work, understandable by every project stakeholder.
What is an example of AsciiDoc?
= Document Title Alec Ten Harmsel
This is the body of the document.
Why write a functional specification?
A functional specification is a source of truth for every member of the team across disciplines, and forces high-level design before implementation, preventing wasted effort.
What is Ruby on Rails?
Ruby on Rails is a web framework that aims to optimize for programmer happiness.
Alec Ten Harmsel
Grand Rapids, MI, United States
Member since March 7, 2019
About the author
Alec ran an autonomous drone team in college before graduating to work on software in banking and construction management
Expertise
PREVIOUSLY AT