Software Development with Anki Books Software Development with Anki Books Study cards

Software Development with Anki Books


It makes sense to me that Anki Books should explain the technical knowledge needed to run it or make something like it using itself as a code example. I believe in the Rails way of teaching, and why should a learning software not explain what it does in an interactive way, so that anyone interested in using it can get up and running?

It goes without saying that no book will make you proficient in programming without you writing some programs on your own. I will do my best to explain what I think is important to be able to move on and do things independently. If you find mistakes, please let me know.
What to do if you don't know how to solve a problem right now?
What does John Ousterhout say is the most fundamental problem in CS (he disagrees with Donald Knuth)?
What kind of memory can flashcards only help you with to an extent because it needs literal practice?
What did John Ousterhout say about what Donald Knuth believes is the most fundamental problem in CS?
What idea did many people think was an unfortunate course of Donald Knuth's career but had some good ideas popular today that are used in tools that extract API documentation from code?
What tool often used with Ruby allows writing tests that specify the behavior of the application from the outside using statements that begin with Given, When, and Then that are translated into code according to step definitions?
What might be the implementation of a step definition that visits the root path of the web application using the Capybara API?
What language is essentially English if you start every sentence with one of these words: given, when, then, and?
What gem/domain-specific language can be used to automate a web browser with the same API whether the underlying driver is :rack_test or the Selenium WebDriver API?
What MIT press book is called the "Wizard Book?"
Software Development with Anki Books Cucumber Study cards

Cucumber 🥒🥒🥒🥒🥒🥒


Cucumber lets you write test code as English statements that start with the Gherkin words: Given, When, and Then. Anki Books sometimes uses And to substitute for a consecutive Given, When, or Then. This means that Cucumber is a tool that enables nontechnical people to write feature tests or specify the interaction with the software from the point of view of a user that can accurately dictate the actions of the program. For this to work, the programmers must also set up a mapping between the patterns of the grammar in the statements starting with Given, When, and Then. The mapping between Gherkin statements and execution of the code in Cucumber code is specified using step definitions which are code.

Example: some step definitions


Note these step definitions do not and typically would not all be in the same file. Usually, they would be organized by the type of action or by domains and features of the software.

With a Ruby on Rails app that is using Cucumber step definitions to automate a web browser, it is common to use the domain specific language or API from the Capybara gem. Capybara provides a common API to automate the web browser whether Capybara is using Selenium Webdriver or :rack_test under the hood. Anki Books uses both of these drivers--:rack_text is faster but cannot execute JavaScript. The Capybara DSL is used to drive the web browser through the Anki Books test instance by clicking links, filling in and submitting forms, and dragging and dropping stuff around to prevent regressions in the user interface.

When "I visit the root path" do
  visit "/"
end

Given "there is a user with username {string}, email {string}, and password {string}" do |string, string2, string3|
  User.create!(username: string, email: string2, password: string3)
end

When "I click the {string} button" do |button|
  click_button button
  sleep 0.5
end

Some methods added by the Capybara API in the above step definitions are visit and click_button. A path is a part of a URL that many users of the web are familiar with. User in the code above is the Active Record model User in Anki Books.

Example: Cucumber test and step definition for visiting the homepage of Anki Books


An example Cucumber feature test:

Feature: The website homepage
  
  Scenario: Visiting the homepage
    When I visit the root path
    Then I should see the homepage

This is the step definition for the last Gherkin statement in the above:

Then "I should see the homepage" do
  expect(page).to have_content("This is the system article to serve as the homepage.")
end

This syntax is also related to RSpec.

Example: Cucumber feature test dismissing a flash message


@javascript
Feature: Flash messages

  Scenario: Dismissing an alert flash message
    Given I visit the root path
    And there is a user with username "test_user", email "test@example.com", and password "123abc777www"
    When I click the "Login" link
    And I fill in the "Email" field with "test@example.com"
    And I fill in the "Password" field with "wrong_password"
    And I click the "Log in" button
    And I click the "Dismiss" button
    Then I should not see "Invalid email or password"

The @javascript tag above the test indicates to Capybara that it should use the Selenium Webdriver API which supports JavaScript (:rack_test does not support JavaScript). This means that the feature tests without the @javascript tag will use :rack_test by default which is a little faster. This test may not even need JavaScript to execute but the performance cost of using Selenium WebDriver a couple times unnecessarily is not that much.

This example does also show what I meant with using And instead of repeating one of the other Gherkin words over and over.

I hope it's clear intuitively how the Ruby code in the step definitions corresponds to the Gherkin statements.
What driver can Capybara use out of the box but cannot execute JavaScript (written as a Ruby symbol)?
What define how tests written in Cucumber/Gherkin are translated into Ruby code?
What is the driver supported by Capybara out of the box that cannot execute JavaScript?
What gem do you need to install (in addition to capybara) if you want to use Capybara with Selenium?
What is the method in Capybara which takes a single string parameter and navigates to that path by making a GET request?
What method in the Capybara API documentation can take a snapshot of the page as it currently is and then open it so you can take a look at it?
If you have required capybara/rails, what will Capybara.save_path default to?
Does Capybara locate non-visible elements, by default?
What tag is written over a Cucumber scenario (or feature) to switch to the Capybara.javascript_driver (:selenium by default)?
What is the Capybara gem (the name of the gem to add to the Gemfile)?
In addition to creativity, the only ability a person needs to program computers is what according to John Ousterhout in APOSD?
Software Development with Anki Books Capybara Study cards

Capybara


Configuration


Capybara.register_driver :selenium_chrome do |app|
  options = Selenium::WebDriver::Chrome::Options.new
  options.add_argument("--headless=new")
  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = :selenium_chrome

The above shows how to configure the JavaScript feature tests to use a headless Google Chrome to automate the browser. Headless means that the web browser will be running in the background. Sometimes I use the term headed to refer to a web browser that has an open GUI, but I may have made that word up.

Example: debugging step definitions


# Saves a screenshot to tmp/capybara
Then "screenshot" do
  @i ||= 0
  @i += 1
  page.save_screenshot("screenshot#{@i}.png", full: true)
  puts "Page screenshot saved to screenshot#{@i}.png"
end

Then "show JavaScript console" do
  puts page.driver.browser.logs.get(:browser)
end

# rubocop:disable Lint/Debugger
Then "pry" do
  binding.pry
end
# rubocop:enable Lint/Debugger

This is some debugging code used to debug test code. I'm not a big believer in test code test code (code being used to test test code). The ||= is a null-coalescing assignment that assigns the instance variable here @i to 0 if @i is nil. It saves screenshots to anki_books/tmp/capybara.

