algonote(en)

There's More Than One Way To Do It

Statistical Comparison of Open Source Rails Repositories

An Overview of Rails Development

Comparing the Statistics of Open Source Rails Repositories

Rails has a wealth of implementations for open source web services. Previously, I compared the rails stats of a non-public commercial web service, and this time I looked into those that are publicly available.

I ran the commands using the repositories’ default settings. If the original configuration isn’t set up properly, some files might not be parsed correctly.


Redmine

commit 9315481cdfefed08d0e1bfa0701de872ba6d241b

One thing that sets it apart is that its tests are not based on RSpec. The Code to Test Ratio is low. The count of table is relatively small.

 $ bundle exec rails stats
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |   8829 |   6599 |      55 |     534 |   9 |    10 |
| Helpers              |   6711 |   4923 |       1 |     395 | 395 |    10 |
| Jobs                 |    112 |     88 |       3 |       9 |   3 |     7 |
| Models               |  19585 |  14174 |     102 |    1502 |  14 |     7 |
| Libraries            |  20353 |  14511 |     149 |    1180 |   7 |    10 |
| Controller tests     |      0 |      0 |       0 |       0 |   0 |     0 |
| Helper tests         |   4150 |   3224 |      21 |     283 |  13 |     9 |
| Model tests          |      0 |      0 |       0 |       0 |   0 |     0 |
| Mailer tests         |      0 |      0 |       0 |       0 |   0 |     0 |
| Integration tests    |  10351 |   7269 |     101 |     575 |   5 |    10 |
| System tests         |   1504 |   1022 |      10 |      65 |   6 |    13 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                |  71595 |  51810 |     442 |    4543 |  10 |     9 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 40295     Test LOC: 11515     Code to Test Ratio: 1:0.3

Mastodon

commit 728eb6a15387da9074ccc024c9002f74ff829470

Despite its size, the specs are broken down into detailed concepts.

 $ RAILS_ENV=development bundle exec rails stats
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |  12340 |   9255 |     247 |    1533 |   6 |     4 |
| Helpers              |   1612 |   1295 |       0 |     144 |   0 |     6 |
| Models               |  12884 |   8600 |     168 |    1170 |   6 |     5 |
| Mailers              |    399 |    298 |       4 |      38 |   9 |     5 |
| Views                |    310 |    201 |       0 |       0 |   0 |     0 |
| JavaScript           |  32723 |  26838 |       0 |     489 |   0 |    52 |
| Libraries            |   7756 |   5732 |      94 |     286 |   3 |    18 |
| Chewy specs          |    124 |    100 |       0 |       4 |   0 |    23 |
| Config specs         |    106 |     75 |       0 |       1 |   0 |    73 |
| Controller specs     |  15700 |  11988 |       0 |      43 |   0 |   276 |
| Fabrication specs    |     14 |     10 |       0 |       0 |   0 |     0 |
| Feature specs        |    434 |    271 |       0 |       1 |   0 |   269 |
| Generator specs      |     27 |     20 |       0 |       0 |   0 |     0 |
| Helper specs         |   1326 |   1038 |       0 |       7 |   0 |   146 |
| Lib specs            |   9841 |   7667 |       0 |      10 |   0 |   764 |
| Locale specs         |     35 |     28 |       0 |       0 |   0 |     0 |
| Mailer specs         |    602 |    445 |       3 |      27 |   9 |    14 |
| Model specs          |   8655 |   6733 |       0 |      15 |   0 |   446 |
| Policy specs         |   1245 |    991 |       0 |       0 |   0 |     0 |
| Presenter specs      |    404 |    318 |       0 |       0 |   0 |     0 |
| Request specs        |   5575 |   4089 |       0 |       4 |   0 |  1020 |
| Routing specs        |    207 |    161 |       0 |       0 |   0 |     0 |
| Serializer specs     |    324 |    253 |       0 |       0 |   0 |     0 |
| Service specs        |   7600 |   6043 |       0 |      16 |   0 |   375 |
| System specs         |     45 |     31 |       0 |       0 |   0 |     0 |
| Validator specs      |    762 |    592 |       0 |       3 |   0 |   195 |
| View specs           |     48 |     35 |       0 |       0 |   0 |     0 |
| Worker specs         |   1704 |   1280 |       0 |       1 |   0 |  1278 |
| App Libraries        |   8680 |   6416 |     158 |     984 |   6 |     4 |
| Presenters           |    348 |    273 |      13 |      34 |   2 |     6 |
| Services             |   6593 |   4788 |      91 |     590 |   6 |     6 |
| Validators           |    537 |    385 |      19 |      59 |   3 |     4 |
| Workers              |   2305 |   1635 |      87 |     172 |   1 |     7 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                | 141265 | 107884 |     884 |    5631 |   6 |    17 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 65716     Test LOC: 42168     Code to Test Ratio: 1:0.6

