Ruby: Referencing global variables with the built-in English library

tl;dr

Don't forget require 'English' if you use a named global such as $LAST_MATCH_INFO. Otherwise this could result in an annoying bug.

With Ruby's build-in library English you can reference global variables with an english name. This makes you code easier to read and is also suggested by Rubocop's Style/GlobalVars cop.

Example before:

if 'foo' =~ /foo/
  puts $~[1] # => foo
end

Example af...

whenever: Installing cron jobs only for a given Rails environment or Capistrano stage

We use the whenever gem to automatically update the crontab of the servers we deploy to. By default, whenever will update all servers with a matching role (we use the :cron role ).

This card describes how to install some tasks only for a given Rails environment or for a given Capistrano stage ("deployment target").

Installing jobs only for a given Rails environment
-----------------------------------...

Ruby: Using the pry debugger in projects with older Ruby versions

In case you want to use pry with an older version of Ruby, you can try the following configurations.

Ruby 1.8.7

Your pry version must not be greater than 0.9.10.

gem 'pry', '=0.9.10'
gem 'ruby-debug', '=0.10.4'
gem "ruby-debug-pry", :require => "ruby-debug/pry"
gem 'pry-nav'
gem 'ruby18_source_location'

Ruby 1.9.3

Your pry version must not be greater than 0.9.9.

gem 'debugger', '=1.1.4'
gem 'pry-debugger', '=0.2.0'
gem 'pry', '=0.9.9'

Known errors

No source for ruby-1...

Ruby / Rails: clone vs. dup vs. deep_dup

Ruby and Rails have several methods for creating a new object that looks like another: clone, dup, deep_dup. When using them you should be aware of their differences so that you can select the method you really need.

