Project management best practices: The story tracker

In general, the tracker should always be the definitive source of truth of what needs to be done as part of a project. If you identify a task that needs to be done, you should create a story. If you learn something that contradicts an existing story, change it.

The tracker can contain two types of stories: Developer stories, and non-developer stories.

Non-developer stories

Non-developer stories should be clearly marked. They usually belong to the PM (or maybe people from the operations team). Those story can take all forms, could just...

Reading an element's attributes with Capybara

capybara_element['attribute_name'] allows accessing an element's attributes in Capybara.

A few examples:

find('#my_element')['class']
# => "first-class second-class"

find('#my_input')['placeholder']
# => "My placeholder value"

find('a#example-link')['href']
# => "http://example.com"

find('#my_element')['missing_attribute']
# => nil

Using git patchfiles to speed up similar implementation tasks

Sometimes you'll find yourself with a set of tasks that require similar code for different models. For example, if you start working at a new application that allows CRUDing pears and apples, each commit might look similar to this:

commit 41e3adef10950b324ae09e308f632bef0dee3f87 (HEAD -> ml/add-apples-12345)
Author: Michael Leimstaedtner <michael.leimstaedtner@acme.com>
Date:   Fri Aug 11 09:42:34 2023 +0200

    Add Apples as a new fruit

diff --git a/app/models/apple.rb b/app/models/apple.rb
new file mode 100644
index 0000000..a51...

JavaScript has a native event emitter

Suppose you want to implement a publish/subscribe pattern in your Frontend application to react to data changes and events. First, you might be looking for an event emitter library.

Today I learned that vanilla JavaScript comes with a native event emitter, which I've been indirectly using forever. There's no need for extra code!

const emitter = new EventTarget()

emitter.addEventListener('YOLO', () => {
  console.log('YOLO');
})

// invoke attached event listeners
emitter.dispatchEvent(new Event('YOLO'));

Git stash: Working with old entries

First find the reference for the entry you want through looking at the stash:

$ git stash list
stash@{0}: WIP on feature/foo
stash@{1}: WIP on feature/bar
stash@{2}: WIP on fix/baz

Now you can simply use that reference, but curly braces must be escaped:

git stash pop stash@\{1\}

or quoted:

git stash apply "stash@{1}"