Forem

commit ca6e6355493ddb49a31f697dd64baa2904356df6

The look of dev.to is modest and has a simple, unpretentious feel. Correspondingly, there is little front-end code.

 $ bundle exec rails stats
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |  10858 |   8513 |     167 |     851 |   5 |     8 |
| Helpers              |   1280 |    951 |       0 |     142 |   0 |     4 |
| Models               |   8526 |   5840 |     113 |     676 |   5 |     6 |
| Mailers              |    285 |    225 |       5 |      28 |   5 |     6 |
| Views                |  21570 |  19904 |       0 |       0 |   0 |     0 |
| JavaScripts          |   5672 |   4138 |       0 |     163 |   0 |    23 |
| Stylesheets          |  17530 |  14900 |       0 |       0 |   0 |     0 |
| JavaScript           |  21635 |  15225 |       0 |     338 |   0 |    43 |
| Libraries            |   2803 |   2387 |     148 |     170 |   1 |    12 |
| Controller specs     |    110 |     90 |       0 |       0 |   0 |     0 |
| Decorator specs      |   1317 |   1111 |       0 |       1 |   0 |  1109 |
| Form specs           |     70 |     59 |       0 |       0 |   0 |     0 |
| Generator specs      |      5 |      4 |       0 |       0 |   0 |     0 |
| Helper specs         |   1294 |   1050 |       0 |       1 |   0 |  1048 |
| Initializer specs    |    293 |    244 |       0 |       1 |   0 |   242 |
| Lib specs            |   2510 |   2011 |       2 |      10 |   5 |   199 |
| Liquid_tag specs     |   3301 |   2719 |       0 |      59 |   0 |    44 |
| Mailer specs         |    729 |    575 |       5 |      24 |   4 |    21 |
| Model specs          |  11286 |   8998 |       1 |       7 |   7 |  1283 |
| Policy specs         |   1452 |   1061 |       0 |       1 |   0 |  1059 |
| Query specs          |   1431 |   1101 |       0 |       2 |   0 |   548 |
| Refinement specs     |     10 |      9 |       0 |       0 |   0 |     0 |
| Request specs        |  29241 |  23601 |       1 |      80 |  80 |   293 |
| Routing specs        |    152 |    124 |       0 |       0 |   0 |     0 |
| Sanitizer specs      |     66 |     58 |       0 |       0 |   0 |     0 |
| Serializer specs     |    264 |    218 |       0 |       0 |   0 |     0 |
| Service specs        |  15712 |  12645 |       0 |      49 |   0 |   256 |
| System specs         |   6155 |   4930 |       0 |      23 |   0 |   212 |
| Task specs           |     78 |     59 |       0 |       0 |   0 |     0 |
| Uploader specs       |    335 |    264 |       0 |       0 |   0 |     0 |
| Validator specs      |    184 |    137 |       0 |       6 |   0 |    20 |
| View_object specs    |     87 |     68 |       0 |       0 |   0 |     0 |
| View specs           |    403 |    323 |       0 |       1 |   0 |   321 |
| Worker specs         |   3185 |   2556 |       0 |       3 |   0 |   850 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                | 169829 | 136098 |     442 |    2636 |   5 |    49 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 72083     Test LOC: 64015     Code to Test Ratio: 1:0.9

Discourse

commit 4f8d52bbcb3d6e55eec7c8a789bd4f22b491c4aa

This repository is the most front-end heavy. In terms of lines of code, it’s the second largest after GitLab.