clone

  • Shallow copy: references to other objects/values are copied (instead of cloning those objects/values)
  • Clones the object and all its "special object attributes" like frozen, tainted and modules that the object has been extended with
  • [Ruby 2.6 documentation for clone](https://devdocs.io/ruby~2.6/obj...

Rubygems: Installing the last version of rubygems that has no rubyforge_project deprecation warning

You can install rubygems 3.0.8 (released on February 18, 2020) to keep all the Gem::Specification#rubyforge_project deprecation warnings away from your development log. With Rubygems >= 3.1 this deprecation warning was introduced. While maintaining older projects this could get quite annoying and the fix below might okey, for newer projects the right ways is to upgrade the gems.

gem update --system 3.0.8

Example message:

NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed o...

How to: Run bundle install in parallel

You can run bundle install in parallel. This might be helpful for development, where you often install many new gems when switching between projects.

  1. Find out the number of processors you have:
lscpu
  1. Set the config in your ~/.bundle/config globally (replace 8 with your number of proccessors):
bundle config jobs 8

Note

If you suspect parallel execution for bundling issues, you can try serially with bundle install --jobs 1.

Git: How to add changes matching a regular expression

When you have many changes, and you want to spread them across different commits, here is a way to stage all changes matching a given regular expression for a single commit.

Example

Consider the following git diff output.

diff --git a/file1.rb b/file1.rb
index 806ca88..36d536b 100644
--- a/file1.rb
+++ b/file1.rb
@@ -1,7 +1,5 @@
-# Here is a useless comment.
-# It will be removed.
 class File1
-  def foo
+  def bar
     # ...
   end
 end
diff --git a/file2.rb b/file2.rb
index 550e1c6..600f4e3 100644
--- a/file2.rb
+++ b/file2...

Five years of "Today I Learned" from Josh Branchaud

The linked GitHub repository is a bit like our "dev" cards deck, but groomed from a single person (Josh Branchaud). It includes an extensive list of over 900 TILs on many topics that might be interesting for most of us. (e.g. Ruby, Rails, Git, Unix..)

Ruby

Here is an excerpt of all the Ruby TILs that were new to me. I encourage you to take your time to skim over the original list as well!

Rails: Concurrent requests in development and tests

With puma you can have concurrent requests. There are two concepts on how Puma can handle two incoming requests: Workers and Threads.

Workers

Puma can have multiple workers. Each worker is a process fork from puma and therefore a very heavy instance and can have multiple threads, that handle the incoming requests.

Example: A Puma server with 2 workers and 1 thread each can handle 2 request in parallel. A third request has to wait until the thread of one of the workers is free.

Threads

Rails is thread-safe since version 4 (n...

How to use Simplecov to find untested code in a Rails project with RSpec and Cucumber

Simplecov is a code coverage tool. This helps you to find out which parts of your application are not tested.

Integrating this in a rails project with rspec, cucumber and parallel_tests is easy.

  1. Add it to your Gemfile and bundle

    group :test do
      gem 'simplecov', require: false
    end
    
  2. Add a .simplecov file in your project root:

    SimpleCov.start 'rails' do
      # any custom configs like groups and filters can be here at a central place
      enable_cov...
    

Using #deep_dup for copying whole hashes and array

"Everything in Ruby is an object". This is also true for nested hashes and arrays. If you copy a hash with #clone or #dup and you modify the copy, you will run into the following behavior:

original_hash = { foo: { bar: 'original value' } }
copied_hash = original_hash.dup
copied_hash[:foo][:bar] = 'changed value'

original_hash # => { foo: { bar: "changed value" }

This is, because { bar: 'baz' } is an object, which is referenced in :foo. The copy of original_hash still holds the reference to the same object, so alterin...

Bundler in deploy mode shares gems between patch-level Ruby versions

A recent patch level Ruby update caused troubles to some of us as applications started to complain about incompatible gem versions. I'll try to explain how the faulty state most likely is achieved and how to fix it.

Theory

When you deploy a new Ruby version with capistrano-opscomplete, it will take care of a few things:

  • The new Ruby version is installed
  • The Bundler version stated in the Gemfil...

Video transcoding: Web and native playback overview (April 2020)

Intro

Embedding videos on a website is very easy, add a <video> tag to your source code and it just works. Most of the time.

The thing is: Both the operating system and Browser of your client must support the container and codecs of your video. To ensure playback on every device, you have to transcode your videos to one or more versions of which they are supported by every device out there.

In this card, I'll explore the available audio and video standards we have right now. The goal is to built a pipeline that...

Heads up: Ruby's Net::HTTP silently retries a failing request

Ruby's Net::HTTP library repeats a failing request once, as long as it deems it idempotent (GET, HEAD etc). Both requests will use the configured timeout. Hence, if both requests time out, Net::HTTP will only return after twice the configured timeout.

This can become an issue if you rely on the timeout to strike precisely.

Ruby: How to fetch a remote host's TLS certificate

TLS/SSL certificates are often used for HTTPS traffic. Occasionally a service may also use their TLS certificate to support public-key encrypting data (e.g. when it is part of the URI and visible to the user, but contains sensitive information).

Here is how to easily fetch such certificate data.

certificate = Net::HTTP.start('example.com', 443, use_ssl: true) { |http| http.peer_cert }
# => #<OpenSSL::X509::Certificate: subject=#<OpenSSL::X509::Name CN=www.example.org,...>

certificate.public_key
# => #<OpenSSL::PKey::RSA:0x...

Guide to String Encoding in Ruby

The linked article has a great explanation how to to deal with string encodings in Ruby. Furthermore you can check out some of our cards about encoding:

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...

Capybara 'fill_in': Ambiguous match for different input names

When you have two inputs, where one contains the name of the other (eg. Name and Name with special treatment), Capybara's fill_in method will fail with the following message:

Ambiguous match, found 2 elements matching visible field "Name" that is not disabled (Capybara::Ambiguous)

You can force Capybara to match exactly what you are typing (which makes your tests better anyways) with match: :prefer_exact:

name = 'Name'
value = 'Bettertest Cucumberbatch'
fill_in(field, with: value, match: :prefer_exact)

Furthermore...

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...

How to set up SMTP email delivery with a Gmail account

If you want to make your Rails application be capable of sending SMTP emails, check out the action mailer configuration section in the Ruby on Rails guide.

TL;DR you will end up having an smtp_settings hash that looks something like this:

smtp_settings = {
  address: ...,
  domain: ...,
  port: ...,
  user_name: ...,
  password: ...,
  authentication: ...,
  tls: ...,
  enable_starttls_auto: ...,
}

This hash can be set as the `delivery_me...

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...

How to update the bundler version in a Gemfile.lock

  1. Install the latest bundler version:

    gem install bundler
    Fetching bundler-2.3.5.gem
    Successfully installed bundler-2.3.5
    1 gem installed
    
  2. Update the bundler version in Gemfile.lock:

    bundle update --bundler  
    
  3. Confirm it worked:

    $ tail -n2 Gemfile.lock 
    BUNDLED WITH
      2.3.5
    

Notes:

  • Bundler should automatically detect the latest installed version. If it does not, you can specify your preferred version like so:

    b...