Analyzing Ruby on Rails: the Whitehall case study

Analyzing JavaScript React Native: the Zooniverse case study shows how we can build high level models for a specific framework within a language. Ruby on Rails is another such example, only with a few differences.

But first, let's clone a repository (it can take a couple of minutes):

rootDirectory := 'whitehall' asFileReference
  
rootDirectory ensureDeleteAll.
repository := IceRepositoryCreator
		fromUrl: 'git@github.com:alphagov/whitehall.git'
		to: rootDirectory.
rootDirectory
  

Once we have the sources, we can create a model of the system:

importer := GtSmaCCRubyImporter new.
model := importer
	importASTs: (importer parseDirectory: rootDirectory); 
	createForeignKeys;
	createActiveRecordLinksAndAttributes;
	createStubClassesFromTables;
	inferDeclaredTypes;
	createRoutes;
	model
  

The model creation logic is implemented in GtSmaCCRubyImporter GtRubyImporter subclass: #GtSmaCCRubyImporter uses: TRubyProgramNodeVisitor - {#visitFile:} instanceVariableNames: 'includes' classVariableNames: '' package: 'GToolkit4Ruby-Importer' and does several things. At the basic level, we get entities like classes or methods which are rather generic. We also get more specific entities like Ruby modules. But the interesting part of this exercise lies in the deeper model.

Browsing Active Records

Rails comes with an active record library that typically plays an important role in a system. Take a look at the hierarchy by starting at the root:

applicationRecord := model allClasses entityNamed: #'ApplicationRecord'
  

These classes define their structure in two places: on the one hand, we have the Ruby source code; on the other hand, there is a SQL snippet that defines the corresponding table.

For example, one of the classes is Edition:

edition := model allClasses entityNamed: #'Edition'
  

This class is mapped on a table called editions.

model allTables entityNamed: #'editions'
  

Thus, to reason about the Edition as a conceptual entity, we need to look at two kinds of sources: one with logic, and one with the persistent state. That is why when inspecting the Edition class, we have two views: Source and Table source.

Furthermore, when we have a field like document_id in the table definition, that is mapped on a foreign key to a documents table that corresponds to a Document Ruby class. Thus, even if Ruby is a dynamically typed language, the document field is statically typed through the framework. This is a piece of information we can leverage in our tools. For example, take a look at the All connections view of the Edition class.

Browsing Routes

A common entry point into the server side of a system is the REST API. Thus it can be important to browser the routes:

model allRailsRoutes
  

Often routes have two parts: the definition of the end point, and the associated logic. Navigating through these can be less than comfortable. We can address this with custom tools. For example, take a look at this one:

model allRailsRoutes detect: [:each | each method mooseName = #'Admin::OrganisationsController.features()' ]
  

There are two views: one with the source that defines the route, and one with the controller source that defines the logic.

Another way to browse the routes is by the API path. Take a look at the overall tree:

model allUrlPaths entityNamed: #'/'