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

Rbenv: Alias a Ruby version

For newer Ubuntu versions we currently need to install the patch level version 1.8.7-p375, otherwise the dev dependencies from openssl will cause the installation to fail.

For a project that specifies the Ruby version 1.8.7 in the .ruby-version the rbenv autoswitch will not work. You have several options how you can solve this problem:

  • Install rbenv-aliases, which will alias your Ruby 1.8.7-p375 ...

Bundler: How to release a gem with 2FA enabled

Rubygems supports a 2FA for your account. Once enabled you need to provide your personal OTP code for every release. Despite the CLI of the rake release task does not work well with the command prompt for your OTP code with Bundler versions < 2.0.2. It just looks like the task is frozen:

Image

  • Workaround 1: Just type your OTP code and hit enter, your gem is released afterwards.
  • Workaround 2: Upgrade to Bundler >= 2.0.2.. Your supported Ruby versions for this gem must be >= 2.3.

When r...

ActiveSupport includes Timecop-like helpers

ActiveSupport (since 4.1) includes test helpers to manipulate time, just like the Timecop gem:

  • To freeze the current time, use freeze_time (ActiveSupport 5.2+):

    freeze_time
    
  • To travel to a specific moment in time, use travel_to:

    travel_to 1.hour.from_now
    

    Important

    When freezing time with #travel_to, time will be frozen (like with freeze_time). This means that your application can't detect passage of time by using Time.now.

  • To travel a re...

Ruby: The YAML safe_load method hides some pitfalls

The Ruby standard lib ships with a YAML Parser called Psych. But serializing and deserializing data seems not as obvious as if you are using JSON.

To safely write and read YAML files you should use Psych#dump (String#to_yaml) and Psych.safe_load (YAML.safe_load):

data = {'key' => 'value'}.to_yaml
=> "---\nkey: value\n"
YAML.safe_load(data)
=> {"key"=>"value"}

Unfortunately you might encounter a few pitfalls which are not obvious in the first place. All of them are a side effect that you can not configure Psych#dump to o...

Capybara: Execute asynchronous JavaScript

Capybara provides execute_script and evaluate_script to execute JavaScript code in a Selenium-controlled browser. This however is not a good solution for asynchronous JavaScript.

Enter evaluate_async_script, which allows you to execute some asynchronous code and wait until it finishes. There is a timeout of a couple of seconds, so it will not wait forever.

Use it like this:

page.evaluate_async_script(<<~JS)
  let [done] = arguments
  doSomethingAsynchronous().then(() => {
    done() // call this to indicate we're done
  })
J...

Email validation regex

There is a practical short list for valid/invalid example email addresses - Thanks to Florian L.! The definition for valid emails (RFC 5322) can be unhandy for some reasons, though.

Since Ruby 2.3, Ruby's URI lib provides a built-in email regex URI::MailTo::EMAIL_REGEXP. That's the best solution to work with.

/\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[...

Integrating or upgrading makandra-rubocop

Introduction

Most of the time it is a tedious task to apply a code style guide to an existing code base as there are likely to be a lot of conflicts. At makandra we are using makandra-rubocop to have code style checks. Here is some advice on how to add makandra-rubocop efficiently.

Note

RubyMine by default has a Rubocop inspection with rules that we don't always agree with. We recommend replacing this with makandra-rubocop or disabling the inspection.
...

Fix error: rails console No such file to load -- irb/encoding_aliases.rb (LoadError)

I got this error after upgrading Ruby from 2.4.5 to 2.6.4 when I opened the Rails console - rails server still worked.

Running via Spring preloader in process 14679
Loading development environment (Rails 5.2.2.1)
Traceback (most recent call last):
.../lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:175:in 'fork': No such file to load -- irb/encoding_aliases.rb (LoadError)
.../lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:175:in 'fork': undefined method 'reject!' for nil:NilClass (NoMethodError)
.../li...

How to enable Chromedriver logging

When using Chrome for Selenium tests, the chromedriver binary will be used to control Chrome. To debug problems that stem from Selenium's Chrome and/or Chromedriver, you might want to enable logging for the chromedriver itself. Here is how.

Option 1: Use Selenium::WebDriver::Service

In your test setup, you may already have something like Capybara::Selenium::Driver.new(@app, browser: :chrome, options: ...), especially when passing options like device emulation.

Similar to options, simply add an extra key service and pass an inst...

palkan/isolator: Detect non-atomic interactions within DB transactions

With this gem your transaction blocks raise an error when they have side effects that cannot be rolled back.

By default it checks whether you're connecting with HTTP, queuing a Sidekiq job or sending an e-mail within a transaction. You can add custom checks, too.

Found in this RubyGuides article.

Convert SCSS to SASS

The ruby sass gem also installs a command line tool to convert to and from SCSS. Use it for a directory of .scss-files like

sass-convert -R assets/stylesheets --from scss --to sass

Rails: Do not load frameworks you don't need

Rails is split into a large number of (sub-) frameworks.

The most important and central of those are

  • activesupport (extends the Ruby standard library)
  • activerecord / activemodel (ORM for Rails)
  • actionview / actionpack (controller / views)
  • actionmailer (sends mails)

However, there are also some more situational frameworks included, such as

  • actioncable (real time communications using websockets)
  • actionmailbox (receives mails)
  • actiontext (support for WYSIWYG text editor)
  • activejob (background jobs)
  • activestorage (file uplo...

Take care of existing users when upgrading Clearance

When upgrading Clearance, pay attention whether the password hashing strategy might have changed. Old clearance versions (< 1.0) used SHA1-encrypted passwords by default. Current versions default to BCrypt.

If you simply upgrade without taking this into account, users will get a BCrypt::Errors::InvalidHash when trying to sign in. Your tests will not notice this, since they create new users for each scenario.

To fix it, you'll either have to force all users to reset their passwords, or you can allow old users to keep signing in with t...

Ruby: How to use global variables for a conditional debugger

You can share a state in Ruby with global variables. Even if you should avoid them whenever possible, for debugging an application this could be temporary quite handy.

Example:

class User

  after_save { byebug if $debug; nil }

  def lock
   self.locked = true
   save
  end

end
Rspec.describe User do

  let(:user) { create(:user) } 

  before do
   # Many users are created and saved in this hook, but we don't want the debugger to stop for them...

Unpoly: Showing the better_errors page when Rails raises an error

When an AJAX request raises an exception on the server, Rails will show a minimal error page with only basic information. Because all Unpoly updates work using AJAX requests, you won't get the more detailled better_errors page with the interactive REPL.

Below is an event listener that automatically repeats the request as a full-page load if your development error shows an error page. This means you get...

Webpack(er): A primer

webpack is a very powerful asset bundler written in node.js to bundle (ES6) JavaScript modules, stylesheets, images, and other assets for consumption in browsers.

Webpacker is a wrapper around webpack that handles integration with Rails.

This is a short introduction.

Installation

If you haven't already, you need to install node.js and Yarn.

Then, put

gem 'webpacker', '~> 4.x' # check if 4.x is still cu...

Installing Ruby 2.3 or below on Ubuntu 17 and above

From Ubuntu 17, rbenv fails to install Ruby below 2.4 because of a mismatching OpenSSL dependency: it needs libssl1.0-dev for the installation process, but recent Ubuntus come with libssl-dev.

From the linked StackOverflow comment:

As far as I know (and tested), Ruby versions < 2.4 requires libssl1.0, while >2.4 libssl1.1+. The two libssl packages conflict with each other, so you can't have both of them, so I had to juggle the libs in order to install the required ruby version. To make things even funnier (or more complicated),...