Ruby Open Source: Zammad example
Lucian Ghinda
Posted on November 10, 2023
Another example of an open source web app written in Ruby
I will probably start a series of open-source Ruby Projects. Maybe I will call it #opensource #Friday.
Zammad is an open-source ticketing system, that also offers an on-cloud product.
They have their product open-sourced on Github and it is built using (at the moment of writing this article) Ruby 3.1.3 and Rails 7
Licensing
The license is GNU AGPL
They mention on their website why they chose to make the product open source:
Each file in the repository has a license line like:
Some ideas from the open-source repo
I don't have enough time to analyse in depth the repo. So just looking around for 30 minutes, here are some things that I extracted
Stats
Running rails stats
returned the following:
Styling Guide
They use Rubocop with extra cops added. They require some cops provided by gems and custom cops written by them:
require:
- rubocop-capybara
- rubocop-factory_bot
- rubocop-faker
- rubocop-graphql
- rubocop-inflector
- rubocop-performance
- rubocop-rails
- rubocop-rspec
- ../config/initializers/inflections.rb
- ./rubocop_zammad.rb
In rubocop_zammad.rb
they are loading custom cops from .rubocop/cop/zammad
Here are some custom cops written by them:
Checking if the migration file starts with a valid timestamp
Checking usages of
find_by
to check if an Active Record exists and replace it withexists?
Checking that the only allowed
default_scope
is about simple orderingChecking for using
rand
Checking usages of
.to_sym
on strings and change to:
prefixChecking usages of
unless
and suggest using!
Checking copyright notice and adding it when missing
More about their style guide can be found at doc/developer_manual/standards
Persistence
They appear to use 3 DBs, each one having their group. One (the activerecord-nulldb-adapter) is actually a NullObject pattern implemented for Active Record.
Based on the loaded connection, they do a preflight check in an initializer. This is what it looks like:
Rails.application.config.after_initialize do
Zammad::Application::Initializer::DbPreflightCheck.perform
end
and you can go check lib/zammad/application/initializer
to see what kind of checks are executed for each adapter.
This is a way to make sure that the actual DB server respects a contract they have defined in these initializers (e.g. what extensions are activated or config defaults or minimum version).
See this example of a check for MySQL from the same file:
Some gems used
argon2 - "A Ruby gem offering bindings for Argon2 password hashing"
rszr - "Rszr is an image resizer for Ruby based on the Imlib2 library. It is faster and consumes less memory than MiniMagick, GD2 and VIPS, and comes with an optional drop-in interface for Rails ActiveStorage image processing"
biz - "Time calculations using business hours"
diffy - "It provides a convenient way to generate a diff from two strings or files. Instead of reimplementing the LCS diff algorithm Diffy uses battle tested Unix diff to generate diffs, and focuses on providing a convenient interface, and getting out of your way"
chunky_png - "ChunkyPNG is a pure Ruby library to read and write PNG images and access textual metadata. It has no dependency on RMagick, or any other library for that matter"
localhost - "This gem provides a convenient API for generating per-user self-signed root certificates"
activerecord-nulldb - "NullDB is the Null Object pattern as applied to ActiveRecord database adapters. It is a database backend that translates database interactions into no-ops. Using NullDB enables you to test your model business logic - including after_save hooks - without ever touching a real database"
Design Patterns
They use service objects. There is a Service::Base
class that is empty and then there is also Service::BaseWithCurrentUser
that looks something like this:
The primary method that a Service object should define is execute
but there is no enforcement of this. It is just that everything under app/services/service has this method defined.
GraphQL objects
All the logic about GraphQL is inside app/graphql, namescoped to Gql.
Jobs
They can be found at app/jobs. Job priority is defined in a concern called ApplicationJob::HasQueuingPriorit
y
that looks like this:
And all jobs have a default priority of 200 while low_priority is defined as being 300
Models
They are under app/models and there is an ApplicationModel
defined that includes some defaults like this:
There are probably many more interesting things to discover about the codebase but this is what I got in a short review.
Enjoyed this article?
Join my Short Ruby News newsletter for weekly Ruby updates from the community. For more Ruby learning resources, visit rubyandrails.info. You can also find me on Ruby.social or Linkedin or Twitter.
Posted on November 10, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.