RailsStateMachine 2.2.0 released

  • Added: State machine can now use the :prefix-option to avoid name collision if you define multiple state machines on the same model, and use state names more than once
    • Previously state_machine-definitions like this:
        class Form
        
          state_machine :first_wizard_stage do
            state :completed
          end
      
          state_machine :second_wizard_stage do
            state :completed
          end
          
        end
      
      would produce the following warning:
        rails_state_machine-2.1....
      

Livereload + esbuild

Getting CSS (and JS) live reloading to work in a esbuild / Rails project is a bit of a hassle, but the following seems to work decently well.

We assume that you already use a standard "esbuild in Rails" setup, and have an esbuild watcher running that picks up your source code in app/assets and compiles to public/assets; if not change the paths below accordingly.

Basic idea

We will

  • use the guard-livereload gem as the livereload server (which send updates to the browser),
  • use the livereload-js npm package in the browser to con...

Webpack: How to split your bundles

To keep JavaScript sources small, it can sometimes make sense to split your webpack bundles. For example, if your website uses some large JavaScript library – say TinyMCE – which is only required on some selected pages, it makes sense to only load that library when necessary.

In modern webpack this is easily doable by using the asynchronous import function.

Say we have an unpoly compiler that sets up TinyMCE like this (code is somewhat simplified):

// TinyMCE as part of the main bundle!

import tinymce from 'tinymce/tinymce'

// U...

MySQL: CONCAT with NULL fields

In MySQL,

CONCAT('foo', 'bar', NULL) = NULL

the NULL always wins in MySQL.

If you would rather treat NULL as an empty string, use CONCAT_WS (concatenation with separator) instead:

CONCAT_WS('', 'foo', 'bar', NULL) = 'foobar'

PostgreSQL

In PostgreSQL the NULL is not viral in CONCAT:

CONCAT('foo', 'bar', NULL) = 'foobar'

Simple database lock for MySQL

Note: For PostgreSQL you should use advisory locks. For MySQL we still recommend the solution in this card.


If you need to synchronize multiple rails processes, you need some shared resource that can be used as a mutex. One option is to simply use your existing (MySQL) database.

The attached code provides a database-based model level mutex for MySQL. You use it by simply calling

Lock.acquire('string to synchronize on') do
  # non-th...

Javascript: Avoid using innerHTML for unsafe arguments

Make sure that you use the correct property when editing an HTML attribute. Using innerHTML with unsafe arguments makes your application vulnerable to XSS.

  • textContent: Sets the content of a Node (arguments are HTML-safe escaped)
  • innerHTML: Sets the HTML of an Element (arguments are not escaped and may not contain user content)

Hierarchy

This hierarchy gives you a better understanding, where the textContent and the innerHTML properties are defined. It also includes (just for completeness) the innerText property, whi...

Transfer records to restore database entries (with Marshal)

If you ever need to restore exact records from one database to another, Marshal might come in handy.

Marshal.dump is part of the ruby core and available in all ruby versions without the need to install anything. This serializes complete ruby objects including id, object_id and all internal state.

Marshal.load deserializes a string to an object. A deserialized object cannot be saved to database directly as the the dumped object was not marked dirty, thus rails does not see the need to save it, even if the object is not present in...

RSpec: Messages in Specific Order

tl;dr

You can use ordered to ensure that messages are received in a specific order.

Example

expect(ClassA).to receive(:call_first).ordered
expect(ClassB).to receive(:call_second).ordered
expect(ClassB).to receive(:call_third).ordered

#ordered supports further chaining

Example

How to define constants with traits

When defining a trait using the Modularity gem, you must take extra steps to define constants to avoid caveats (like when defining subclasses through traits).

tl;dr

In traits, always define constants with explicit self.

If your trait defines a constant inside the as_trait block, it will be bound to the trait module, not the class including the trait.
While this may seem unproblematic at first glance, it becomes a problem when including trai...

Event delegation (without jQuery)

Event delegation is a pattern where a container element has a single event listener that handles events for all descendants that match a CSS selector.

This pattern was popularized by jQuery that lets you do this:

$('.container').on('click', '.message', function(event) {
  console.log("A message element was clicked!")
})

This technique has some advantages:

  1. When you have many descendants, you save time by only registering a single listener.
  2. When the descendants are changed dynamic...

Spreewald development steps

Our gem spreewald supports a few helpers for development. In case you notice errors in your Cucumber tests, you might want to use one of them to better understand the underlying background of the failure. The following content is also part of the spreewald's README, but is duplicated to this card to allow repeating.

Then console

Pauses test execution and opens an IRB shell with current cont...

How to (and how not to) design REST APIs · stickfigure/blog Wiki

In my career, I have consumed hundreds of REST APIs and produced dozens. Since I often see the same mistakes repeated in API design, I thought it might be nice to write down a set of best practices. And poke fun at a couple widely-used APIs.

Much of this may be "duh", but there might be a few rules you haven't considered yet.

RSpec: Composing a custom matcher from existing matchers

When you find similar groups of expect calls in your tests, you can improve readability by extracting the group into its own matcher. RSpec makes this easy by allowing matchers to call other matchers.

Example

The following test checks that two variables foo and bar (1) have no lowercase characters and (2) end with an exclamation mark:

