How to write modular code
Or: How to avoid and refactor spaghetti code
Please note that I tried to keep the examples small. The effects of the methods in this card are of course much more significant with real / more complex code.
What are the benefits of more modular code?
Code is written once but read often (by your future self and other developers who have to understand it in order to make changes for example). With more modular code you reduce the scope of what has to be understood in order to change something. Also, naming things gives you the opportunity t...
Rails: Rest API post-mortem analysis
This is a personal post-mortem analysis of a project that was mainly build to provide a REST API to mobile clients.
For the API backend we used the following components:
- Active Model Serializer (AMS) to serializer our Active Record models to JSON.
- JSON Schema to test the responses of our server.
- SwaggerUI to document the API.
It worked
The concept worked really good. Here are two points that were extraordinary compared to normal Rails project with many UI components:
- Having a Rails application, that has no UI components (only...
How to: Validate dynamic attributes / JSON in ActiveRecord
PostgreSQL and ActiveRecord have a good support for storing dynamic attributes (hashes) in columns of type JSONB
. But sometimes you are missing some kind of validation or lookup possibility (with plain attributes you can use Active Record's built-in validations and have your schema.rb
).
One approach about being more strict with dynamic attributes is to use JSON Schema validations. Here is an example, where a project has the dynamic attributes analytic_stats
, that we can use to store analytics from an external measurement tool.
- A g...
Structuring Rails applications: the Modular Monorepo Monolith
Root Insurance runs their application as a monolithic Rails application – but they've modularized it inside its repository. Here is their approach in summary:
Strategy
- Keep all code in a single repository (monorepo)
- Have a Rails Engine for each logical component instead of writing a single big Rails Application
- Build database-independent components as gems
- Thus: gems/ and engines/ directories instead of app/
- Define a dependency graph of components. It should have few edges.
- Gems and Engines can be extracted easier once nece...
Always convert and strip user-provided images to sRGB
Debugging image color profiles is hard. You can't trust your eyes in this matter, as the image rendering depends on multiple factors. At least the operation system, browser or image viewer software and monitor influence the resulting image colors on your screen.
When we offer our users the possibility to upload images, they will most likely contain tons of EXIF metadata and sometimes exotic color profiles like eciRGB. We want to get rid of the metadata, as it might contain sensitiv...
Defining new elements for your HTML document
Browsers come with a set of built-in elements like <p>
or <input>
. When we need a new component not covered by that, we often build it from <div>
and <span>
tags. An alternative is to introduce a new element, like <my-element>
.
When a browser encounters an unknown element like <my-element>
, the browser will proceed to render <my-element>
's children. The visual rendering of your page will not be affected.
If you care about their HTML being valid, your new element should contain a dash character (-
) to mark it as a *custom el...
Effectively Using Materialized Views in Ruby on Rails · pganalyze
It's every developer's nightmare: SQL queries that get large and unwieldy. This can happen fairly quickly with the addition of multiple joins, a subquery and some complicated filtering logic. I have personally seen queries grow to nearly one hundred lines long in both the financial services and health industries.
Luckily Postgres provides two ways to encapsulate large queries: Views and Materialized Views. In this article, we will cover in detail how to utilize both views and materialized views within Ruby on Rails, and we can even take...
A Migration Path to Bundler 2+
Bundler 2 introduced various incompatibilites und confusing behavior. To add to the confusion, Bundler's behavior changed after the release of their version 2.
The linked article explains what happened.
Resources for working with the Youtube API
You can use the Youtube API to get data from youtube. Here are some resources and some things that are good to know.
You need to create an API key and each request will cost a certain amount of Request-Points (depending on the kind of query).
It may help to understand youtube's domain model before starting.
It's best to just try out your requests in the browser to see what you will get.
A request can look like this (note that you have to replace the playlis...
Capybara: Execute asynchronous JavaScript
Capybara provides execute_script
and evaluate_script
to execute JavaScript code in a Selenium-controlled browser. This however is not a good solution for asynchronous JavaScript.
Enter evaluate_async_script
, which allows you to execute some asynchronous code and wait until it finishes. There is a timeout of a couple of seconds, so it will not wait forever.
Use it like this:
page.evaluate_async_script(<<~JS)
let [done] = arguments
doSomethingAsynchronous().then(() => {
done() // call this to indicate we're done
})
J...
Email validation regex
There is a practical short list for valid/invalid example email addresses - Thanks to Florian L.! The definition for valid emails (RFC 5322) can be unhandy for some reasons, though.
Since Ruby 2.3, Ruby's URI lib provides a built-in email regex URI::MailTo::EMAIL_REGEXP
. That's the best solution to work with.
/\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[...
WProofreader: How to manually enable for a WYSIWYG editor instance (with CKeditor 4 example)
WProofreader is a spelling and grammar checking tool that integrates with textareas and numerous WYSIWYG editors.
While it usually activates automatically, depending on your application, it may fail to boot.
In my case, an application was using CKEditor 4 and programmatically activated CKEditor. WProofreader's autoSearch
logic failed to hook into that for some reason.
You can choose not to use its autoSearch
feature, but explicitly enable WProofreader.
Here is a guide for CKEditor 4. It should apply to other WYSIWYG editors as well.
1...
Checking database size by row count
As an application exists, data accumulates. While you'll be loosely monitoring the main models' record count, some supportive database tables may grow unnoticed.
To get a quick overview of database table sizes, you can view the row count like this:
PostgreSQL
SELECT schemaname,relname,n_live_tup
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC
LIMIT 12;
schemaname | relname | n_live_tup
------------+------------------------------------------------+------------
public | images...
How to enable Chromedriver logging
When using Chrome for Selenium tests, the chromedriver
binary will be used to control Chrome. To debug problems that stem from Selenium's Chrome and/or Chromedriver, you might want to enable logging for the chromedriver itself. Here is how.
Option 1: Use Selenium::WebDriver::Service
In your test setup, you may already have something like Capybara::Selenium::Driver.new(@app, browser: :chrome, options: ...)
, especially when passing options like device emulation.
Similar to options
, simply add an extra key service
and pass an inst...
Bash script to list commits by Pivotal Tracker ID
The main benefit of our convention to prefix commits by their corresponding Pivotal Tracker ID is that we can easily detect commits that belong to the same story. You can either do that manually or use the bash script below by copying it somewhere to your .bashrc
.
# Usage: ptcommits 123456
function ptcommits {
if test "$1"
then
local PTID=$(echo "$1" | grep "[0-9]*" -o) # Allow URLs
git log --onel...
Joining PDFs with Linux command line
There are several ways to merge two (or more) PDF files to a single file using the Linux command line.
If you're looking for graphical tools to edit or annotate a PDF, we have a separate card for that.
PDFtk (recommended)
PDFtk is a great toolkit for manipulating PDF documents. You may need to install it first (sudo apt install pdftk
).
Merging multiple files works like this:
pdftk one.pdf two.pdf cat output out.pdf
Unlike pdfjam, PDFtk should not mess with page sizes but simply joins pages as they are.
...
Known MariaDB version incompatibilities
MariaDB (and MySQL) is released in different versions with different behaviors. For backwards compatibility this can be managed with the sql_mode
option.
Following you can find behavior differences which cannot be changed by this option. This can cause a regression in an existing application. There is no workaround available. The application needs to be adapted.
Queries with aggregated date types
For a database instance of MariaDB-10...
Ruby: How to use global variables for a conditional debugger
You can share a state in Ruby with global variables. Even if you should avoid them whenever possible, for debugging an application this could be temporary quite handy.
Example:
class User
after_save { byebug if $debug; nil }
def lock
self.locked = true
save
end
end
Rspec.describe User do
let(:user) { create(:user) }
before do
# Many users are created and saved in this hook, but we don't want the debugger to stop for them...
Regular Expressions: Excessive backtracking can get yourself in trouble
Two weeks ago, Cloudflare was struck by a global outage that lasted ~30 minutes. The incident was rooted on a CPU exhaustion caused by a single regular expression containing some catastrophic backtracking:
.*(?:.*=.*)
This is a small reminder do keep using the lazy operator ?
whenever possible and furthermore be aware that regular expressions should not only be unit-tested but also evaluated in terms of performance. See <https://makandracards.com/makandra/494822-regul...
Webpack(er): A primer
webpack is a very powerful asset bundler written in node.js to bundle (ES6) JavaScript modules, stylesheets, images, and other assets for consumption in browsers.
Webpacker is a wrapper around webpack that handles integration with Rails.
This is a short introduction.
Installation
If you haven't already, you need to install node.js and Yarn.
Then, put
gem 'webpacker', '~> 4.x' # check if 4.x is still cu...
RubyMine: Efficiently filtering results in the "Finder" overlay
RubyMine comes with a nice way to grep through your project's files: The finder (ctrl + shift + f
). Don't be discouraged about the notice 100+ matches in n+ files
if your searched keyword is too general or widely used in your project.
RubyMine comes with a few ways to narrow down the resulting list, don't hesitate to apply those filters to speed up your search. Your keybinding might vary based on your personal settings.
File mask (alt + k
)
If you already know the file extension of your ...
Jasmine: using async/await to write nice asynchronous specs
Jasmine has long standing support for writing asynchronous specs. In days gone by we used the done
callback to achieve this, but these days it is possible to write much more readable specs.
Async specs
As a first example, say we want to check that some form disables the submit button while working.
// bad (how we used to do it)
beforeEach(() => {
this.form = setupMyForm()
this.submitButton = findTheSubmitButton()
})
it('disables the submit button while working', (done) => {
expect(this.submitButton.disabled).toBe(false)
...
ActiveType 1.2 supports "change_association"
With ActiveType 1.2 you can modify associations (has_many
etc.) after they have been defined.
One common use case for this is to change an association inside a form model, like this:
class Credential < ActiveRecord::Base
end
class User < ActiveRecord::Base
has_many :credentials
end
class SignUpCredential < ActiveType::Record[Credential]
end
class SignUp < ActiveType::Record[User]
change_association :credentials, class_name: 'SignUpCredential'
end
Now, if you load `credentials...
Katapult EOL
Katapult was an endeavor to dramatically speed up starting a new Rails application. However, it turned out to save less time than expected, while requiring more time for maintenance than anticipated. Since its benefits fell too low, we have decided to not continue developing Katapult.
You can still use Katapult for generating ready-to-run applications with model CRUD, authentication and all of Katapult's features, but the rapid development of the web will quickly render the generated code antiquated. Nevertheless, its architecture may remai...