Project management best practices: Budget control
When starting a project we always make a good estimate of all known requirements, and plan budgets and available developers accordingly.
Requirements change. Budgets usually don't.
To make sure a project stays on track, we update our estimates once a month and compare them to the remaining budget. If this doesn't match any more, we have to act.
To update an estimate, do the following:
- Start with the most recent estimate for the project.
- Which stories have been completed? Set their estimate to zero.
- Have any requirements cha...
Careful: `fresh_when last_modified: ...` without an object does not generate an E-Tag
To allow HTTP 304 responses, Rails offers the fresh_when
method for controllers.
The most common way is to pass an ActiveRecord instance or scope, and fresh_when
will set fitting E-Tag
and Last-Modified
headers for you. For scopes, an extra query is sent to the database.
fresh_when @users
If you do not want that magic to happen, e.g. because your scope is expens...
Firefox cancels any JavaScript events at a fieldset[disabled]
If you try to listen to events on elements that are nested inside a <fieldset disabled>
, Firefox will stop event propagation once the event reaches the fieldset. Chrome and IE/Edge will propagate events.
Since we often bind event listeners to document
this can be annoying.
You could solve it by...
- ...adding event listeners on elements themselves. Note that this is terrible when you have many elements that you'd register events for.
- ...adding event listeners on a container inside the
<fieldset>
, around your eleme...
SQL: Fast threshold counts with LIMIT
Performing COUNT(*) on large tables is slow. Sometimes you don’t need the exact number once results exceed a certain threshold.
For example, you may only need to display 100+ in the UI. Using a plain COUNT(*) would scan all matching rows.
The following query would be pretty slow when counting many rows because it has to scan all rows.
SELECT COUNT(*) FROM movies;
Limiting within a subquery
Use a subquery with LIMIT
to cap the scan early. You’ll get the exact number up to 100 otherwise you can show 100+.
SE...
How to tweak RSpec's truncation behavior
When RSpec sets out to print any given object to the console, it will never print more than 200 characters from it. Only the first and last 100 chars from overly long strings are displayed, which sometimes means that the root cause for the failing test is buried in truncation.
For example, I could not easily make out the reason for this failure:
Failure/Error: expect { crawler.pull_new_commits }.not_to raise_error
expected no Exception...
Matching Unicode characters in a Ruby regexp
On Ruby 1.9+, standard ruby character classes like \w
, \d
will only match 7-Bit ASCII characters:
"foo" =~ /\w+/ # matches "foo"
"füü" =~ /\w+/ # matches "f", ü is not 7-Bit ASCII
There is a collection of character classes that will match unicode characters. From the documentation:
-
/[[:alnum:]]/
Alphabetic and numeric character -
/[[:alpha:]]/
Alphabetic character -
/[[:blank:]]/
Space or tab -
/[[:cntrl:]]/
Control character -
/[[:digit:]]/
Digit -
/[[:graph:]]/
Non-blank character (excludes spaces...
JavaScript: Stopping expensive work in inactive tabs
Is your application doing something expensive every few seconds? Maybe an animated slider that rotates images? Maybe you are updating data over the network every five minutes? It's a good idea to pause this if the your document tab is not even visible to the user. This saves your user's battery and data plan.
You can ask document.visibilityState
or document.hidden
whether this tab is visibl...
zizmor - Static analysis for GitHub Actions
The linked tool can be used to scan your CI/CD workflows for potential security issues and suboptimal defaults if they are based on GitHub Actions.
For example, it warns you about
- string interpolations that may expand into attacker-controllable code
- suboptimal defaults like e.g.
persist-credentials: true
for the checkout action - actions that are not pinned to a tag instead of a git SHA
Some of the warnings can be auto-fixed. The tool comes wi...
Rails 8: The db:migrate task might not run all migrations in db/migrate/ anymore
In Rails 8 the behavior of the rails db:migrate
command has changed for fresh databases (see PR #52830).
- Before Rails 8: The command runs all migrations in the folder
db/migrate/*
- After Rails 8: The command loads the schema file (
db/schema.rb
ordb/structure.sql
) if existing and runs all pending migrations in the folderdb/migrate/*
afterwards
This speeds up the command. But e.g. migrations with data manipulations are missing.
The only way to run all pending mig...
How to reliably center (block) icons vertically with text
vertical-align
is hard. Have you ever wanted to vertically center an icon with text? This usually means "vertically align with capital letters", as visually, a text line goes from baseline up to the capital top. (That's because descenders are far less frequent than ascenders.)
In this card we'll vertically center an icon (or any "blockish" inline element, really) with the capital letters of surrounding text. This works well with our [modern approach to SVG icons](/mak...
Ruby tempfiles
With the the Ruby Tempfile
class you can create temporary files. Those files only stick around as long as you have a reference to those. If no more variable points to them, the GC may finalize the object at some point and the file will be removed from the file system. In other words: tempfiles are removed automatically. If you would then try to access the tempfile using its path (which you stored previously), you would get an error because the file no longer exists.
You can proactively unlink your tempfiles to delete them earlier...
How to use pessimistic row locks with ActiveRecord
When requests arrive at the application servers simultaneously, weird things can happen. Sometimes, this can also happen if a user double-clicks on a button, for example.
This often leads to problems, as two object instances are modified in parallel maybe by different code and one of the requests writes the results to the database.
In case you want to make sure that only one of the requests "wins", i.e. one of the requests is fully executed and completed while the other one at least has to wait for the first request to be completed, you ha...
Unpoly 3.12.0 released
This release adds asynchronous compilers and many other features requested by the community.
We also fixed a number of performance regressions introduced by Unpoly 3.11.
Breaking changes are marked with a ⚠️ emoji and polyfilled by unpoly-migrate.js
.
Asynchronous compilers
Compiler functions can now be [async
](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Testing HTTPS with badssl.com
Website that offers lots of different kinds of HTTPS configurations, bad or good or complicated.
They also offer a dashboard to check if your browser's HTTPS handling works as expected (which might be compromised e.g. due to security products or enterprise proxy servers).
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...
Speed up your sass compile with sass-embedded
Note
Compiling Sass is probably the slowest part of your build, so this is worth a try if you're using Sass.
If you're using the sass
npm package to compile your SASS/SCSS, consider switching to sass-embedded
. It should be a drop-in replacement in a large app and it's around 50% faster than using the deprecated sass
package.
Geordi 12.6.0 was released
Changelog
-
geordi dump
: Allow to forward the compression option to the underlyingdumple
command, e.g.geordi dump --compress=zstd:3
(for PostgreSQL) orgeordi dump --compress
(for MySQL). -
dumple
: Allow to specify a compression algorithm for PostgreSQL, e.g.dumple --compress=zstd:3
. The already supported compression for MySQLdumple --compress
is kept untouched.
In PostgreSQL you will notice a speedup of ~50%.
Example
Performance improvements during a deploy (e.g. lib/capistrano/tasks/db.rake
)
namespace...
How to change the time for a Docker container from the outside
I had to modify the time for an application that I launch through Docker.
Here is an approach that worked for me without modifying or wrapping the container image.
Note
This is quite hacky and may not work for everyone. The "proper" way is probably to have a custom
Dockerfile
to wrap an existing image, or adjust a custom image'sDockerfile
. I wanted to do neither.
So, I had an application that I ran through a pre-built image. Basically this:
docker run --rm -it example/image
The [faketime](https://manpages.ubuntu.c...
GoodJob: Ensure you get notified about background exceptions
GoodJob and ActiveJob rescue exceptions internally, preventing exception_notification from triggering. This can cause silent job failures.To get notified, subscribe to ActiveJob events and configure GoodJob's on_thread_error
hook. This lets you manually call your exception notifier for every retry, discard, or internal GoodJob error.
# config/initializers/good_job.rb
# Manually notify on job failures, as they are handled internally by ActiveJob/GoodJob.
ActiveSupport::Noti...
Format your JavaScript with prettier
prettier calls itself an opinionated code formatter. I recommend using it for your JavaScript and TypeScript code.
prettier only concerns itself with the formatting of your JavaScript (and also some other file types like json or yaml). When you use it, it has an opinion on every single whitespace and linebreak, as well as a few other things. You renamed a variable and now your line is a bit longer than looks good? prettier will reformat your code.
This might not work for you if you have strong opinions yourself....
Ruby: Avoiding errors when casting user input to Integers
There's a method Integer() defined on Kernel
, that typecasts everything into an Integer.
Integer("2") # 2
Integer("foo") # Invalid value for Integer() (ArgumentError)
Integer(nil) # Can't convert nil to Integer (TypeError)
Integer([]) # Can't convert Array into Integer (TypeError)
Integer(Object.new) # Can't convert Object into Integer (TypeError)
Integer(2) # 2
Integer("11", 2) # 3
This is very similar but not identical to to_i
:
"2".to_i # 2
"foo".to_i #...
Automatic Log Rotation in Rails
Rails log files rotate automatically when they reach approx. 100MB:
$ ls -lh log/
-rw-r--r-- 1 user group 55M Sep 15 09:54 development.log
-rw-r--r-- 1 user group 101M Aug 22 13:45 development.log.0
This behavior is a built-in feature of Ruby's standard Logger
class, which Rails uses by default.
To control the maximum file size, set config.log_file_size in yo...
Checklist: Rails Authentication
Authentication is a special part of web applications. On the one hand, it usually is a crucial security mechanism restrict access to certain people and roles. On the other hand, most users authenticate only once, so it is very unlikely to spot issues by accident.
So, here comes a quick checklist to help you verifying your authentication solution is all set.
- This should be default: use HTTPS with HSTS. The HSTS part is important.
- Use a reliable authentication solution, e.g. [Compose Rails authentication primitives](https://makandracards...
Documenting your project's Node.js version in .nvmrc
Not all versions of Node.js are compatible with each other. Also npm packages may require a minimum or maximum version of Node.js. We use nvm on our development PCs so we can operate multiple versions of Node.js in parallel.
To make sure that all developers use a compatible version of Node.js, your project should declare the required Node.js in a file called .nvmrc
.
When a .nvmrc
exists, developers can cd
in your project directory and activate the p...