Quick reminder to [not shoot yourself in the foot](https://makandracards.com/makandra/634-use-the-git-stash-withou...

Don't assign time values to date attributes

Do not pass times to date attributes. Always convert times to dates when your application uses time zones.

Background

A time-zoned Time attribute on a Rails record is converted to UTC using to_s(:db) to be stored, and converted back into the correct time zone when the record is loaded from the database. So when you are not on UTC, time objects will be converted as follows.

>> Time.current
=> Fri, 15 Mar 2013 11:56:03 CET +01:00
>> Time.current.to_s(:db)
=> "2013-03-15 10:56:03" # This is now UTC

Problem

That will...

Webmock normalizes arrays in urls

Typhoeus has a different way of representing array params in a get request than RestClient.

Typhoeus: http://example.com/?foo[0]=1&foo[1]=2&foo[2]=3
RestClient: http://example.com/?foo[]=1&foo[]=2&foo[]=3

Webmock normalizes this url when matching to your stubs, so it is always http://example.com/?foo[]=1&foo[]=2&foo[]=3. This might lead to green tests, but in fact crashes in real world. Rack::Utils.build_nested_query might help to build a get re...

CarrierWave: Processing images with libvips

When you write your next CarrierWave uploader, consider processing your images with libvips instead of ImageMagick.

Reasons for libvips

There are several upsides to using libvips over ImageMagick:

How to find child nodes that match a selector with JavaScript

Using querySelector or querySelectorAll in JavaScript, you can easily find descendants of a node that match a given selector.

But what if you want to find only children (i.e. direct descendants) of an element?
Easy: use :scope. It references the element on which DOM API methods are being called:

element.querySelectorAll(':scope > .your-selector')

Example

Consider this HTML

<body>
  <div id="container1">
    <div id="container1a">foo</div>
    <div id="container1b">bar</div>
    <div id="container1c">baz</...

Code splitting in esbuild: Caveats and setup

TL;DR Still has caveats.

Code splitting is a feature of JavaScript bundlers that can keep huge libraries out of the main bundle.

How code splitting works

Like Webpack esbuild lets you use the await import() function to load code on demand:

// application.js
const { fun } = await import('library.js')

fun()

However, esbuild's code splitting is disabled by default. The code above would simply [inline](https://en.wiki...

RubyMine and Rubocop: Performing safe autocorrects on save

  • Ctrl + Alt + S > search "rubocop on save"
  • Under "Inspections", check the highlighted box on rubocop -a

Caveat: This adds a little time overhead to saving. When you're editing many files at once (e.g. using "Replace All"), this may be inacceptable.

RubyMine: Fixing "Rubocop returned exit code -1. See the logs for details"

When RubyMine reports Rubocop returning "exit code -1", upgrading Rubocop can be the fix:

gem install rubocop

"The logs" can be accessed with Ctrl + Shift + A > Show log in (Files). This opens your file manager at the IDEA log location; the log file is called "idea.log".
If your operating system supports it, right click into the file manager > Open in Terminal. There run tail -f idea.log to follow the log.

Create and send any HTTP request using the Postman request builder

Talking with APIs makes more fun using Postman. As an alternative you can also use command line tools like cURL.

Image

snap install postman

How does it help me?

  • Editing multiline JSON bodies is much more comfortable than in the terminal
  • Saving named requests in a collection (can be shared with others)
  • Syntax highlighting when writing JSON bodies
  • History with all my requests
  • Multiple environments
  • Cookie manager

We now have our own memoization gem "Memoized"

We forked trusty memoizer to make two changes:

  1. Memoized methods now preserve their arity. Previously all memoized methods had an arity of -1.
  2. Memoized methods are now faster at runtime. This will only be noticable if you call a memoized methods many times in the same request.

We published our fork as a new gem named memoized.

memoized is API-compatible to memoizer, you just need to include Memoized instead of `M...

Cucumber: Identifying slow steps that drag down your test speed

In most projects I know, Cucumber test suite speed is not an issue. Of course, running 350 features takes its time, but still each test for itself is reasonably fast. There is nothing you can do to fundamentally speed up such a test (of course, you should be using parallel_tests).

However, in projects that go beyond clicking around in a web GUI and checking results, there might be various measures to speed things up. Katapult tests for example could be sped up more than 4 times by re...

Balance your texts today with text-wrap: balance

So you have a heading that is just barely wider than the container it should fit into, and it wraps a single word to a new line and it's not really pretty?
Cry no more, for you can use text-wrap: balance today to fix that. At least in some browsers.

When browsers encounter a text-wrapping element with text-wrap: balance style, they will try breaking to a new line sooner, if it balances out the width of lines.

Without text-wrap: balance With text-wrap: balance
Image ![...

Checklist for Implementing Design

We have a long-standing checklist for merge requests. However, it hardly matches the intricate requirements for design. This checklist fills the gap.

Before starting implementing, look at all designs: are there components similar to yours? Have they already been implemented? Can you build on this prior art when implementing yours?

Checklist: I confirm my design implementation

  • has been tested manually by me
  • adheres to the code style of the project (e.g. BEM)
  • avoids "magic numbers" (don't say e.g. ...

Ruby: alias_method conflicting with prepend

alias_method makes a copy of a method. This works fine until the same method is overridden using prepend. If the prepend is executed after alias_method, you'll either see an infinite loop, or loose the prepended functionality.

Solution

Your options are:

  • Ensure prepend happens before alias_method
  • If you can control the alias_method invocation: rewrite to the modern & better prepend
  • If you can only control the prepend invocation: rewrite to the legacy alias_method

Git: Splitting up changes into several commits

Splitting up commits makes the process of reviewing often easier, since you can create several merge requests or review every commit one by one.

So when you find out that you have portions of the code that you initially didn't intend to change or when you do some refactoring along the current changes, you can use one of the following processes to split up the changes into several commits in a logical order:

#1 Splitting up the last n commits into m commits
#2 Adding changes to a previous commit
2.1 While adding new changes
2.2 S...

Best practice: How to manage versions in a package.json

It most cases it's not necessary to add a version constraint next to your packages in the package.json. Since all versions are saved in a lockfile, everyone running yarn install will get exactly the same versions. Yarn saves this lockfile as yarn.lock and npm as package-lock.json.

There are some exceptions, where you can consider adding a version constraint to the package.json:

  • You are not checking the lockfile into version control (not recommended)
  • A specific package has a bug in a more recent version
  • You want to ensure no...

Best practice: How to manage versions in a Gemfile

It most cases it's not necessary to add a version constraint next to your gems in the Gemfile. Since all versions are saved in the Gemfile.lock, everyone running bundle install will get exactly the same versions.

There are some exceptions, where you can consider adding a version constrain to the Gemfile:

  • You are not checking in the Gemfile.lock into the version control (not recommended)
  • A specific gem has a bug in a more recent version (adding a comment for the reason is highly recommended)
  • You want to ensure no one upgrade...

RSpec 3 argument constraints use weak equality

If you expect method calls in RSpec 3, be aware that the argument matchers use very liberal equality rules (more like === instead of ==).

For example:

expect(subject).to receive(:foo).with(MyClass)

subject.foo(MyClass)      # satisfies the expectation
subject.foo(MyClass.new)  # also satisfies the expectation

expect(subject).to receive(:bar).with(/regex/)

subject.bar(/regex/)      # satisfies the expectation
subject.bar('regex')      # also satisfies the expectation

This is usually not an issue, except when your method ...

Rails < 5: How to get after_commit callbacks fired in tests

If you use transactional_fixtures or the database_cleaner gem with strategy :transaction, after_commit callbacks will not be fired in your tests.

Rails 5+

Rails 5 has a fix for this issue and no further action is needed.

Rails 3, Rails 4

Add the gem test_after_commit to your test group in the Gemfile and you are done. You don't need to change the database strategy to deletion (wh...

ActiveRecord: Creating many records works faster in a transaction

When you need to insert many records into the same table, performance may become an issue.

What you can do to save time is to open a transaction and save multiple records within that transaction:

transaction do
  500.times { Model.create! }
end

Although you will still trigger 500 INSERT statements, they will complete considerably faster.

When I tried it out with a simple model and 500 iterations, the loop completed in 1.5 seconds vs. 6 seconds without a transaction.

Alternative

Another fast way to insert many ...