3891 cards
Linked contentRepeats

Custom error messages in RSpec or Cucumber steps

Sometimes you have a test expectation but actually want a better error message in case of a failure. Here is how to do that.

Background

Consider this test:

expect(User.last).to be_present

In case of an error, it will fail with a not-so-helpful error message:

expected present? to return true, got false (Spec::Expectations::ExpectationNotMetError)

Solution

That can be fixed easily. RSpec expectations allow you to pass an error message like this:

expect(User.last).to be_present, 'Could not find a user!'

Now your t...

Repeats

ActiveRecord: When aggregating nested children, always exclude children marked for destruction

When your model is using a callback like before_save or before_validation to calculate an aggregated value from its children, it needs to skip those children that are #marked_for_destruction?. Otherwise you will include children that have been ticked for deletion in a nested form.

Wrong way

class Invoice
  has_many :invoice_items
  accepts_nested_attributes_for :invoice_items, :allow_destroy => true # the critical code 1/2
  before_save :calculate_and_store_amount                              # the critical code 2/...

Carrierwave: Using a nested directory structure for file system performance

When storing files for lots of records in the server's file system, Carrierwave's default store_dir approach may cause issues. Here is a simple solution that scales for a long while.

The default storage directory from the Carrierwave templates looks like so:

class ExampleUploader < CarrierWave::Uploader::Base
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

If you store files for 500k records, that store_dir's parent directory will have 500k sub-directories which will cause s...

Repeats

Carrierwave: How to remove container directories when deleting a record

When deleting a record in your Rails app, Carrierwave automatically takes care of removing all associated files.
However, the file's container directory will not be removed automatically. If you delete records regularly, this may be an annoyance.

Here is a solution which was adapted from the Carrierwave GitHub wiki and cleans up any empty parent directories it can find.

class ExampleUploader < CarrierWave...

Debugging your Webpack build time with Speed Measure Plugin

If your Webpack build is slow, you can use the Speed Measure Plugin for Webpack to figure out where time is being spent.
Note that at time of writing, Webpack 5 seems unsupported. It works on Webpack 4, though.

Wire it into your application as described in the library's documentation:

  1. Hook into your environment file, e.g. config/webpack/development.js and instead of exporting your Webpack config,...
Repeats

The Ruby Object Model

In Ruby (almost) everything is an Object. While this enables a lot of powerful features, this concept might be confusing for developers who have been programming in more static languages, such as Java or C#. This card should help understanding the basic concepts of Ruby's object model and how things behave.


Usage of objects in Ruby

When working with objects in Ruby, you might think of a "container" that holds metadata, variables and methods. Metadata describes stuff like the object's class or its object_id whi...

Run code before or after an existing Rake task

Before

To run additional code before an existing Rake tasks you can add a dependency like this:

task :before_task do
  # runs before :existing_task
end

Rake::Tasks[:existing_task].enhance [:before_task]

The new dependency will be called after all existing dependencies.

After

To run additional code after an existing Rake tasks, pass a block to enhance:

Rake::Tasks[:existing_task].enhance do
  # runs after :existing task
end

Webpacker: Loading code on demand

Sometimes you want to load code on demand. For instance, when a a large library is only used on a single screen, this library should only be loaded for that screen. It should not blow up the bundle size for all screens.

You can load code on demand by using import() as a function (with parentheses) instead of using it as a keyword import (without parentheses). The import() function returns a promise with the exported variables:

let exports = await import('large-library')

console.log("A named export is ", exports.exportedName)
c...
Linked contentAuto-destruct in 37 days

Updated: How to make Webpacker compile once for parallel tests, and only if necessary

The solution was rewritten to be more robust:

  • Fix "ChunkLoadError: Error loading chunk 0" in parallel tests runs (affected apps that have a config.asset_host)
  • Prevent RSpec from compiling packs once for each test process when calling specs without a local webpack-dev-server
  • Don't freeze the parallel tests suite if the first test process crashes during compilation
  • Prevent making a trillion SHA1 hashes over the file contents of every file in app/ during tests
  • Remove duplicate digest logic already implemented in Webpacker itself
  • ...

When does Webpacker compile?

Webpack builds can take a long time, so we only want to compile when needed.

This card shows what will cause Webpacker (the Rails/Webpack integration) to compile your assets.