expect(foo).to_not match(/[a-z]/)
expect(foo).to end_with('!')

expect(bar).to_not match(/[a-z]/)
expect(bar).to end_with('!')

We can extract the repeated matcher chains into a custom m...

Ruby: Using `sprintf` to replace a string at fixed named references

The sprintf method has a reference by name format option:

sprintf("%<foo>d : %<bar>f", { :foo => 1, :bar => 2 }) # => 1 : 2.000000
sprintf("%{foo}f", { :foo => 1 })                      # => "1f"

The format identifier %<id> stands for different data types to be formatted, such as %f for floats:

sprintf('%f', 1) # => 1.000000

Example:

This is quite useful to replace ...

`simple_format` does not escape HTML tags

simple_format ignores Rails' XSS protection. Even when called with an unsafe string, HTML characters will not be escaped or stripped!

Instead simple_format calls sanitize on each of the generated paragraphs.

ActionView::Base.sanitized_allowed_tags
# => #<Set: {"small", "dfn", "sup", "sub", "pre", "blockquote", "ins", "ul", "var", "samp", "del", "h6", "h5", "h4", "h3", "h2", "h1", "span", "br", "hr", "em", "address", "img", "kbd", "tt", "a", "acrony...

How to use Rails URL helpers in any Ruby class

If you have any class which requires access to some path methods generated by your routes. Even though you could technically include Rails.application.routes.url_helpers, this may include way too many methods and even overwrite some class methods in the worst case.

Instead, most of the time the following is advised to only make the desired methods available:

class Project
  delegate :url_helpers, to: 'Rails.application.routes'

  def project_path
    url_helpers.project_path(self)
  end
end

Using Rationals to avoid rounding errors in calculations

Ruby has the class Rational which allows you to store exact fractions. Any calculation on these variables will now use fractional calculations internally, until you convert the result to another data type or do a calculation which requires an implicit conversion.

Example use case:

Lets say you want to store the conversion factor from MJ to kWh in a variable, which is 1/3.6. Using BigDecimals for this seems like a good idea, it usually helps with rounding errors over a float, but the...

List of default browser stylesheets

Even when you app has no CSS at all, you still inherit a default user agent stylesheet from your browser.

These default styles vary from browser to browser:

Links found in A look at CSS Resets in 2018.

A reasonable default CSP for Rails projects

Every modern Rails app should have a Content Security Policy enabled.

Very compatible default

The following "default" is a minimal policy that should

  • "just work" for almost all applications
  • give you most of the benefits of a CSP

In your config/initializers/content_security_policy.rb, set

Rails.application.config.content_security_policy do |policy|
  policy.object_src :none
  policy.script_src :unsafe_eval, :strict_dynamic, :https # Browsers with support for "'strict-dynamic'" will ignore "https:"
  po...

Know your Haml comments

There are two distinct ways of commenting Haml markup: HTML and Ruby.

HTML comments

This will create an HTML comment that will be sent to the client (aka browser):

/= link_to 'Example', 'www.example.com'

This produces the following HTML:

<!-- = link_to 'Example', 'www.example.com' -->

Only use this variant if you need the comment to appear in the HTML.

Ruby comments

This will comment code so it will not be sent to the client:

-# = link_to 'foo'

99% of the time you'll be adding notes f...

Merging two arbitrary ActiveRecord scopes

(Rails has a method ActiveRecord::Relation#merge that can merge ActiveRecord scopes. However, its behavior has never been clear, and in Rails 7 it still discards conditions on the same column by the last condition. We discourage using #merge!)

The best way to merge ActiveRecord scopes is using a subquery:

scope_a.where(id: scope_b)

It is a little less concise than #merge, but unambiguous.

Example

Assume a model where a deal has many documents:

class Deal < ApplicationRecord
  has_many :...

Testing if two date ranges overlap in Ruby or Rails

A check if two date or time ranges A and B overlap needs to cover a lot of cases:

  1. A partially overlaps B
  2. A surrounds B
  3. B surrounds A
  4. A occurs entirely after B
  5. B occurs entirely after A

This means you actually have to check that:

  • neither does A occur entirely after B (meaning A.start > B.end)
  • nor does B occur entirely after A (meaning B.start > A.end)

Flipping this, A and B overlap iff A.start <= B.end && B.start <= A.end

The code below shows how to implement this in Ruby on Rails. The example is a class `Interv...

Zeitwerk: How to collapse folders in Rails

All direct child directories of app are automatically added to the eager- and autoload paths. They do NOT create a module for namespacing. This is intuitive, since there normally is no module Model, or module Controller. If you want to add a new base directory, there's no additional config needed.

Example

app
├── controllers
├── helpers
├── inputs # No config needed 
├── mailers
├── models
├── uploaders # No config needed
├── util # No config needed
└── workers # No config needed

Sometimes it's handy to group files wit...

How to: Upgrade CarrierWave to 3.x

While upgrading CarrierWave from version 0.11.x to 3.x, we encountered some very nasty fails. Below are the basic changes you need to perform and some behavior you may eventually run into when upgrading your application. This aims to save you some time understanding what happens under the hood to possibly discover problems faster as digging deeply into CarrierWave code is very fun...

Whitelists and blacklists

The following focuses on extension allowlisting, but it is the exact same thing for content type allowlisting with the `content_ty...