$ bundle exec rails stats
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |  19897 |  15856 |     111 |    1035 |   9 |    13 |
| Helpers              |   1203 |    958 |       0 |     120 |   0 |     5 |
| Jobs                 |   8510 |   6768 |     202 |     425 |   2 |    13 |
| Models               |  40354 |  29270 |     271 |    2447 |   9 |     9 |
| Mailers              |   1136 |    949 |       9 |      51 |   5 |    16 |
| Views                |   3131 |   2821 |       0 |       0 |   0 |     0 |
| JavaScripts          | 254029 | 214041 |       0 |    5185 |   0 |    39 |
| Stylesheets          |  38779 |  33250 |       0 |       0 |   0 |     0 |
| Libraries            |  75276 |  58686 |     602 |    4917 |   8 |     9 |
| Helper specs         |   1078 |    869 |       0 |       2 |   0 |   432 |
| Import_export specs  |    246 |    189 |       0 |       1 |   0 |   187 |
| Initializer specs    |    129 |     93 |       0 |       0 |   0 |     0 |
| Integration specs    |   3273 |   2762 |       0 |      12 |   0 |   228 |
| Integrity specs      |    371 |    295 |       0 |       9 |   0 |    30 |
| Job specs            |  10087 |   7982 |       5 |      36 |   7 |   219 |
| Lib specs            |  74837 |  60645 |      49 |     296 |   6 |   202 |
| Mailer specs         |   1916 |   1576 |       0 |       1 |   0 |  1574 |
| Model specs          |  44934 |  36027 |       6 |     115 |  19 |   311 |
| Multisite specs      |    834 |    676 |       2 |      10 |   5 |    65 |
| Request specs        |  60027 |  48287 |       1 |      78 |  78 |   617 |
| Script specs         |   1137 |    967 |       1 |       6 |   6 |   159 |
| Serializer specs     |   5139 |   4099 |       0 |      11 |   0 |   370 |
| Service specs        |  15175 |  12244 |       0 |      38 |   0 |   320 |
| System specs         |   7181 |   5564 |      47 |     419 |   8 |    11 |
| Task specs           |    674 |    507 |       0 |       5 |   0 |    99 |
| View specs           |    108 |     84 |       0 |       0 |   0 |     0 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                | 669461 | 545465 |    1306 |   15219 |  11 |    33 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 362599     Test LOC: 182866     Code to Test Ratio: 1:0.5

GitLab

commit bcdfc757c31b2e71adaea24a2b9343aaac252a65

This is the largest repository. There are many parts where the setup can be challenging.

$ bundle exec rails stats
+----------------------+---------+---------+---------+---------+-----+-------+
| Name                 |   Lines |     LOC | Classes | Methods | M/C | LOC/M |
+----------------------+---------+---------+---------+---------+-----+-------+
| Controllers          |   32803 |   24239 |     345 |    3179 |   9 |     5 |
| Helpers              |   18669 |   14554 |       1 |    1803 | 1803 |     6 |
| Models               |   83027 |   59217 |     725 |    7643 |  10 |     5 |
| Mailers              |    2492 |    1857 |      10 |     258 |  25 |     5 |
| Channels             |     142 |      97 |       4 |      11 |   2 |     6 |
| Views                |    1046 |     814 |       0 |       0 |   0 |     0 |
| JavaScripts          |  134131 |   99295 |       0 |     968 |   0 |   100 |
| Stylesheets          |   47170 |   38792 |       0 |       0 |   0 |     0 |
| Libraries            |  251796 |  184939 |    3550 |   19198 |   5 |     7 |
| Bin specs            |     717 |     574 |       0 |       0 |   0 |     0 |
| Channel specs        |     103 |      74 |       0 |       0 |   0 |     0 |
| Command specs        |     672 |     525 |       0 |       0 |   0 |     0 |
| Component specs      |    2630 |    2046 |      10 |      28 |   2 |    71 |
| Config specs         |     923 |     748 |       0 |       2 |   0 |   372 |
| Contract specs       |    1799 |    1538 |       1 |      17 |  17 |    88 |
| Controller specs     |   63009 |   48053 |       1 |     366 | 366 |   129 |
| Db specs             |     835 |     636 |       0 |      16 |   0 |    37 |
| Dependency specs     |      26 |      20 |       0 |       0 |   0 |     0 |
| Experiment specs     |     411 |     298 |       0 |       0 |   0 |     0 |
| Feature specs        |   93841 |   70386 |       0 |     528 |   0 |   131 |
| Finder specs         |   21757 |   16461 |       0 |      31 |   0 |   529 |
| Graphql specs        |   32982 |   24901 |       0 |     192 |   0 |   127 |
| Haml_lint specs      |     228 |     161 |       0 |       0 |   0 |     0 |
| Helper specs         |   29231 |   23025 |       0 |      54 |   0 |   424 |
| Initializer specs    |    4053 |    3054 |       0 |      31 |   0 |    96 |
| Lib specs            |  335839 |  261381 |      18 |    1194 |  66 |   216 |
| Mailer specs         |    5032 |    3915 |       0 |      10 |   0 |   389 |
| Metrics_server specs |     260 |     191 |       0 |       0 |   0 |     0 |
| Migration specs      |   11854 |    9121 |       0 |      24 |   0 |   378 |
| Model specs          |  162193 |  123999 |       4 |     286 |  71 |   431 |
| Policy specs         |   13495 |   10241 |       0 |      22 |   0 |   463 |
| Presenter specs      |    8461 |    6458 |       0 |       8 |   0 |   805 |
| Rack_server specs    |      82 |      60 |       0 |       2 |   0 |    28 |
| Request specs        |  146710 |  113690 |       0 |     473 |   0 |   238 |
| Routing specs        |    2332 |    1683 |       0 |       3 |   0 |   559 |
| Rubocop specs        |   14034 |   11662 |     319 |     281 |   0 |    39 |
| Script specs         |    4542 |    3534 |       0 |       5 |   0 |   704 |
| Serializer specs     |   12556 |    9752 |       0 |       6 |   0 |  1623 |
| Service specs        |  146314 |  112737 |       2 |     389 | 194 |   287 |
| Sidekiq specs        |      20 |      16 |       0 |       0 |   0 |     0 |
| Sidekiq_cluster specs |     122 |      96 |       0 |       0 |   0 |     0 |
| Spam specs           |      31 |      25 |       0 |       0 |   0 |     0 |
| Support_spec specs   |    2761 |    2150 |       0 |      29 |   0 |    72 |
| Task specs           |    7182 |    5470 |       0 |      28 |   0 |   193 |
| Tooling specs        |    7347 |    5791 |       3 |       6 |   2 |   963 |
| Uploader specs       |    4005 |    3051 |       1 |      10 |  10 |   303 |
| Validator specs      |    1813 |    1279 |       0 |       8 |   0 |   157 |
| View specs           |   10235 |    7583 |       0 |      27 |   0 |   278 |
| Worker specs         |   28213 |   21376 |       0 |      87 |   0 |   243 |
+----------------------+---------+---------+---------+---------+-----+-------+
| Total                | 1749926 | 1331565 |    4994 |   37223 |   7 |    33 |
+----------------------+---------+---------+---------+---------+-----+-------+
  Code LOC: 423804     Test LOC: 907761     Code to Test Ratio: 1:2.1