When you run a dev server

While development it is recommended to boot a webpack dev server using bin/webpack-dev-server.

The dev server compiles once when booted. When you access your page on localhost before the initial compilation, the page may load without assets.

The ...

Linked contentRepeats

Protected and Private Methods in Ruby

In Ruby, the meaning of protected and private is different from other languages like Java. (They don't hide methods from inheriting classes.)

private

Private methods can only be called with implicit receiver. As soon as you specify a receiver, let it only be self, your call will be rejected.

class A
 
  def implicit
    private_method
  end
  
  def explicit
    self.private_method
  end
  
  private
  
  def private_method
    "Private called"
  end
  
end

...

Linux: Using grep with a regular expression

You can use three different versions of the regular expression syntax in grep:

  • basic: -G
  • extended: -E(POSIX)
  • perl: -P (PCRE)

Difference between basic and extended:

In basic regular expressions the meta-characters '?', '+', '{', '|', '(', and ')' loose their special meaning; instead use the backslashed versions '?', '+', '{', '|', '(', and ')'.

Difference between extended (POSIX) and perl (PCRE): E.g. \d is not supported in POSIX.

This g...

Repeats

How to iterate over an Enumerable, returning the first truthy result of a block ("map-find")

Ruby has Enumerable.find(&block), which returns the first item in the collection for which the block evaluates to true.

first_post_with_image = posts.find do |post|
  post.image
end

However, sometimes it's not the item you're interested in, but some value depening on it – e.g. the value the block evaluated to. You could first map the collection and then take the first truthy value, but this way you need to process the whole collection twice:

first_image_url = posts.map(&:image).find(&:present?).url

If the mapping ...

Linked content

Limiting GitLab CI runner to specific branches or events

Use rules to include or exclude jobs in pipelines.

Rules are evaluated in order until the first match. When a match is found, the job is either included or excluded from the pipeline, depending on the configuration. The job can also have certain attributes added to it.

rules replaces only/except and they can’t be used together in the same job. If you configure one job to use both keywords, the linter returns a key may not be used with rules error.

GitLab 12.3 introduced rules. You can use them in your .gitlab-ci.yml in your proj...

Repeats

Clean code: Avoiding short versions in command options

This card is a general reminder to avoid the short version of a command option in shared code. It's much easier to understand a command and search for an option when it's written out.

You can still use the short version of the options in your own terminal or in code snippets that are more useful when they are very compact. For the latter case you often see a description of the command options one line below e.g. in posts on stackoverflow.

Example good (in code):

/usr/bin/gpg --output password.txt --decrypt password.txt.gpg

...

Bundler: Packaging gems into the git repository (offline installation)

Installing gems on a server that has no access to the internet (especially rubygems.org) requires to bundle the gems into the repository itself. This requires to adjust the bundle config in the repository.

  1. Create a file .bundle/config with the following content.
---
BUNDLE_PATH: "vendor/"
BUNDLE_DISABLE_SHARED_GEMS: "true"
  1. Change your .gitignore.
# Make sure to check in .bundle
# /.bundle/

/vendor/ruby
  1. Run bundle package and commit the changes together with all gems in vendor/cache/.

Afterwards you c...

How to generate and test a htpasswd password hash

Generate a password

htpasswd -Bn firstname.lastname

This will ask you for a password and use bcrypt (-B, more secure) and print the output to stdout (-n).

Check if password matches the hash

You'll first have to write the password hash to a file:

echo firstname.lastname:$2y$05$4JXxd2GM/J2...9c3KJmFS > htpass_test

Check, if it is correct:

htpasswd -v htpass_test firstname.lastname

You probably should not use the -b switch to read the password from the command line as the password will then be visible...

Repeats

How to push to Git without running CI on GitLab CI, GitHub Actions, or Travis CI

If a project ist configured to spawn CI runners for tests or deployment when pushing to the Repo, a habit of pushing WIP commits regularly may conflict with that.
Here are two solutions that allow you to keep pushing whenever you feel like it.

Special commit message

To skip a CI run, simply add [ci skip] or [skip ci] to your commit message. Example:

git commit -m "wip authentication [ci skip]"

Git push options (GitLab)

In addition to that, GitLab CI supports Git push options. Instead of changing your commit message, ...

This website uses short-lived cookies to improve usability.
Accept or learn more