Skip to content

Latest commit

 

History

History
163 lines (93 loc) · 10 KB

cloi2yd2y000c09jtgkw8ceoe.md

File metadata and controls

163 lines (93 loc) · 10 KB
title seoTitle seoDescription datePublished cuid slug cover tags
Ruby Open Source: Zammad example
Zammad Ruby on Rails Open Source Example
Explore Zammad: open-source Ruby ticketing system with cloud, custom styling, multiple databases, and service objects on Ruby 3.1.3, Rails 7
Fri Nov 03 2023 03:52:55 GMT+0000 (Coordinated Universal Time)
cloi2yd2y000c09jtgkw8ceoe
ruby-open-source-zammad-example
programming-blogs, ruby, opensource, ruby-on-rails, coding

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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697602812644/7f4332e3-f201-4b13-9665-edcc4b62ba93.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697602714959/cb56b30a-549e-425b-b779-aa6d0c4439fc.png align="center")

Licensing

The license is GNU AGPL

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697612651893/a97191ad-5d59-4f61-b9f4-d310eda87e2d.png align="center")

They mention on their website why they chose to make the product open source:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697612717159/df787418-80d9-4629-9b45-12104b3fca55.png align="center")

Each file in the repository has a license line like:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697613596157/626b9c49-9262-47c2-a3b2-144bf95f9728.png align="center")

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:

![Result of executing rails stats on the repo](https://cdn.hashnode.com/res/hashnode/image/upload/v1698983056409/424bdfb1-3437-4fb2-a49a-6e421b6066b4.png align="center")

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 with exists?

  • Checking that the only allowed default_scope is about simple ordering

  • Checking for using rand

  • Checking usages of .to_sym on strings and change to : prefix

  • Checking 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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697613005230/e9580f24-8d99-4bf4-a9cd-df14a0726516.png align="center")

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:

![Code sample from MySQL DB preflight check](https://cdn.hashnode.com/res/hashnode/image/upload/v1698982343291/90dad251-2dda-44dc-86e4-842e72de559e.png align="center")

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:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697617174559/c087e526-1895-4740-b0a0-261223e24d47.png align="center")

The main 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::HasQueuingPriority that looks like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697617529027/969eb9ec-d08d-47ed-9877-7a8d8a5c7b9b.png align="center")

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:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1697617801805/e23db320-1e38-4684-b485-2e42f50ec466.png align="center")

There are probably a lot 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 and visit rubyandrails.info, a directory with learning content about Ruby.

👐 Subscribe to my Ruby and Ruby on rails courses over email at learn.shortruby.com - effortless learning anytime, anywhere

🤝 Let's connect on Ruby.social or Linkedin or Twitter where I post mainly about Ruby and Rails.

🎥 Follow me on my YouTube channel for short videos about Ruby