This is just a random file I had in my development working directory for Anki Books in tmp/capybara called screenshot1.png. It is just showing a screenshot from when I was debugging a failing test (probably from weeks ago). It is considered by Git to be an ignored file (see .gitignore) so you will not get it if you clone the repo. I think that when I added the HTML attribute required to some <input> elements with type "text" that does client-side validation of the form. The test was failing because it was no longer possible to submit the form using a web browser (that's not made specifically for malicious actions). This is good because it prevents user frustrations and helps not congest the entire Internet (kids are trying to play video games, adults are trying to video call).


As an experiment to demonstrate the dependency on these debugging steps to Selenium WebDriver, I added Then screenshot to a feature test that had the @javascript tag, removed that tag, and executed the test:

$ bundle exec cucumber features/tests/books/dragging_basic_notes_between_articles.feature
Using the default profile...
# Anki Books, a note-taking app to organize knowledge,
# is licensed under the GNU Affero General Public License, version 3
# Copyright (C) 2023 Kyle Rego
Feature: Creating an article

  Background: Adding an article to a book                                                    # features/tests/books/dragging_basic_notes_between_articles.feature:7
    Given there is a user "test_user", email "test@example.com", and password "1234asdf!!!!" # features/step_definitions/user_steps.rb:16
    And the user "test_user" has a book called "My first book"                               # features/step_definitions/books_steps.rb:7
    Then screenshot                                                                          # features/step_definitions/debugging_steps.rb:8
      Capybara::Driver::Base#save_screenshot (Capybara::NotSupportedByDriverError)
      <internal:kernel>:90:in `tap'
      ./features/step_definitions/debugging_steps.rb:11:in `"screenshot"'
      features/tests/books/dragging_basic_notes_between_articles.feature:10:in `screenshot'
    And the book "My first book" has the article "test article 1"                            # features/step_definitions/article_steps.rb:10
    And the article "test article 1" has 2 basic notes                                       # features/step_definitions/article_steps.rb:15
    And the book "My first book" has the article "test article 2"                            # features/step_definitions/article_steps.rb:10
    And the article "test article 2" has 2 basic notes                                       # features/step_definitions/article_steps.rb:15
    And I am logged in as the user "test_user" with password: "1234asdf!!!!"                 # features/step_definitions/user_steps.rb:7
    And I click the "Books" link                                                             # features/step_definitions/shared/link_steps.rb:7
    And I click the last "My first book" link                                                # features/step_definitions/shared/link_steps.rb:12
    And I click the "My first book" link                                                     # features/step_definitions/shared/link_steps.rb:7

With :rack_test driver that does not attempt to execute JavaScript, capybara throws an exception called Capybara::NotSupportedByDriver.

Aside on Ruby constants


If the identifier in a Ruby program starts with a capital letter, it is a constant. Looking at Capybara::NotSupportedByDriver, Capybara is a module being used as a namespace for a class called NotSupportedByDriver. Modules and classes in Ruby are constants. It is common for a gem to namespace all of its API inside a module with a name closely related to the gem name. For example, the Anki Record gem uses a namespace AnkiRecord. If we do a quick git clone git@github.com:teamcapybara/capybara.git, cd capybara, and code . to open it in Visual Studio Code, we can see some related code in capybara.rb:

# (... means I omitted code)
module Capybara
  ...
  class NotSupportedByDriverError < CapybaraError; end
  ...
end

Example: a step definition that uses a regular expression


Regular expressions are pretty hard so I will try to sprinkle some examples throughout. I wouldn't spend too long trying to understand this if it makes no sense.

This step definition from capybara_steps.rb in capybara is similar to an example you will also find in Anki Books:

When(/^I visit the (?:root|home) page$/) do
  visit('/')
end

Regular expressions in Ruby can be created using the Regexp.new constructor and a few different literal syntaxes (/pat/ and %r{pat} are examples of Ruby regex literals). The ^ matches the beginning of the line, and $ matches the end of the line. This is different from using \A and \z which match the start and end of a Ruby string, respectively. The distinction could be important because $ could be exploited as an attack vector (not here though, this step definition that is just part of test code would never be an attack point on any app). The ?: in the parentheses has the effect of making the parentheses be only used for grouping, and not capturing. This just relates to how the parentheses has multiple purposes in regular expressions and here it's only being used to group this part where the | is saying it can match either root or home. This regular expression would match either of these strings: "I visit the root page" and "I visit the home page."
What is the optional argument given to the RSpec it method to switch to the Capybara.javascript_driver (:selenium by default)?
Does the Capybara DSL vary with the specific browser and headless driver being used?
What is the fast, but limited driver that is the default used by Capybara and does not support JavaScript?
What is the line of code to make Capybara use :selenium as the default driver instead of :rack_test?
Software Development with Anki Books Software testing Study cards

Software testing


Software testing is a really important part of software development. If software is not tested, it is bound to fail. Even carefully tested software has some faults. I tested Anki Books really carefully and still discover subtle bugs every once in a while.

RSpec tests of the FixOrdinalPositionsJob


I have had several times introduced slightly incorrect logic that resulted in articles having notes that had an invalid ordinal position set. For example, an article with notes at positions 0, 1, and 3 instead of 0, 1, and 2. Because I cannot figure out an efficient way to prevent this from ever happening, I developed a class called FixOrdinalPositionsJob:

##
# Job to fix ordinal positions in case they become incorrect due to a bug
class FixOrdinalPositionsJob < ApplicationJob
  ##
  # Fixes ordinal positions of all the articles' basic notes and books' articles
  def perform
    Book.find_each do |b|
      unless b.correct_children_ordinal_positions?
        b.articles.order(:ordinal_position).each_with_index do |articl, i|
          articl.update!(ordinal_position: i)
        end
      end
    end

    Article.find_each do |a|
      unless a.correct_children_ordinal_positions?
        a.notes.order(:ordinal_position).each_with_index do |note, i|
          note.update!(ordinal_position: i)
        end
      end
    end
  end
end

It has a comment above it that is pretty consistent with my paragraph above. I never know what to call these comments that are written above functions and extracted from the code as literate documentation. In the context of Ruby, you could call them RDoc comments and in general I think I've heard docstrings, function signatures, and I think they're good for public API documentation. Avoid using them for private API documentation. Anyway, you may notice that books also have ordinally positioned children (articles). This class can be invoked with the perform_now class method because it is a derived application job. It is a class that inherits that interface from the parent class, and by implementing its perform instance method, the perform_now method will call the perform method for you. This is related to the Active Job part of Rails. By the way, the logic of the above job is only correct because of a check constraint enforced on the PostgreSQL database that ordinal position integers cannot be negative.

Anki Books uses RSpec which is a good Ruby library (multiple gems) for testing that emphasizes the tests as specifications (RSpec tests are called specs a lot). Here are some RSpec tests for the above job class:

# frozen_string_literal: true

RSpec.describe FixOrdinalPositionsJob, ".perform" do
  subject(:fix_ordinal_positions_job) { described_class.perform_now }

  context "when a book has one article with an incorrect ordinal positions" do
    let(:book) { create(:book) }

    before do
      create_list(:article, 2, book:)
      bad_ord_pos_article = book.articles.second
      bad_ord_pos_article.ordinal_position = 2
      bad_ord_pos_article.save(validate: false)
    end

    it "fixes the ordinal positions" do
      expect(book.correct_children_ordinal_positions?).to be false
      fix_ordinal_positions_job
      expect(book.correct_children_ordinal_positions?).to be true
    end
  end

  # ... a lot of specs are omitted
end

Cucumber tests of the drag and drop functionality


Anki Books also has an automated feature test suite that uses Cucumber, Capybara, Selenium, :rack_test, chromedriver, etc. that allows writing the tests in plain English to drive the web browser through the app.

# Anki Books, a note-taking app to organize knowledge,
# is licensed under the GNU Affero General Public License, version 3
# Copyright (C) 2023 Kyle Rego

@javascript
Feature: Reordering basic notes

  Background:
    Given there is a user "test_user", email "test@example.com", and password "1234asdf!!!!"
    And the user "test_user" has a book called "test book 1"
    And the book "test book 1" has the article "test article 1"
    And the article "test article 1" has 5 basic notes
    And I am logged in as the user "test_user" with password: "1234asdf!!!!"
    And I am viewing the article "test article 1"

  Scenario: Reordering one basic note with drag and drop
    When I drag the note at position "0" to the dropzone at position "3"
    Then the front of the note at position "3" should be "Front of note 0"
    When I refresh the page
    Then the front of the note at position "3" should be "Front of note 0"

  Scenario: Reordering notes several times to reverse the original order
    When I drag the note at position "4" to the dropzone at position "0"
    And I drag the note at position "1" to the dropzone at position "4"
    And I drag the note at position "3" to the dropzone at position "1"
    And I drag the note at position "3" to the dropzone at position "2"
    Then the front of the note at position "0" should be "Front of note 4"    
    And the front of the note at position "1" should be "Front of note 3"
    And the front of the note at position "2" should be "Front of note 2"
    And the front of the note at position "3" should be "Front of note 1"
    And the front of the note at position "4" should be "Front of note 0"
    When I refresh the page
    Then the front of the note at position "0" should be "Front of note 4"
    And the front of the note at position "1" should be "Front of note 3"
    And the front of the note at position "2" should be "Front of note 2"
    And the front of the note at position "3" should be "Front of note 1"
    And the front of the note at position "4" should be "Front of note 0"


Selenium tests of the top nav keyboard accessibility


To do even more thorough testing, Anki Books also has a .NET Selenium NUnit project that also uses an automated web browser to test the app.

namespace Tests;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using Tests.Extensions;
using Tests.Interfaces;

[TestFixture]
public class TopNav : AppTests
{
    [Test]
    public void Test()
    {
        driver.Navigate().GoToUrl("http://localhost:3000/");
        driver.Manage().Window.Size = new System.Drawing.Size(948, 1003);
        driver.TryToLoginWithClick("test@example.com", "1234asdf!!!!");
        driver.DanceOnTopNav();
    }
}

Here is the extension method DanceOnTopNav() of the IWebDriver interface, implemented by the ChromeDriver class that represents a driver that can be controlled using the Selenium WebDriver language bindings, used in the above:

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Interactions;

namespace Tests.Extensions;

public static partial class ChromeDriverExtensions
{
    /// <summary>
    /// Tabs and shift tabs in a pattern over the top nav
    /// </summary>
    /// <param name="driver"></param>
    public static void DanceOnTopNav(this IWebDriver driver)
    {
        driver.PressTabUntilOnText("Logout");
        driver.PressShiftTabUntilOnText("Read");
        driver.PressTabUntilOnText("Downloads");
        driver.PressShiftTabUntilOnText("Read");
        driver.PressTabUntilOnText("Books");
        driver.PressShiftTabUntilOnText("Read");
        driver.PressTabUntilOnText("Concepts");
        driver.PressShiftTabUntilOnText("Read");
        driver.PressTabUntilOnText("Write");
        driver.PressShiftTabUntilOnText("Read");
    }
}


At every level, tests prevent bugs before they occur (it is better for mental health to spend time testing instead of debugging), prevent bugs before they damage data and annoy users, drive the design (test-driven design) because you need to use interface of the code in the test code, and also provide documentation that is somewhat more reliably correct by it being passing tests.

There can be downsides with testing too if it is not done in a way that the benefits are worth more than the maintenance costs of the tests. Manual testing is error-prone but also time consuming. Other ways that testing can bring downsides are when the test suite takes forever to run which slows development, the short-term time investment of writing tests, and time spent maintaining tests that break naturally as the app refactors.

The best approach to testing is to automate as much as possible. This is mainly done by writing test code, code that tests the code.

In general, tests should ensure that the correct output or behavior happens for valid inputs, and that the software behaves appropriately for invalid inputs. User inputs should not be trusted to be valid. Sometimes even the application database cannot be trusted to always have valid data. Unit tests are good to make sure the app is having acceptable behavior for exceptional conditions. The heavy-duty tests that drive a web browser just need to focus on the happy path for the most part.

Summary: Test!
What word is a common synonym for a fault?
Good programming leads to more time spent doing what than what?
Why is testing early a good idea?
According to Code Craft, studies show that there are how many errors per 1000 SLOC in carefully tested software?
What advocates that test code is written before the code being tested?
According to Code Craft, what is the golden rule of testing?
According to Code Craft, how often should tests be run?
What will happen in macro level testing if micro level testing is not thorough?
What is the comment to put at the top of a Ruby source code file to enforce frozen string literals?
Software Development with Anki Books Selenium Study cards

Selenium 


Selenium is a somewhat modularly composed toolset for automating web browsers that is built on the WebDriver spec that browser vendors should adhere to. Oftentimes in Rails apps like this one, Selenium WebDriver is an API used by the Capybara gem to allow specification of automating the web browser using a domain language that also can be used to drive the web browser using a few other open-source browser drivers. To use Selenium WebDriver, you need to install the language bindings. This is similar to how Anki can use Qt with Python instead of C++ (it does this using the PyQt binding).  For Ruby, the Selenium WebDriver bindings are installed as a gem called selenium-webdriver. A related gem is webdrivers which can fetch the chromedriver provided by the browser vendor that is used by the Selenium WebDriver bindings. With the newest versions of selenium-webdriver, you may not need webdrivers anymore.

Here, we will look at Selenium WebDriver using C# instead of Ruby because I think it will add a lot to the overall explanation.

Example: Logging into Anki Books


namespace Tests;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

[TestFixture]
public class LoggingInAsTestUser {
  private IWebDriver driver;
  public IDictionary<string, object> Vars {get; private set;}
  private IJavaScriptExecutor js;

  [SetUp]
  public void SetUp() {
    driver = new ChromeDriver();
    driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2);
    js = (IJavaScriptExecutor)driver;
    Vars = new Dictionary<string, object>();
  }

  [TearDown]
  protected void TearDown() {
    driver.Quit();
  }

  [Test]
  public void Test() {
    driver.Navigate().GoToUrl("http://localhost:3000/");
    driver.Manage().Window.Size = new System.Drawing.Size(948, 1003);
    driver.FindElement(By.LinkText("Login")).Click();
    IWebElement emailInput = driver.FindElement(By.Id("email"));
    IWebElement passwordInput = driver.FindElement(By.Id("password"));
    emailInput.SendKeys("test@example.com");
    passwordInput.SendKeys("1234asdf!!!!");
    driver.FindElement(By.Id("login")).Click();
  }
}

This could be a shared thing for tests that need a user to be logged in. driver.Quit() is what ends the test by closing the web browser which you can watch as it runs the tests if you want. If you have the app running locally on localhost and port 3000, then this test has a chance to pass because it will immediately fail if the browser can't even get there. By calling FindElement on the driver, we can find an element on the page via an identifier that can be thought of as a CSS selector. If you call FindElement on a different element on the page (an object that implements the IWebElement interface) then you can scope the search to only descendents of that one. FindElements is similar to FindElement but it returns a container of all the elements that matched instead of one. A common way tests like these could fail is that a method like FindElement found multiple, and not a single element. The selectors here include one that matches a link (or HTML <a> element) by text content and three that use an id selector. Id selectors are useful for this kind of thing and if web standards are being followed, every id on the web page should be unique. The SendKeys() method is part of the Actions API which is a slightly lower-level way to control the browser in Selenium WebDriver. It is pretty much just shorthand for using the KeyDown() and KeyUp() methods which do exactly what they sound. Those ones can take members of the Keys enum as arguments (Keys.Tab is an example). I believe calling Click() on an IWebElement will click it in the middle. 

What gem installs the Selenium bindings for Ruby?
Why are the browser drivers generally not included in the standard Selenium distribution?
What refers to both the language bindings and the implementations of the individual browser controlling code in Selenium?
What is a specific WebDriver implementation that each web browser is backed with?
Software Development with Anki Books Docker Study cards

Docker


Docker is a tool that makes getting up and running with developing software including web applications easy, and it can also make deploying the software to servers easier too. The dependencies and the way they interact with each other as services is described in the project code itself. The Dockerfile is the instructions to Docker to create an image for the application logic code. The image then creates a container, which is sort of a runtime environment for the application logic, with its own filesystem that you can get a Bash shell inside of to look around if you want. Docker Compose is a tool that starts up multiple containers at once as a network of services. Anki Books does have a Dockerfile and docker-compose.yml file for Docker Compose, and I have been able to use the app in containers locally.

This is the Dockerfile at time of writing:

FROM ruby:3.2.1

RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends libvips libvips-tools

COPY . /usr/src/app

WORKDIR /usr/src/app

RUN bundle install

CMD ["passenger", "start"]

It starts by downloading an existing image with ruby 3.2.1 that the image it is describing will extend. It installs some stuff including libvips which is a dependency for Active Storage in Rails. It copies the working directory at the location of the Dockerfile in the host computer into the filesystem of the image at /usr/src/app. The WORKDIR command is like cd in Bash, making that the working directory. Then it does a bundle install to install the Ruby dependencies and finally, starts up the application logic by invoking the application server, Passenger.

docker-compose.yml:

version: '3'

services:

  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
      - exclude_tmp:/usr/src/app/tmp
    environment:
      DATABASE_HOST: database
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: 1234asdf!!!!
      POSTGRES_DB: anki_books_development

  database:
    image: postgres:14
    volumes:
      - data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: 1234asdf!!!!
      POSTGRES_DB: anki_books_development

volumes:
  exclude_tmp:
  data:

This sets up two services (it is a network of two containers). One is for the application logic, and one is for the PostgreSQL database. The database service specifies the image to use by name, and the web service is using the Dockerfile it finds in the same directory. There is a mapping between port 3000 of the host machine and port 3000 of the container running the web service. When your web browser makes a request to your local machine IP address (127.0.0.1 or localhost) at port 3000 which is standard for a Rails app run locally in development, it is forwarded to the container, inside which has a Rails app listening at the same port. The volumes are named volumes and are persistent, shared places in the filesystem of the host machine and container that Docker manages for you. There are also environment variables being set, for example the name of the database, and the name and password of the database role that the application logic will use to connect to the database.

To understand the environment variables, this part of the config/database.yml is relevant:

default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: anki_books_development
  # The TCP port the server listens on. Defaults to 5432.
  port: 5432
  #
  # To use Docker Compose to run the app (`$ docker compose up`)
  # uncomment (including ERB <%# comments) and use the following:
  #
  # host: <%# ENV.fetch("DATABASE_HOST") %>
  # database: <%# ENV.fetch("POSTGRES_DB") %>
  # The specified database role being used to connect to postgres.
  # To create additional roles in postgres see `$ createuser --help`.
  # When left blank, postgres will use the default role. This is
  # the same name as the operating system user running Rails.
  # username: <%# ENV.fetch("POSTGRES_USER") %>
  # The password associated with the postgres role (username).
  # password: <%# ENV.fetch("POSTGRES_PASSWORD") %>

The issue I had with Docker was my computer having only 256 Gb of storage. I am using the Windows Subsystem for Linux 2, and it seems to use more storage over time and not give it back, until I do this manual thing that gives me back about 15 Gb from it. I uninstalled a lot of programs to get storage and Docker Desktop was too juicy to not uninstall. I may have been using it in a way that made it use a lot of storage though.
What in Docker can be thought of as just an empty vessel for executing software in?
What is an object-oriented programming metaphor for containers and images in Docker? (Images = ?, containers = ?)
What is the default user inside of a Docker container?
What command is essentially the Docker CLI?
On what operating systems must the Docker daemon be run inside a lightweight Linux virtual machine?
Software Development with Anki Books Computer networking

Networking


What is the Internet?


The Internet is a really big network of computers (the really big one). A network of computers is some computers sending messages to each other. The Internet relies on protocols, which are algorithms implemented as computer programs which specify the rules used by the computers for communication. The protocols of the Internet also form a stack conceptually, and there are different overlapping versions of this model that have different names.

The lowest layer is the physical layer and could involve electromagnetic waves. Then there is a link layer (devices: switches; identifiers: MAC addresses), an Internet layer (protocol: IP; devices: routers; identifiers: IP addresses), a transport layer (protocols: TCP, UDP; identifiers: port numbers which identify processes), an application layer (protocols: HTTP), and even a session layer (protocol: TLS).

The link layer


The link layer sits on top of the physical layer and includes switches, devices operating here that forward Ethernet packets based on destination MAC addresses.

The Internet Protocol Suite


The two most important protocols are TCP (Transport Control Protocol) and IP (Internet Protocol). Together, TCP/IP is referred to as the Internet Protocol Suite.

The Internet layer



Above the link layer is the Internet layer, which has routers instead of switches. The routers use a routing table, which is set up by some other protocol unrelated to these, to send packets toward other routers that are closer to the target and know more about the entire network closer to the target. It makes no guarantee that it will arrive, or that it will arrive in order, but it makes a best effort attempt.

The transport layer


The two main protocols of the transport layer are TCP and UDP. UDP is pretty much just a stripped-down version of TCP that is used sometimes to bootstrap custom protocols in this layer.

TCP


TCP is on top of IP and guarantees that they are interpreted as being intact and in order. This introduces a downside called head-of-line blocking. If 5 packets get sent and the first is dropped somewhere, then the 4 packets that have already arrived must wait for the first packet to get resent (and it always will get resent when it gets dropped, because that is part of what TCP does to guarantee the complete data arrives).

The TCP handshake

A TCP connection is started with a handshake. The client sends a SYN packet, and then the server sends a SYN ACK packet, and then the client sends an ACK followed immediately by application data which means one roundtrip of messages has to occur before application data is transmitted. 

UDP


UDP is an alternative protocol that can be used for things like video games where things are happening too fast to wait for a single dropped packet to get resent. A distinction to make between IP and TCP is that IP enables packets to be sent between computers (host-to-host), and TCP enables segments to be sent between processes.

Processes


TCP enables segments to be sent between processes, which are identified here with port numbers. There are some port numbers that are good to know, like 80 which is standard for the web server process waiting for HTTP requests. If HTTPS is being used instead of HTTP, the web server listens on port 443.

A similar but different thing to a process is a thread. A process will always have a primary thread, and it can spawn worker threads. When you have worker threads, it's possible for the process to no longer be thread-safe. This is a consideration with Sidekiq, a library (used with Rails) for queueing jobs that uses worker threads to do the jobs.

The application layer


The main protocol of the application layer is HTTP (Hypertext Transport Protocol). This is the most important layer to keep in mind when developing a web application like this one. When TLS is added to HTTP, it is called HTTPS.

HTTP stands for Hypertext Transport Protocol. The markup language used to describe the structure of a website as a document is HTML, which stands for Hypertext Markup Language.

The client-server pattern is a very common pattern where a client sends a message to a server, which can do something, and then sends a message back to the client. HTTP involves the client-server pattern/architecture. Databases also use the pattern, which is exhibited when you use a client program CLI like psql or sqlite3 to send SQL queries to the database server which then gives you a syntax error (if your query was syntactically correct, it will be the data you asked for). The docker CLI is also sending messages to a Docker daemon which does the heavy lifting.

The client sends an HTTP request to the server which responds with an HTTP response, a clear example of a client-server interaction. The client is the web browser sending the message on behalf of the user, and the server is a program called a web server running on a host computer. The requests and responses are both HTTP messages which have status codes, data parameters (URL parameters such as in the query string and POST data in the body), HTTP headers, and bodies. There are some differences between HTTP requests and responses in terms of what is required for it to be a valid one.

The web server might be Nginx or Apache. WEBrick is another simple web server that you can use right away after installing the rails gem and using the rails new rails_app_name command (the command to start the new Rails app with WEBrick is bin/rails server). A web server like Apache can be used to serve static assets. Conventionally the asset served to a GET request to the root path / is an HTML file called index.html. Instead of serving static assets, the web server can be communicating with an application server and Ruby on Rails application logic. 

The web server is listening on port 80 or port 443 depending on if it's HTTP or HTTPS. It receives a message which is an HTTP request. It just passes the HTTP request to the Ruby on Rails web application. The application then has an opportunity to send commands to the database and make network calls which are HTTP requests to other servers. This can be done synchronously or asynchronously. For example, the primary thread of the program could spawn a worker thread to go send commands to the database and synchronously wait for it to complete. It could also send off a job to do it asynchronously. It may only put the job in a queue of jobs that worker processes/threads pick up as they get capacity too. Be careful not to press any "virus attack" buttons that cause too many jobs to get sent at the same time. Network calls can also be done synchronously or asynchronously with a design pattern like the reactor where it arranges for the response to be handled in some way when it arrives back later.

Anyway, so all that can happen, and many other things can happen too (like language models, emails, sending messages to client devices like mobile phones) and you probably forgot that the server needs to after all this, send the HTTP response to the client that sent it the HTTP request in the first place. The server also would have looked at things like the session in the HTTP response which could have stored an identifier for the user, parameters in the URL including the query string and POST data (the HTTP request body of a HTTP request with the POST method). The method of an HTTP request is an important thing to know about. GET requests are the standard ones your web browser sends when you click a hypertext link. Clicking the link causes the GET request to get sent to the server identified by that URL, which includes a domain name. The domain name identifies the host (a computer) by being a human-readable alias of the IP address of the host computer. Remember: the IP protocol enables host-to-host communication. IP addresses are the way that the computers are identified. Inside the computer, the processes are identified by port numbers. TCP enables process-to-process communication. It is in this transport layer, that sits above the Internet layer (which as you will remember, is on top of a physical layer that involves waves both electric and magnetic in nature depending on the motion of the reference frame (electromagnetic waves--see Griffith's Electromagnetic Theory)). 

What is the World Wide Web?


The web is what you access using a web browser like Google Chrome or Microsoft Edge. It is all the websites that you get to see when you use Google. Google is by far the most dominant Internet search engine. It maintains an index, not unlike the index at the back of a textbook, of all of the web pages. If you remember from reading a textbook, there is a section at the end of it which has all of the key terms of the textbook in alphabetical order and accompanied by all the page numbers that you can see the key term discussed on. Google's index is a similar data structure that allows Google to efficiently search this huge amount of information for only things that will be relevant for your search. You often will find Google's index talked about in terms of SEO (Search Engine Optimization) which is the practice of trying to get your website ranked high in Google search results. You can tell Google not to index your website too. I use the Google Search Console to see how Google perceives my website. To do that, I just put an HTML tag in this website which Google uses to collect useful data for me. They might find the data useful too, I'm not sure. It is possible a "retrieval structure" (in a sense of the cognitive approach to memory based on a deep understanding of an activity) allows someone to be really good at that activity (having fast and nuanced thinking, perceiving more, knowing and remembering more, and making complex decisions quickly) is a similar kind of thing to an index.

Ruby is a programming language that might also be called a scripting language. It is mainly used for rapid web application development (e.g., gluing a database to a website) with the Ruby on Rails framework. Ruby and Rails are used for the server-side (also referred to as back-end) application logic that accepts the data submitted by the web browser (as the user agent uses the website) and after consideration of that data, interacts with the database and then sends a response to the user to be interpreted as the website in the browser. The database is managed by PostgreSQL, an impressive open-source object-relational database management system. Explicit SQL statements are scarcely used in the application logic here because Rails provides an object-relational mapping tool called Active Record (apparently an implementation of the active record pattern) which allows using the domain-specific language of Active Record instead.

Since the user interface is a website, the three main languages of front-end web development, HTML, CSS, and JavaScript, are being used. HTML describes the structure of the website and is really pretty straightforward for the most part. CSS describes how the HTML is displayed and is concerned with colors, fonts, sizes, placement of things, and a lot of other things too. At times it may seem to encroach on what JavaScript is concerned with, which is client-side interactivity. This is programming where the code is executing on the website without a round-trip message to the server (although JavaScript can make requests to servers too). The flashcards on this page implement this feature which uses a little JavaScript: clicking or tapping on them hides or reveals the back. These fundamental technologies of the web are enhanced and built upon by additional tools and frameworks. For example, the HTML is created on the server from ERB (Ruby embedded in HTML) files, almost all of the CSS classes are provided by a framework called Tailwind CSS, and the JavaScript is mostly written inside a modest JavaScript framework called Stimulus. Stimulus is one part of a larger framework called Hotwire along with Turbo, which is also being used here but it is more related to HTML.

Linux


Some videos about Linux:
Software Development with Anki Books CSS

CSS


!Anki Books uses CSS with no framework and instead uses the power of native cascade layers (eek)


Cascading Stylesheets is a stylesheet language mostly used in web development to style HTML and other dialects of SVG. The most important concept in CSS (related to the first word in CSS) is probably the {{c1::CSS cascade}} which begins with the three origins of stylesheets: {{c2::user agent stylesheets}}, {{c3::author stylesheets}}, and {{c4::user stylesheets}}. A web developer writes CSS in {{c1::author stylesheets}} which cascade over {{c2::user agent stylesheets}} by default. A user, maybe someone who has particular preferences or limitations, can use {{c1::user stylesheets}} to change the presentation of a web page cascading over {{c2::author stylesheets}}. If you limit yourself to considering the cascade in only the stylesheets that a web developer writes, the highest division of CSS that determines the cascade is the {{c1::cascade layers}}. {{c1::Specificity}} which has to do with CSS selectors and is thought of as tuple values similar to ones, tens, and hundreds places, becomes relevant when you are considering cascade in one single cascade layer.

Terminology of CSS


A CSS ruleset looks like this:

.p-1 {
  padding: 0.25rem;
}

The .p-1 is a class selector and specifies that an HTML element with "p-1" in its class attribute will have the declarations in the associated ruleset applied to those matching elements. The naming of CSS classes in Anki Books is heavily influenced by Tailwind because originally it was a Rails app scaffolded using the option for that CSS framework and was developed using it for about 7 months.  In Tailwind, the CSS class p-1 is pretty much equivalent to the inline CSS attribute style="padding: 0.25rem;" and this adds a distance (equal to one-quarter of the font size in the root HTML element <html>) that surrounds the content to that element with the class. In the above code, the entire construct is a ruleset. padding is a CSS property, and 0.25rem is a property value denoting a length for that property. The entire CSS construct "padding: 0.25rem;" is a declaration. A ruleset can contain multiple declarations.

The padding CSS property


The padding is one of the boxes in the CSS box model. The CSS box model relates to how an HTML web page is presented visually as boxes that can be displayed as inline or block. With a typical block element like the HTML <p> element that semantically means paragraph, the padding is the box that surrounds the innermost box, the content box. The padding box is surrounded by a border box, which is then surrounded by a margin box. That outermost margin box can collapse on any side if it is adjacent to an element with greater margin there which is called margin collapsing. The exact form of the CSS box model can also depend on the box-sizing property. To say "padding: 0.25rem;" applies to an element specifies a padding on all four sides of the element's box equal in length to one-quarter of the font size of the root HTML element <html>.

The CSS box model


Boxes in CSS are a pretty hard topic relative to a lot of CSS and very important because every visible element on the web page is a rectangle. Sometimes web developers will apply "border: 1px solid red;" to every element on the page (selected by the universal CSS selector: *) to quickly see the boxes on the page. Every web element on the page has a content box surrounded by a padding box, that is surrounded by a border box, that is surrounded by a margin box.
A web developer writes CSS in [...] which cascade over [...] by default.
The most important concept in CSS (related to the first word in CSS) is probably the [...] which begins with the three origins of stylesheets: [...], [...], and [...].
[...] which has to do with CSS selectors and is thought of as tuple values similar to ones, tens, and hundreds places, becomes relevant when you are considering cascade in one single cascade layer.
A user, maybe someone who has particular preferences or limitations, can use [...] to change the presentation of a web page cascading over [...].
If you limit yourself to considering the cascade in only the stylesheets that a web developer writes, the highest division of CSS that determines the cascade is the [...].
Software Development with Anki Books Ruby programming

Ruby programming


Ruby is the programming language used for the back-end application logic in Anki Books. Ruby is a really sharp language and also very gentle (like Python's sister) used mostly for web development (Python is used for everything).

Local variables


Variables are pretty fundamental to programming, so I guess we start with them. This is what it looks like to assign a variable:

my_first_v = 1

This creates an identifier in the scope of the current object, which is an object called main if you're just writing a script.rb file to declare variables. It is a local variable called my_first_v that references an object in memory which is essentially data representing the number 1. If we do this:

my_first_v = 2

Now my_first_v references (or "points to") a different object in memory that represents the number 2. The first object that represented 1 can be collected out of the heap of memory being used by the garbage collector when it is detected that it is not needed anymore.

"Everything is an object in Ruby"


Variables are references to objects. In Ruby, everything is an object including data (there are exceptions to "everything is an object in Ruby" such as block arguments). The different types of data in Ruby such as numbers, strings, and lists (called arrays in Ruby) of data are all different types of objects. The type of an object is a class. An actual instance of a class is called an object. An object in Ruby has some internal private data as instance variables and a reference to its class which defines the methods that the class uses to respond to messages and operate on that private data. Variables are usually named to carry some useful information about the objects they reference. In Ruby, names of local variables should be snake_case.

Constant variables


Identifiers or names in the program that begin with a capital letter are constants and that includes the names of modules, classes, and constant variables. Constant variables are usually named using all capital letters but technically you can just start them with one capital letter. Static analysis of Ruby code where a constant variable is named like that would probably issue a warning.

String data type


str = "hello world"
num = 10

Common mistake: typing = instead of ==


If you are new to programming, take note that = is assigning a value to a variable; it is not comparing for equality like in algebra. == can be used to compare for equality in Ruby and it is a common mistake to type = instead of == in many different programming languages.

Declaring vs instantiating a variable


Declaring a variable in programming usually involves specifying a name and a type for the variable. With Ruby we do not need to specify a type and we can initialize or instantiate the value of the variable at the same time. Even in languages where you can declare a variable and initialize it later, it is best to give it a value as soon as it is declared. It is also good to declare variables as late as possible most of the time and in the tightest scope of the program that they are needed. Note that although we do not declare a type for the variable in Ruby, the object itself does have a reference to its class and therefore does have a type. The variable name can be reassigned to point to a different object and that new object does not have to be the same type as the previous value.

Methods


Methods in Ruby are defined using the def keyword. Methods take a number of named variables called parameters. The data objects that are called with the method and at the same time assigned to those parameters are called arguments. Sometimes people do not care about the difference so you might hear them used incorrectly.

Classes


Classes in Ruby are basically templates for creating objects. Instances of a class are generally called objects. When an object is created, it is given some initial data and a reference to its class which defines the expected data (as instance variables) and also the behavior (as methods). Instance variables are the variables in Ruby that start with @. It may be a good idea to always expose instance variables through a getter and setter even when using the instance variable inside the class where it is accessible without those. A quick way to create getters and setters in Ruby is with the class methods attr_reader, attr_writer, and attr_accessor that take the symbol name of an instance variable as an argument.

Example: Ruby block argument


Given "there is a user with username {string}, email {string}, and password {string}" do |string, string2, string3|
  User.create!(username: string, email: string2, password: string3)
end

In Ruby, there are 3 types of closures to know, and the main one to know is the block argument. The block argument to a method in Ruby is a closure because it captures all of the local artifacts in its environment and carries them into the method call. This means that inside the method call that receives a block argument, it can execute the code of the block argument it receives inside of its internal implementation. I would recommend anyone take some time and compare the Gherkin statements to the step definitions, even if you do not code because it is the fundamental thing to understand about Cucumber. The main thing that Cucumber lets you do is have executable documentation that stays up to date because it is a runnable test suite, and it also allows anyone on the team write technical documentation/specify a new feature. Cucumber is a tool related to the behavior-driven development approach where the program is specified outside-in rather than inside-out which would be more like test-driven development where you write unit tests first.
Software Development with Anki Books Git

Git


Git is probably the most popular version control software today. It helps collaboration with other programmers and tracks the changes happening in the code over time.

Difference between Git and GitHub


Git was originally developed by Linus Torvalds as a by-product of his having to manage the development of the Linux kernel. GitHub is a web application that uses Git and helps developers collaborate on software. It is common for Git and GitHub to be used even for short CS group projects in college. The source code for Anki Books is on GitHub; this is where you can get the source code if you want it. If you have Git installed, then you can clone the repo with the git clone git@github.com:KyleRego/anki_books.git command.

The git command


The Git command line interface is the git command commonly used inside a Bash shell, which is a program that provides an interface to the operating system (usually a Linux distribution) inside of a terminal emulator. I believe on Windows you can use Git Bash to get a version of that shell. To see the various git commands, use the git --help command, and the output will show common git commands. The --help is a long option, and many command line programs take this option to display helpful documentation about the program including how to get further help.

The git --help command


The output of the git --help command includes git init, which creates a new Git repository in the current directory. The git --help also outputs some information about the git clone command, which you could use to clone the Anki Books repository on GitHub (see GitHub docs for how to do that). This would include every version of the project all the way back to the initial "commit."

Introduction to the Git index and the Git object store


A Git repository has two main things: and index and an object store. When you clone a Git repository, you get the object store, but not the index. The index is a private data structure to a repository and tracks the changes that are getting ready for or "staged" for a new commit.

The four atomic data types of Git


Git has four atomic data types that make up the other types: blobs, trees, commits, and tags.

The simplest atomic Git type one is the blob, which represents a file.

The tree is the atomic Git type that represents a directory. Since a directory (directory is an older way to refer to "folder" by the way) itself contains files and directories, a Git tree references blobs and other trees.

A commit is an atomic Git data type that represents metadata about a change introduced to the repository, which includes the author of the commit. That the author of each commit is tracked is how the git blame command can be used to see who last modified some code. At the beginning of the Git commit history is the root commit (commonly called the initial commit in the first commit message), the only commit that does not have a parent or previous commit. Every commit after the root commit will be pointing back to at least one previous commit. A commit can have multiple parents in the case of merging multiple branches together.

Note while branches are central to how developers speak about Git, they are not one of the fundamental data types. The last fundamental Git data type is the tag, which might be used to label a commit.

The Git branch: a directed acyclic graph of Git commits


The branch can be thought of as a series of commits each pointing backwards to one or more previous commits until eventually it all reaches the root commit. A graph in general can be defined as a structure of nodes connected by edges. If at least one edge has a direction, it is a directed graph. Git involves a directed acyclic graph which is acyclic because there is no way to follow the edges in the correct direction to the root commit. Anki Books early on had the domains table with a many-to-many self-referential relationship to itself (which was an example of a graph too), and it was fine until I introduced a cycle which made my recursive SQL query to crash my Windows Subsystem for Linux.

The Git index


The Git index is an important thing to know about. Let's say you cloned a repo, created a new branch and switched to it using the git checkout -b <new_branch_name> command, made some changes, and wrote some new unit tests covering your changes. It is time to introduce your changes to a branch by making a commit. Before you commit your changes, you stage them in the index with the git add command. By using the git status command, you can see the state of changes in the working directory and index: what is tracked, untracked, and staged. Files that are ignored should not be visible in the output of the git status command. When you use the git commit command, it takes the files that are staged in the index and those changes are introduced. Usually after that, you might push your branch up to GitHub and open a "pull request" which would merge that change into a different branch, like one called main for example. A quick way to add all your changes to the index is to use the git add . command. The -m short option is usually used with git add to specify the commit message.

Merging branches


A merge is when two branches are combined, and in order to preserve the history of commits of the branches being merged, it introduces a merge commit. The resulting code does not reflect any branch more than any other branch that was involved in the merge. A linear history can be easier to reason about, so oftentimes people use the git rebase command to rebase their commits onto the target branch of the merge. This will rewrite the history of the branch being rebased such that the commits appear linearly after the target branch's head, which refers to the most recent commit on a branch. After this type of rebase, the merge will not introduce a merge commit because it is just doing a degenerate type of merge called a fast forward.

You can merge branches together on the command line with git merge: if you are on branch A and use git merge <branch_b_name> it will make a merge commit on branch A that is the result of a merge between the two branches. The git branch command with the -d short option can then be used to delete branch B safely. If you ever use the -d short option with git branch and it throws a warning, that means there is a commit in branch B that would be lost. Even if you did do that and realized you need the lost commit, you can turn to the reflog with the git reflog command.

Oftentimes merging branches is done on GitHub which provides a nice UI for code reviews and discussions. It is called a "pull request" and the merge will happen by clicking a button. The merge can happen between forks of the GitHub repository which are different remote versions of the repository on GitHub.

If you can use the git command on the command line, a good way to learn is to use the command line to get help. The git command takes a long option --help that you can use to get help: git --help. The different git commands also take the same long option to show help, e.g., git add --help, git commit --help.

Example: a commit


113 scenarios (113 passed)
1182 steps (1182 passed)
7m36.288s
Coverage report generated for Cucumber Features, RSpec to /path_to/anki_books/coverage. 3340 / 3342 LOC (99.94%) covered.
$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   app/controllers/books_controller.rb
        modified:   db/schema.rb
        modified:   spec/factories/books.rb
        modified:   spec/requests/books/show_spec.rb

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        db/migrate/20231026144625_add_public_to_books.rb
        db/migrate/20231026145631_update_books_public_to_false.rb
        db/migrate/20231026150352_add_not_null_to_books_public.rb
        db/migrate/20231026151536_add_default_public_for_books.rb

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .
$ git branch
* main
$ git commit -m "Add public books"
Running pre-commit hooks
Analyze with RuboCop........................................[RuboCop] OK

✓ All pre-commit hooks passed

Running commit-msg hooks
Check subject capitalization.....................[CapitalizedSubject] OK
Check subject line................................[SingleLineSubject] OK
Check for trailing periods in subject................[TrailingPeriod] OK
Check text width..........................................[TextWidth] OK

✓ All commit-msg hooks passed

[main f127946] Add public books
 8 files changed, 73 insertions(+), 3 deletions(-)
 create mode 100644 db/migrate/20231026144625_add_public_to_books.rb
 create mode 100644 db/migrate/20231026145631_update_books_public_to_false.rb
 create mode 100644 db/migrate/20231026150352_add_not_null_to_books_public.rb
 create mode 100644 db/migrate/20231026151536_add_default_public_for_books.rb
$ git status
On branch main
nothing to commit, working tree clean
Software Development with Anki Books Software Design Study cards

Software design


John Ousterhout, the designer of the Tcl scripting language and CS professor teaching a unique software design course at Stanford, wrote a nice book about complexity, modular design, information hiding, comments, etc. Sandy Metz wrote the "Clean Code" book of Ruby programming that is quite good especially for those programmers using Ruby, a language which is so gentle that systems can fail due to lack of design by inexperienced programmers. It is very practical and can easily be read a few times. Service-oriented design with Ruby is a not completely up to date book but it is a really good discussion of design concerns at a higher level especially of web services. Code Craft is a really entertaining book on programming that touches everything and has many thoughtful ideas about software that are relevant to design (code architecture as pasta).

Agile


Software design is an incremental process where a more ideal, consistent, organized, simple, and optimal design emerges as one gains experience in the system; a good philosophy or approach to allow that to unfold is Agile. With Agile, there is no big design up front of the entire system. An Agile approach might involve some development and then correcting problems with the design (technical debt or actual user-facing functionality) before the next development effort. Don't use Agile as an excuse to be lazy though; do not add unnecessary design technical debt to go faster unless there is a really good reason to. 

Module


A piece of code may be considered as a module, generally a method, function, class, service, system, API. In Ruby, a module is a type of constant that can be used to namespace other constants or as a mixin to extend multiple classes with the same functionality. Anki Books uses a module called Anki Record that is a Ruby library (gem), and all of its code is namespaced inside a Ruby module AnkiRecord. Module is also the superclass of Class in Ruby.

Interface vs implementation


It is important to know the difference between an interface and an implementation in software design and programming. The interface of a module is what the module is specified to do through how it responds to messages to it. The implementation of a module is the internal code of the module that carries out the functionality that the interface provides. In object-oriented programming, a class typically has a set of methods that are public and part of the class's public API and a set of methods that are private and are only callable from inside the class or from inside instances of the class. A web service may have a RESTful API that sends data to other applications and is an example of an interface of a much larger module that might be called a component.

It is more important for a module to have a simple interface than a simple implementation. It will be easier to reuse a module if it has an easy-to-use interface. An easy-to-use interface will also be easier to test and maintain the tests for.

The interface of a module is where dependencies will form on the module and is not the same as the literal public API that callers can invoke. The interface is everything that a dependency can form on which includes information that cannot be understood by the programming language.

API


An API is an application programming interface, and it is a pretty general way to refer to the interface of something related to application programming.

API documentation


A good way to incorporate some ideas of literate programming is API documentation extracted from specially formatted comments in the source code typically right above the methods that they are describing the API of. The source code is the only documentation that will always be present for a program: it should be made as good of documentation as possible. Sometimes a comment above a method explaining what it does is pretty unnecessary, but if you follow the habit then you will probably convey some information for other methods that is really useful later.

Here is an example from the Anki Record gem:

##
# Saves the deck to the collection.anki21 database.
def save
  collection_decks_hash = anki21_database.decks_json
  collection_decks_hash[@id] = to_h
  sql = "update col set decks = ? where id = ?"
  anki21_database.prepare(sql).execute([JSON.generate(collection_decks_hash), anki21_database.collection.id])
end

A comment in Ruby can be started with #. The comment above can be parsed out of the program as documentation of this method. The Rails API Documentation Guidelines explains some very good conventions around this that are used in the development of Ruby on Rails itself. In a development environment where code is being reviewed by other developers before merging into the main branch, code reviews are a good time to ensure that these specially formatted comments are up to date and accurate.

The North Star of software design: minimize complexity.


The most important thing for a piece of code is simplicity. The main objective of software design is minimizing complexity (see Ousterhout). Do not do something in the name of SOLID or any other design principle if the effect is to add more complexity.
What is the guiding star of software design?
Software Development with Anki Books Comments

Comments


Comments are parts of source code that the compiler ignores: they have no effect on the program to the computer. A lot of programmers would see any comment and consider them failures because good code "self-documenting." Unfortunately, as John Ousterhout points out in A Philosophy of Software Design, the code interface includes informal aspects that cannot be expressed in the programming language. It is true that writing code to be as self-documenting as possible is a good idea, but it is an unattainable ideal for all but the smallest units of code. Since the program itself is the only documentation guaranteed to be up to date, of course code should be written to be as readable as possible: the best way to design good code is for it be simple. Even so, there are times when the only way to make the code clearer is comments. Comments should be used describe why something is the way it is, not what which is what the code describes. Comments can be used to provide literate API documentation extracted from source code by following some conventions in the language (the tool for doing this in Ruby is RDoc).

See this example from the Anki Record gem which uses basic RDoc conventions:

module ChecksumHelper
  ##
  # Returns the integer representation of the first 8 characters of the SHA-1 digest of the +sfld+ argument
  def checksum(sfld)
    Digest::SHA1.hexdigest(sfld)[0...8].to_i(16).to_s
  end
end
Software Development with Anki Books RSpec

RSpec


RSpec tests, RSpec specs, and RSpec examples mean the same thing throughout this book.

In terms of scope, the RSpec examples in Anki Books are unit tests, component tests, and small integration tests. This terminology around classifying tests has different meaning to different people. Unit tests test the smallest units of the system in isolation and the smallest units are typically functions or methods that do not need to call other methods. Component tests test the combinations of units and units working together. Integration tests are then one level greater in scope than component tests. Remember this terminology is not universally agreed upon. You might call tests that show the system working correctly acceptance tests. You might call tests that interact with the application as a user agent like a web browser would feature tests. You might also say automated tests, tests that automate a web browser, and end to end tests. With Rails in particular, tests are often classified by what class hierarchy the class being tested belongs to. For example, there are model tests and controller tests (now request specs are preferred). There are also view specs, routing specs, etc.

Testing something in isolation can involve mocking the things that it interacts with. For example, if you have an object that sends a message to a different object and gets a value in return, that other object could be replaced by a double and the return value can be stubbed. The implementation of the real object's method might have its own unit tests elsewhere. This can be a tricky thing in practice as it is possible to change the API of something and break the production class while tests continue to pass because they rely on the test double which has not been updated. Dependency injection, the practice of injecting a dependency into an object rather than that object constructing the dependency itself, can make testing easier with or without using a double for the dependency.

An RSpec example is a test or spec that is defined using the RSpec it method. An RSpec example group is a group of examples defined using either one of the RSpec context or describe methods, which are aliases. To the machine, context and describe are the same method, but conventionally they are used in different ways. Keep in mind that the compiler is not the only audience for source code. Following conventions will help people in the future (including you) understand the intentions of the code. Using describe and context in the conventional way makes the design of the test code more explicit and following established conventions which are good characteristics.

The RSpec examples in Anki Books are divided into groups by directory structure. There are model specs, request specs (not controller specs), routing specs, and other types. You will see the model specs in spec/models, view specs in spec/views, etc. Anki Books also uses the convention of splitting the test code over the largest number of files that it would make sense to. The majority of test code files contain tests of only one method each.

Tests demonstrate correctness and provide a safety harness


Test code is not only used to test the correctness of the program for valid inputs and correct failure behavior of the code for invalid inputs at first write. Test code also serves as a safety harness, preventing regressions into the future at a cost of maintaining the test code. Keep in mind it is no possible to completely prove that there are no bugs by writing tests. You can prove that there are bugs though.

Test code can be written for additional purposes at the same time as it is used to show correctness of the program.

Tests provide executable documentation


Specs is short for specifications. That we say specs instead of tests when talking about RSpec emphasizes that these examples are not only for demonstrating some degree of correctness. RSpec test code provides reliable documentation of what the program does. When writing the string argument to the RSpec it and context methods, you should occasionally run the rspec command with the --format doc long option to see the documentation output, and do your best to make it a clear documentation.

Test-driven development is an important skill to absorb


Test-driven development is an approach where you write your tests before or as you write the code. It may have some disadvantages in that it might lock you into a suboptimal design too early, but most of the time it will help you design better.

Writing tests as you write code guides you toward a better design of that code. In order to test code, you will need to use the API of your code. If using the API of your code in tests is really hard, how is it going to be easy to reuse by other parts of the application?  If you do not write any test code for your code, your code has no reason to be easy to test and starts off with no tests. Software without tests is bound to fail, so you can expect it to keep failing and failing until eventually you find yourself writing the tests anyway. Unfortunately, by that point, the code is not easy to test because it was not designed to be and you're less familiar with it.

Example: routing specs


Routing specs are super easy to write using test-driven development: write the tests first and watch them fail, then write the code and watch them pass. Test-driven development is a patient practice that will result in software with tests and APIs that are easier to use by the higher-level layers in the code lasagna (see Code Craft for code architecture as pastas).

RSpec.describe HomepageController do
  it "routes to #homepage" do
    expect(get: "/").to route_to("homepage#show")
  end

  it "routes to #study_cards" do
    expect(get: "/study_cards").to route_to("homepage#study_cards")
  end
end


The code above defines the routing of the root path of the website to a Ruby class called the HomepageController, a class derived from ApplicationController which all Rails controller classes extend. The controller is a central player in the Model-View-Controller pattern given to us for free by Ruby on Rails. Controllers in Rails almost always have a name with a plural word before controller like ArticlesController. ApplicationController is an exception and here I chose to have its child class HomepageController be similarly an exception. The public methods of controllers in Rails that are matched to paths are called actions. Here we have two routing specs that are asserting true the path "/" is matched to the show action and the path "/study_cards" is matched to the study_cards action of the HomepagesController. Remember one of the two driving principles of Rails: Convention over Configuration--here you will see that the naming of the different types of things is following these conventions that removes some of the need for boilerplate code, which is necessary code, but it slows us down.

The following is all that is needed to make the above routing specs pass (this code is from the routes.rb file, which uses a domain specific language to specify the routes):

root "homepage#show"
# Dependency: paths that render study_cards/index must end with /study_cards
get "/study_cards", to: "homepage#study_cards", as: "homepage_study_cards"

A lot of programmers would see a comment like that and say, hey no comments are needed, your code is completely readable on its own. It violates DRY to have an obvious comment above code which is clearly readable. You don't need a comment like "Find the book by id" above the code Book.find params[:id]. The comment above is clarifying a dependency. Dependencies are super important things to notice in understanding the spaghetti. A great book on Ruby, Practical Object-Oriented Design, argues the main idea in practical design is preserving changeability by minimizing dependencies. It's best to not have any dependencies that are unnecessary, but here the dependency is a tradeoff for a benefit elsewhere. If it is not possible to improve the code anymore (we have other things to do too), it could be a good place to use a comment. By clarifying a dependency with a comment, we are decreasing complexity by reducing how likely it is for things to break unexpectedly, which would happen eventually with that kind of special convention. A comment can be used to reduce complexity of the code in this way: explaining why, not what the code is doing (remember DRY).
Software Development with Anki Books Migrations

Migrations


The bin/rails generate migration AddPublicToBooks command, when executed at a particular time in the past creates this file: db/migrate/20231026144625_add_public_to_books.rb

Right away I can hear people yelling about using a reserved word as a column name (public) and I think it's fine here. PUBLIC was a reserved word in SQL-92 and we're using PostgreSQL. Migrations are important though so the bigger they are, the more people should be involved. Anki Books is not at a scale where seconds of downtime is unacceptable. It might look a little clever in the Ruby code depending on how the sharply the language is being used.

class AddPublicToBooks < ActiveRecord::Migration[7.0]
  def change
  end
end

The timestamp in the file name is related to that particular time that the command was run. This is Ruby code in a scaffolded migration. We edit it to this:

class AddPublicToBooks < ActiveRecord::Migration[7.0]
  def change
    add_column :books, :public, :boolean
  end
end

This pretty much translates into an ALTER TABLE SQL statement that adds a column of type boolean to the articles table called public. Before doing any migration, also run the command: bin/rails db:migrate:status. This task uses a special table that Rails puts in the database for you, the schema_migrations table. The output shows what migrations are up and which are down:

   up     20231015101723  Add complete to articles
   up     20231015102321  Update reading writing complete for existing articles
   up     20231015102702  Add default values for articles writing reading complete
   up     20231015102811  Add not null for articles writing reading complete
  down    20231026144625  Add public to books

The schema_migrations table stores the timestamps of the migrations that have been run. By running bin/rails db:migrate, all the down migrations are run (in this case, only one migration is down). The output of that command hopefully looks good:

== 20231026144625 AddPublicToBooks: migrating =================================
-- add_column(:books, :public, :boolean)
   -> 0.0152s
== 20231026144625 AddPublicToBooks: migrated (0.0156s) ========================

And in the schema.rb file that is created by the db:schema task as part of the db:migrate task, we get an additional line:

create_table "books", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
  t.string "title", null: false
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.uuid "parent_book_id"
  t.boolean "public" # t.boolean "public" is new
end

The schema.rb file can be used to build the database from scratch and is committed to source control carefully. 

Then we can run the command bin/rails generate migration UpdateBooksPublicToFalse and this is the output:

invoke  active_record
create    db/migrate/20231026145631_update_books_public_to_false.rb

This time, we edit it to this:

class UpdateBooksPublicToFalse < ActiveRecord::Migration[7.0]
  def up
    execute <<-SQL
      UPDATE books SET public = false;
    SQL
  end

  def down
    execute <<-SQL
      UPDATE books SET public = NULL;
    SQL
  end
end

A really experienced Rails developer probably knows a more magical way, but this just executes the SQL statements to either make the public column false on all records on the table, or NULL, depending on if it is run up or down. You should always write a down method with migrations in case you need to undo that change. Sometimes, it is not possible to write a reversible migration, for example a migration that drops data is not going to magically give you any lost data back. Technically, you could reverse it if you don't care about data but unless you're in a development environment, you don't want to do that.

So we do another bin/rails db:migrate and get:

== 20231026145631 UpdateBooksPublicToFalse: migrating =========================
-- execute("      UPDATE books SET public = false;\n")
   -> 0.0101s
== 20231026145631 UpdateBooksPublicToFalse: migrated (0.0102s) ================

A quick way to test that it can be run up and down is the bin/rails db:migrate:redo command which outputs this:

== 20231026145631 UpdateBooksPublicToFalse: reverting =========================
-- execute("      UPDATE books SET public = NULL;\n")
   -> 0.0021s
== 20231026145631 UpdateBooksPublicToFalse: reverted (0.0022s) ================

== 20231026145631 UpdateBooksPublicToFalse: migrating =========================
-- execute("      UPDATE books SET public = false;\n")
   -> 0.0018s
== 20231026145631 UpdateBooksPublicToFalse: migrated (0.0019s) ================

Now that all the records in books have false, we can add a not null constraint to the column to enforce that it cannot have NULL as the value. So we do a bin/rails generate migration AddNotNullToBooksPublic and edit another new migration file:

class AddNotNullToBooksPublic < ActiveRecord::Migration[7.0]
  def change
    change_column_null :books, :public, false
  end
end

Then we run that one up (by the way, it's never a bad idea to check the state of the migrations with bin/rails db:migrate:status to make sure you are where you think you are). We can see that the schema.rb file has changed on one line:

create_table "books", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
  t.string "title", null: false
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.uuid "parent_book_id"
  t.boolean "public", null: false # this line changed
end

If we had tried to set the not null constraint on the table when it did have NULL values, the migration would have crashed. This is really undesirable when deploying to a lot of servers at once. Then we do another migration to set the default value to be false:

class AddDefaultPublicForBooks < ActiveRecord::Migration[7.0]
  def change
    change_column_default :books, :public, false
  end
end

This ensures that the existing functionality of the app that has not been specified to create a book public true or false will continue to work by defaulting to use false. We run the tests to see them pass at this point (rspec command). A couple minutes and a couple hundred little green dots later, we can continue developing. 
Software Development with Anki Books Glossary

Glossary


API


API (application programming interface) is a term you will encounter all the time as a programmer. In my dictionary, an API is an interface for application programming. There are many different kinds. For beginners learning web development, it might be the HTTP API of a web service that might be described as RESTful if it follows certain design conventions. Browsers provide APIs for website to use and third-party JavaScript packages that can be used to provide even more functionality to websites can also be called APIs. The public methods of a class form an API. When you install a Ruby gem and use it in your Rails app, you can think of it as using an API.

Interface


An interface can be understood by comparison with an implementation. To use a tool, you need only understand its interface. It is not necessary to consider everything that happens when you use a website like this one because you just need to understand the user interface. Behind the website that you see, there is a huge system of things that are necessary to make the website work, including your web browser, the Internet, and code/content specific to the website that lives on a server somewhere.

Implementation


An implementation is that internal code that allows a module to carry out that which its interface does.

Ruby on Rails


A web application framework that is the most popular way to develop web applications using the Ruby programming language. The biggest companies with a Ruby on Rails focus are probably Shopify and GitHub.
Software Development with Anki Books Web application

Web application


Anki Books is a web application: it mediates communication between the user's web browser (as the user interacts with a website) and a database. At the time of writing, the Ruby on Rails application runs on the same physical computer (a laptop running Linux) as the PostgreSQL database. The app has a connection string (details like database name, user/role to connect as, and password) to send SQL statements to the database to retrieve data, insert new data, update data, and delete data. With the Ruby on Rails framework, SQL statements do not have to be written directly. The object-relational mapping that is part of Ruby on Rails, Active Record, allows the database interactions to be described using Ruby. Ruby on Rails also supports the idea of migrations which are ways of updating the schema of the database (what tables and columns are in the database) without using SQL scripts as well.

What happens when ankibooks.io is entered in the web browser address bar?


It's a common interview question to ask something like this: what happens when I visit google.com? To visit a website, a user can type the URL of the website into the address bar of their web browser. This is virtually equivalent to clicking a link to that URL from a different website such as the Google results page given after googling the website name.

URL


A URL is a locator for a physical resource (it is a type of URI). It includes a scheme, a ://, a domain name, a port, a /, a path that may include literal segments and segment variables, a query string parameter, and other data as well. http and https are examples of schemes. :// separates the scheme from the domain name.

Domain name


A domain name is a human-readable alias for an IP address, which identifies a computer connected to the Internet. The domain name is just part of the entire URL, but it is important to understand that it carries the meaning of identifying a host computer. The domain name can be resolved to a real IP address through the Domain Name System (DNS). One of the things that the browser does under the hood when a URL is entered into the address bar is resolving the IP address by asking the DNS. The DNS is a distributed network of databases that store records tracking what IP addresses are aliased by the domain names.



A user is able to visit the website by visiting the URL of the website with their web browser (by typing it in the address bar or clicking a link to it). The part of the URL called the domain name (ankibooks.io in this case) is an alias for an IP address identifying a host computer. When you visit the URL, the web browser sends an HTTP request on your behalf to that host computer. Skipping over a lot of detail for now, the HTTP request reaches the host computer identified by the IP address. On that host computer is a process listening for exactly that type of request, a web server. A web server might just serve up a static website (but this would not typically be described as a web application). Anki Books is a Ruby on Rails web application. The web server sends the request to the Ruby on Rails application logic, which can do many different things to respond to that request. A common thing the Ruby on Rails application logic will do is to ask the database for some data, and then compile some front-end code (a website) using that data and some code. The website it compiles is then passed back to the web server which then sends it back to your web browser which presents it to you in a usable way.

In general, the application logic can respond to HTTP requests by doing many different things. It might communicate with other servers by sending HTTP requests itself, it might send jobs to do some work asynchronously, it might send emails, it might invoke a machine learning model, it might create a zip file that imports into some other program and send it to the user as a download. The final result is not always sending a website or download back to the user. The result could be a response indicating that it should go somewhere else because the content has been moved, or it might just respond with a status code saying the request was malformed.

The web is a great platform for applications that are easy for people to use without needing to install anything. Pretty much everyone who would use your software knows how to use a web browser on their phone already. There is the disadvantage that an Internet connection is needed.
Software Development with Anki Books Action Controller Study cards

Action Controller


Action Controller is C in the Rails model-view-controller code architecture.

Example: An action called articles#change_book


def change_book
  @target_book = current_user.books.find_by(id: params[:book_id])

  if !@target_book
    head :unprocessable_entity
  elsif @target_book == @book
    head :ok
  else
    # TODO: This has a bug I'm pretty sure where it can leave the ordinal positions
    # in a state that it needs to be fixed by the FixOrdinalPositionsJob
    @book.move_ordinal_child_to_new_parent(new_parent: @target_book, child: @article,
                                            new_ordinal_position: @target_book.articles_count)
    redirect_to book_articles_path(@book),
                flash: { notice: "#{@article.title} successfully moved to #{@target_book.title}" }
  end
end


This is a [...].
Hello [...].
Software Development with Anki Books The same origin policy and CORS

The same-origin policy


The same origin policy is a security mechanism of web browsers. Web browsers provide a few different APIs for making HTTP requests from client-side JavaScript code. Both the Fetch API and XMLHTTPRequest, which are browser APIs that can request resources, will be blocked by the same origin policy from requesting resources from a different origin. CORS is a mechanism that can be used to loosen the same origin policy and involves HTTP headers. The browser may send an additional OPTIONS request to the server before making the real cross-origin request of interest to determine if the server will permit a CORS response to the request origin. The additional OPTIONS request is called a preflight request. It should be noted CORS is not a security feature: it relaxes security. 

Origin


An origin is a tuple of a scheme, host, and port. With the URL https://example.com:30000, the scheme is https, the host is example.com, and the port is 30000. If these three things of two URLs are identical, then the two URLs have the same origin and a request from one of those origins to the other is not considered a cross-origin request.
Software Development with Anki Books Introduction Study cards

Anki Books


Anki Books is a budding free and open-source software (see the AGPL-3.0 license) and proof-of-concept of online books featuring active practice and spaced repetition by integrating with Anki.

Motivation for Anki Books


I develop Anki Books because I like it and use it myself and also because I like the practice of developing software. Anki helped me remember many things that helped me in my daily life, but I noticed one limitation is knowledge atomically broken up into thousands of flashcards can become disconnected. A book that acts like an index into the cards and adds context helps with that.

Knowledge graphs in Anki Books


The Anki Books data can be downloaded as CSV files for study with network analysis tools like NetworkX and Gephi. The data structure is largely a tree which would not be interesting on its own, but there is a non-trivial structure due to the cloze deletion notes (fill-in-the-blank flashcards). Each fill-in-the-blank becomes a concept. A cloze deletion note with multiple concepts is a node with an edge to each concept. Visualization of the knowledge structure is a fun way to track your Anki learning habits and it is also useful to see what ideas have been studied from multiple sources. It is especially satisfying because studying the represented information with spaced repetition encodes and consolidates it into long-term memory. Memory naturally has an associative nature, and the mental representation overlaps with the physical representation in the database.

Incremental reading in Anki Books


Anki Books can be used for incremental reading in a basic and limited way. There will definitely be improvements to the software in this aspect. Currently it uses a simple load-balancing algorithm: choose an article at random. Articles are categorized as reading, writing, and complete but this design will almost certainly be changed.

How to use Anki Books?


If you have any comments about Anki Books or need help to set one up to try out, send me an email. It will be easier to try out Anki Books soon. I am looking into Azure to see if it would be possible to host instances on the cloud using Azure App Service and Azure Database for PostgreSQL (and/or other services or containers) for free using native Infrastructure-as-Code templates to make deployment easy. Some other blocking considerations are accessibility and security.

How does Anki Books create Anki notes?


The first part of Anki Books that was developed was a module called Anki Record, a Ruby gem that creates Anki deck packages. Anki deck packages are the files with the .apkg extension that import into Anki and create or update Anki notes. By using Anki Record, Anki Books is able to take its data conforming to a different model and convert it to Anki notes. Anki Books includes a basic note model which represents the front and back flashcards that can be attached to each article. Anki Books can also create cloze notes by extracting sentences with cloze deletions out of the articles and syncing them to the PostgreSQL database with a Levenshtein distance calculation. The cloze deletion terms are tracked as concepts for the purpose of tracking which cloze notes are related to each other. That was okay but it turned out to be too annoying to go in and edit longer articles to edit cloze stuff so now cloze notes do generate concepts and a concept graph, but you make them more similarly to basic notes. Each sentence you write in a group gets split into one cloze note per sentence with good error handling feedback (Turbo frames and native client-side validation). Both types of notes can be created at any ordinal position and dragged and dropped to sort them in the UI.



The image shows an early Anki Books graph with about a dozen clustered tree structures being pulled to the center by a simulated gravity. This article is represented in the very small group near the top left perimeter. Gephi was used for the layout and rendering of the graph (graph in this context meaning a structure of nodes connected by edges). This early graph was created when domains had a self-referential many-to-many relationship. I got rid of that after I accidentally introduced a recursive SQL query that may have caused a memory leak. In the following design, a domain could have only one parent domain. Cloze notes and concepts were not introduced until a lot later. Sometime after cloze notes and concepts were added, domains were removed after they were made redundant by allowing books to act as parents to other books. This simplification allowed removing over 1500 lines of code (commit SHA: f4d76edd49946ecca0873248bc4f76c3b82af4e1). The refactoring of basic notes and cloze notes into using a Single Table Inheritance design pattern simplified things later too but was a more involved effort.

How does Anki Books create graphs?


Anki Books has endpoints for downloading CSV files of all the data that can be used to construct a graph that is similar to Wozniak's knowledge tree at the roots and Wozniak's concept network towards the remote areas especially where there are cloze notes. A cloze note with two concept cloze deletions is a node connecting the two concepts, for example.

This is an example Python script to create a file for Gephi:

// Programmer #1:
// Sorry about the font on this code
// TODO: fix font
// Programmer #2:
// Leave this comment as a warning to be wary in the code around here
import networkx as nx
import matplotlib.pyplot as plt
import csv

path = "./prod"

G = nx.Graph()

G.add_node("center")

with open(f"{path}/books.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    title = row[1]
    parent_book_id = row[2]
    G.add_node(node_id)
    G.nodes[node_id]["book"] = True
    G.nodes[node_id]["title"] = title
    if parent_book_id != "":
      G.add_edge(node_id, parent_book_id)
    else:
      G.add_edge(node_id, "center")

with open(f"{path}/articles.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    title = row[1]
    book_id = row[2]
    G.add_node(node_id)
    G.nodes[node_id]["article"] = True
    G.nodes[node_id]["title"] = title
    G.add_edge(node_id, book_id)

with open(f"{path}/basic_notes.csv", 'r') as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    front = row[1]
    back = row[2]
    article_id = row[3]
    G.add_node(node_id)
    G.nodes[node_id]["basic_note"] = True
    G.nodes[node_id]["front"] = front
    G.nodes[node_id]["back"] = back
    G.add_edge(node_id, article_id)

with open(f"{path}/concepts.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    name = row[1]
    G.add_node(node_id)
    G.nodes[node_id]["name"] = name

with open(f"{path}/cloze_notes.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    node_id = row[0]
    sentence = row[1]
    article_id = row[2]
    G.add_node(node_id)
    G.nodes[node_id]["sentence"] = sentence
    G.add_edge(node_id, article_id)

with open(f"{path}/cloze_notes_concepts.csv", "r") as file:
  csvreader = csv.reader(file)
  next(csvreader, None)
  for row in csvreader:
    cloze_note_id = row[0]
    concept_id = row[1]
    G.add_edge(cloze_note_id, concept_id)

nx.draw_networkx(G, with_labels=False, node_size=5)
plt.savefig(f"{path}/basic_notes_graph.svg")
nx.write_graphml(G, f"{path}/basic_notes_graph.graphml")


Example: a small area of an Anki Books graph


The memory imprint of a book, in this case APSD, is represented here as a concept map in a larger network. The concepts of this book relate to software design and the causes of and manifestations of complexity in software. In dark mode (at least in the future, because in recent refactoring, dark mode was removed temporarily), the labels are hard to read, but the image shows part of a network (or "graph") with a tree structure where there are also labeled concept nodes adding a less trivial structure to the tree. From top-left to bottom-right, the labels are interface, cognitive load, module, implementation, change amplification, unknown unknowns, modular design, abstraction, complexity, obscurity, dependencies, Agile, problem decomposition, and tactical programming.

Why does Anki Books use Ruby on Rails?


I was working a lot with Ruby on Rails when I started work on (this third version of) Anki Books. Ruby is a gentle, quirky, and rapid programming language. Combine this with the power of Rails and it's a great choice if you like new Ruby virtual machines. Ruby is so gentle that it cannot stop some projects from going off the Rails (the CS guys I knew in college would say it's just writing English). Since Anki Books is free software that cannot be made proprietary, the gentleness and power of the Ruby on Rails stack will hopefully be ideal for anyone who wants to extend the functionality.

Why does Anki Books use PostgreSQL?


PostgreSQL is (one of) the finest and most advanced free and open-source object-relational database management software in the world.

Anki Books is licensed under the AGPLV3 (it is free software)

Example: an Anki Books article


ABC metric


The ABC metric is a measure of program size, but it may also measure complexity. It is a vector quantity with the number of assignments, branches, and conditionals in some code. This code may be a method, class, source file, or entire program. The ABC metrics can be added together linearly, but their magnitudes cannot, in the same way as is true for any vectors.

As determined by RuboCop, a static code analysis tool for Ruby, the following method has an ABC metric of <0, 6, 1>:

##
# Returns nth number in the Fibonacci sequence starting with 1, 1 ...
def fibonacci(n)
  [1, 2].include?(n) ? 1 : fibonacci(n - 1) + fibonacci(n - 2)
end

5.times do |i|
  puts fibonacci(i + 1)
end

There are no assignments so the first value of the vector is 0. Each function or method call adds a branch. Since Ruby is an "everything is an object" language, some of the method calls look like operators. There are two recursive calls to fibonacci, one call to include?, and three calls to methods which look like arithmetic operators. In a language where the arithmetic operators were not methods, this function might have a B value of 3 instead of 6.

By default, RuboCop complains about a method's ABC metric when its magnitude exceeds 17. The method can usually be refactored to eliminate this warning by extracting parts of it to smaller methods.

Cyclomatic complexity


Cyclomatic complexity is a measure of program complexity and focuses on the number of linearly independent paths through the program. The fibonacci method from the previous example has a cyclomatic complexity of 2 due to the ternary operator.
RuboCop will complain about this for the following method:

def triangle(a, b, c)
  if invalid_triangle?(a, b, c)
    :invalid
  elsif a == b && b == c && a == c
    :equilateral
  elsif a == b || b == c || a == c
    :isosceles
  else
    :scalene
  end
end

This method has a cyclomatic complexity of 8. The RuboCop documentation explains:

The algorithm counts decision points and adds one.

An if statement (or unless or ?:) increases the complexity by one. An else branch does not, since it doesn’t add a decision point. The && operator (or keyword and) can be converted to a nested if statement, and ||/or is shorthand for a sequence of ifs, so they also add one. Loops can be said to have an exit condition, so they add one.

There are 3 ifs and 4 logical operators adding to 7 decision points, so the cyclomatic complexity is 8. By default, RuboCop will complain about a method's cyclomatic complexity when it exceeds 7.

The Law of Demeter


The Law of Demeter, or principle of least knowledge, is a design guideline that was proposed at Northeastern University in 1987. It recommends that each unit should have limited knowledge about other units and only interact with units to which it is "closely" related. If an object calls a method on an object to only access a third object to call a method on it, then this law is not being followed in that case. Oftentimes this law can be stated as "use only one dot" but this rule is not an exact statement of the idea because the LoD is not always broken when there is more than one dot.
Examples notes [...].
The ABC metric was introduced as a possibly better alternative for what other software metric?
Another [...].
Of cyclomatic complexity and ABC metric, which focuses more on program size?
What software metric measures the number of linearly independent paths through a program?
What is the magnitude of an ABC metric <3,0,4>?
What do A, B, and C stand for in the ABC metric?
What is the ABC metric of this line of Ruby code? (x > 2) ? (y = 3) : (z = 5)
What is the smallest cyclomatic complexity that RuboCop will give a warning for by default?
What version of the name is System.Console?
What is the other name for the Law of Demeter?
Of cyclomatic complexity and ABC metric, which focuses more on program complexity?
Why does an else branch not add one to the cyclomatic complexity in the algorithm used by RuboCop?
Where was the Law of Demeter introduced?
The Law of Demeter is a specific case of:
What is a common statement explaining how to follow the Law of Demeter?
Software Development with Anki Books Testing upload Study cards


What does a brain use to solve problems if you ask Wozniak?
Software Development with Anki Books Setting up a development Anki Books on Ubuntu 20.04 Study cards

Setting up a development instance of Anki Books


Recently I bought a used computer from my friend. A day after I installed Ubuntu 20.04 from an image I had on a flash drive, I went through all the steps of setting up Anki Books from installing git, configuring a public SSH key on GitHub, cloning, installing system packages, installing Ruby, rbenv, PostgreSQL, and executing Rake scripts to set up the application database. At the very end I realized there was a bug with the bin/dev script and fixed it, pushing the commit up. If you follow these, steps, the commit SHA that fixed that bug was b3421913a68812935f690f2447c53687a3c5280e. This means once you have the repository cloned, you can checkout a detached head at this commit and the app that you start will be the exact same as when I did this. In general though, most of the steps here will apply if you are following this in the future. As I set up the app, I just took notes down on paper to ensure I didn't miss anything. Afterwards I took screenshots of them and have uploaded them here, and this article will explain the pictures in even more detail.

What Ruby version manager does Anki Books recommend?
What file in Anki Books tells rbenv the Ruby version?
What database does Anki Books use that is the best open-source relational database management system since Oracle took over MySQL?
What is it called when you checkout a lone commit instead of a branch, and not in a branch?
What could be simply defined as a running program?
What is the CLI for manual pages in a Bash shell?
What letter is to exit a man page on Linux?
What package manager on Ubuntu could you use to install git?
What command can you use to generate ssh keys on a Ubuntu machine for example?
What directory in your home directory is where the ssh-keygen command puts your id_rsa.pub and private key?
What file in the Anki Books source is infrastructure as code (1 file) that specifies a Docker container that can run Anki Books?
What is the other main Ruby version manager (not rbenv)?