Comparison of the Statistics

Now that we have the individual statistics, let’s compare them.

Application Code

Total lines of application code.
It’s true that GitLab has split out parts into external libraries, but Discourse turns out to be a rather huge repository.

Number of model classes.
This is almost equal to the number of database tables.

The ratio of JavaScript LOC (excluding tests) to application code LOC.
This value varies depending on the type of application.

Model LOC per Controller LOC.

We can know whether models have become “fat” or not. There are other layers such as the service layer, so there is some noise. Comparing the ABC size shows that GitLab isn’t in a bad state—this might be a better measure of technical debt.

Test Code

Code to Test Ratio.
GitLab has a high ratio, whereas Redmine’s ratio is low.

Test distribution.
Note that controller and GraphQL tests are included under Request specs, and feature specs are included under System specs.

It is often said that open source projects are more secure, stable, and of higher quality than closed-source projects. With so many eyes watching, things tend to converge into something good over time.

When bugs are found and fixed, tests are often written together. In that sense, it is possible that in open source projects—where many eyes are watching—the amount of test code is proportional to the complexity of the application.


The Test Pyramid and the Testing Trophy

It is common to say that the correct strategy for increasing test code is to follow either a pyramid or a trophy shape.

In the case of the test pyramid:

  1. Unit tests
  2. Integration tests
  3. End-to-End tests

The ideal is for unit tests to be the thickest and end-to-end tests to be the thinnest. End-to-end tests take the longest to run and are more fragile in the face of design changes.

Another perspective, especially in the context of front-end development, is the Testing Trophy.

The testing trophy divides tests into End-to-End, Integration, Unit, and Static. End-to-End tests are few, unit tests are thick, and while integration tests may not be as numerous as unit tests, they are still substantial.

However, the trophy is less strictly defined than the pyramid.

In the original proposal by the author, the integration layer seems to be more emphasized. Unlike the pyramid, the shape of the trophy can vary depending on how you sculpt it.

So for Rails—or more generally, back-end repositories—what is the right test gradient?

Proposal: Testing Ruby

What we can learn from open source Rails repositories is that:

  1. Model tests
  2. Request tests
  3. System tests

are used, and model tests are slightly fewer than request (controller) tests.
While model tests are only a subset of unit tests, the impression is that there are fewer model tests than one might expect. In a way, we may describe the shape as Testing Ruby.

As an application grows in complexity to the point where a service layer becomes necessary, the handling of the application shifts from the model layer to controllers and services. In fact, the ratio of model code decreases as the scale increases.

For products that have thick codebases on both the front and back ends, if we consider the cost-performance of running tests, the order is likely as follows (with 1 being the fastest and least prone to breakage):

  1. Model tests
  2. Request tests
  3. Integration (API scenario) tests
  4. End-to-End tests

On the other hand, from a perspective different from cost-performance, as the application grows in scale the same table tends to be used across multiple use cases, which naturally reduces the proportion of the model layer. In addressing this complexity through test code, the shape becomes Testing Ruby.