Today I Learned

A Zero One initiative

24 posts about #ruby

How to speed up Rubocop's startup time

If you have a fairly complex .rubocop.yml file, you may find that later versions take forever to start up or report results, and it seems like it hanging when you run it manually from the CLI.

The cause for this may be due to you having custom exclusion rules. When you exclude files from Rubocop ala:

AllCops:
  Exclude:
    - 'db/fixtures/**/*'
    - 'db/migrate/*.rb'
    - 'db/schema.rb'
    - 'db/seeds.rb'
    - 'Gemfile.lock'
    - 'bin/*'
    # ... etc... etc... 

Rubocop will then ignore any of its own exclusion rules, and land up scanning node_modules, cache dirs, log dirs, and even your .git folder!

You can fix this by adding the following into your .rubocop.yml file.

inherit_mode:
  merge:
    - Exclude

Rubocop will now merge in its default exclusion list alongside your own.

Why this is not the default behaviour eludes me. :|

Debugging PDFKit and WKHTMLToPDF

When PDFKit errors out with an unhelpful error like: RuntimeError (command failed (exitstatus=1): /bin/wkhtmltopdf --page-size A4 - -):, try the following:

  • Add config.verbose = true to its configuration
  • Add load_error_handling: 'ignore' to its config.default_options
  • Save the rendered HTML to a file by modifying pdfkit.rb’s to_pdf method with this just before the invoke declaration.
open('/tmp/pdfkit-source.html', 'a') { |f| f.puts @source.to_s }

Once you’ve got the html saved, you can inspect it for errors, and manually run wkhtmltopdf on the CLI using it as input like so:

cat /tmp/pdfkit-source.html | <the wkhtmltopdfbin command that is failing> out.pdf

Hopefully one of these techniques can highlight where your problems lie, and get you to a resolution.

Add comments to package.json

Progressively upgrading dependencies in the JS world is a little different compared to Ruby.

When updating a Ruby gem inside a gemfile, you might want to leave notes. For example:

gem 'some-gem', '~> 1.2.3' # TODO: Update once we're on Ruby 3.1

The comment is useful since it shares knowledge related to upgrading this dependency.

Javascript, or JSON, works a little differently. No comments are allowed inside the package.json file. Instead to achieve roughly the same result you can do this:

...
"//": [
    "TODO: Upgrade stylelint-config-sass-guidelines once we're on Node 11+",
    "TODO: Upgrade mini-css-extract-plugin once we're on 12.13+ and webpack 5+",
    "TODO: Upgrade style-loader once we're on 12.13+ and webpack 5+"
  ],
"dependencies": {
  ....
}

"//" will never be used by npm for any purpose, and is reserved for comments.

Finding missing/associated records in Rails

Rails has some built in methods to help you find records which may have missing associated records.

For example, let’s assume we have an app where an Invoice has many Payments, and we wanted to find all invoices which have no payments:

Invoice.where.missing(:payments)

We’ll get back a collection of all invoices which have no payments.

Much cleaner than writing messy joins!

Also, on the flip side you can find all invoices with payments like this:

Invoice.where.associated(:payments)

Extending Rake Tasks

You can extend rake tasks to add extra functionality.

For example, let’s extend the rails db:migrate task to automatically include schema migrations and data migrations.

# Inside lib/tasks/db.rake
# Remove the old migrate task
Rake::Task['db:migrate'].clear

namespace 'db' do
  # Redefine the migrate task to invoke with data migrations
  task migrate: ['migrate:with_data']
end

Now whenever you execute rails db:migrate, the task will include schema + data migrations.

Additionally, you may want to invoke some extra task after another has been executed. To do this, you can enhance a task like this:

# Inside lib/tasks/db.rake
Rake::Task['db:migrate'].enhance do
  Rake::Task['db:do_some_custom_thing'].invoke
end

Now your custom task will be invoked after rails db:migrate is invoked.

Simpler conditional HTML classes with class_names

Coming from JS and React you may be familiar with a package called classnames. It takes an object where the keys represent CSS classes, and the values are booleans which determine if the key should be loaded or not.

Eg. classnames(‘foo’, { bar: true, baz: false })

Returns: ‘foo bar’

Rails includes its own helper which does this same thing. You can call class_names or token_list:

Eg. class_names(‘foo’, { bar: true, baz: false })

Returns: ‘foo bar’

Stimulus Use

I was getting rather annoyed when using modals with stimulus, especially when it came to actually closing them. I didnt like the way I wrote the general function to close the modals so I decided to refactor it. In doing so I came across a package that would solve it all. The package is called stimulus-use (https://github.com/stimulus-use/stimulus-use). It enables new lifecycle behaviors to your stimulus controllers. Now I can close my modals like so, instead of trying to check if my modal has a click event outside of it:

import { Controller } from 'stimulus'
import { useClickOutside } from 'stimulus-use'

export default class extends Controller {

  connect() {
    useClickOutside(this)
  }

  clickOutside(event) {
    event.preventDefault()
    this.modal.close()
  }
}

Default Controller Params

On occasion you may need to assign default values to the params required within a controller. For example, assume we’re working with todos and we want to assign a todo to the current user (by default, unless another user is specified), then I’ve usually seen some people handle default param values like this:

def todo_params
  params
    .require(:todo)
    .permit(:subject, :description, :user_id)
    .tap { |pms|  pms.user_id ||= current_user.id }
end

However, TIL that you can actually make use of a method called with_defaults:

def todo_params
  params
    .require(:todo)
    .permit(:subject, :description, :user_id)
    .with_defaults(user_id: current_user.id)
end

Much cleaner!

Also, with_defaults is defined for ActionController::Parameters and Hash. I find with_defaults much cleaner, and more intention revealing, than reverse_merge!

Fun with Recursive Anonymous Functions

Did you know that an anonymous function can call itself?

All you need is something like this:

factorial_of = -> (n) { n <= 1 ? 1 : n * factorial_of.call(n - 1) }
factorial_of.call(5)
# => 120

This came in handy when I had a recursive method which needed to be wrapped inside an enumerator to navigate a very complex hierarchy of objects.

Instead of something as complex as that, let’s wrap our anonymous factorial function around an enumerator:

factorial_of = -> (n) { n <= 1 ? 1 : n * factorial_of.call(n - 1) }

f = Enumerator.new do |y|
  num = 0
  loop do
    y << factorial_of.call(num)
    num += 1
  end
end

f.next
=> 1
f.next
=> 1
f.next
=> 2
f.next
=> 6

Now we can fetch the next number whenever we want, or enumerate over the numbers until we don’t have any left (which is infinite in this case)

Slide an Organiser into another Organiser's DM's

When calling an Organiser within another Organiser with light-service, a really neat way to do it is to splat the actions from the desired organiser into the other like this:

module Users
  class UpdateProfile
    extend ::LightService::Organizer

    def self.call()
      with(
        # some params go here
      ).reduce(actions)
    end

    def self.actions
      [
        # some actions go here
        *Users::UpdatePreferencesAndNotify.actions,
        # some more actions go here
      ]
    end
  end
end

I needed to call an organiser responsible for updating a completeness score on the user and also running the organiser responsible for updating the users attributes and awarding a badge based on the score. This allowed me to still segment the logic while not cluttering the organiser with more actions.

See https://github.com/adomokos/light-service/issues/160 for reference. Also it’s not necessary to use the extend LightService::Action in the organisers

How to correctly specify files when building gems

I recently found one of my docker images had 19MB of test files in the Ruby gems directory. When building a docker image, you want it as small as possible.

I wondered why some gems in there had tests included, and others did not? Here’s what I discovered…

When building a gem, it looks in your gemname.gemspec file for a list of files to include.

The default is a command that includes every file. It’s better to be specific about what files you include, instead of the default shotgun approach

In your gemspec file:

BAD

s.files = `git ls-files -z`.split("\0")

GOOD

s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md CHANGELOG.md)

Find your notes within your Rails app

Leaving TODOS and other notes sometimes feels like throwing trash onto a pile which you may never come back to.

You can find all notes you’ve left inside a Rails using rails notes.

This will search app, config, db, lib, and test directories for FIXME, OPTIMIZE, and TODO annotations.

Running rails notes will return something like:

app/actions/auth/creates_new_user_from_open_id_payload.rb:
  * [22] [TODO] Find a way to create users linked to accounts?
  * [27] [TODO] Not using these right now, and they should move to the tokens model

You can see that we receive a list of notes including the file, line number and the note.

You can also filter for specific notes with --annotations. For example let’s filter FIXMEs:

rails notes --annotations FIXME

You can also add custom notes in the Rails config like this:

config.annotations.register_tags("DEPRECATEME", "TESTME")

Adding your own custom notes could be handy when you want to add a note for something like gem/app upgrades in code which might not be tested particularly well, or just difficult to test in general.

Combine Pry and RSpec

RSpec provides a way for you to register aliases for the methods you use to create your specs. You can alias these methods and tie them into predefined hooks using metadata.

For example, let’s say we wanted to have run pry whenever a spec includes some metadata like: pry: true. Instead of adding pry: true after each spec, we can add an alias by doing the following:

RSpec.configure do |config|
  config.alias_example_group_to :pdescribe, pry: true
  config.alias_example_to       :pit,       pry: true

  config.after(:example, pry: true) do |e|
    require 'pry'
    binding.pry
  end
end

We’ve registered 2 aliases. Whenever we prepend p onto a method’s name, we’ll now get the same functionality as adding pry: true to our spec’s metadata.

We’ve tied this alias into an after hook. This hook only runs when pry is set to true in the spec’s metadata. RSpec after hooks always run, even if the spec fails!

Simply prepend p to a spec and pry will run once the spec is finished!

We’ve now created a powerful pry alias to debug our specs in RSpec :)

Ensure clauses

Today I learned about the ensure clause when raising exceptions. I was having trouble cleaning up certain operations, now within the ensure block the code will always get executed whether an exception is raised or not.

def foo
  begin
     raise Exception, "Oh no"
   ensure
     return "Clean me up Scotty"
  end
end

DRY up kwargs using `with_options`

Rails has a really awesome helper to DRY up code which makes use of much of the same kwargs. It’s called with_options and is used like this:

class Account < ActiveRecord::Base
  has_many :customers, dependent: :destroy
  has_many :products,  dependent: :destroy
end

# Becomes this with `with_option`

class Account < ActiveRecord::Base
  with_options dependent: :destroy do
    has_many :customers
    has_many :products
  end
end

Your code is now a lil more DRY and its easier to add, or remove, pesky kwargs as needed.

Another quick example:

class AuthenticatedController < ApplicationController
  before_action :logged_in?,         only: [:show, :edit]
  before_action :create_paper_trail, only: [:show, :edit]

  # ...
end

# Becomes this with `with_option`

class AuthenticatedController < ApplicationController
  with_options only: [:show, :edit] do
    before_action :logged_in?
    before_action :create_paper_trail
  end

  # ...
end

Strict loading with Rails

Rails implements a feature called strict loading. This feature can help catch potential N+1 queries.

Did you know there are a couple of ways to enable strict loading?

1. Per Record

Rails let’s you enable strict loading on specific records at runtime:

post = Post.first

post.strict_loading! # Enable strict loading
post.strict_loading? # Check if strict loading is enabled on this object
# => true

2. Per model

Rails let’s you enable strict loading on all models of a specific type:

class Post < ApplicationRecord
  self.strict_loading_by_default = true

  has_many :comments, dependent: :destroy
end

post = Post.first
post.strict_loading?
# => true

3. Enable per association basis

Rails let’s you define strict loading for specific (or pesky) relations on a model:

class Post < ApplicationRecord
  has_many :comments, dependent: :destroy, strict_loading: true
  has_many :likes,    dependent: :destroy, strict_loading: false
end

4. Application wide

Rails also let’s you enable strict loading all across the board when you’re brave enough for the nuclear option:

# config/environments/development.rb

Rails.application.configure do
# ...
  config.active_record.strict_loading_by_default = true
# ...
end

Now that you know your options, you can enable it based on your appetite/distaste for N+1 queries!

Ruby prepend wrappers

Imagine we have some sort of object (which we don’t own) and we’d like to audit it. You could write an explicit wrapper around the object to audit it, but then you’ve got to keep in mind that you have to use this wrapper whenever you use the service.

You can add new functionality, whilst preserving the old interface and function like this:

class Foo # class we do not own
  def bar(arg)
    sleep arg
    "Running bar with #{arg}!"
  end
end

module FooWrapper
  def bar(arg)
    start_time = Time.now
    result = super
    time_taken = Time.now - start_time

    puts "bar() took more than #{time_taken} seconds" if 2 < time_taken
    result
  rescue
    "bar() failed"
  end
end

Foo.class_eval do
  prepend FooWrapper
end

Foo.new.bar 2.5
# bar() took more than 2.501257 seconds
# => "Running bar with 2.5!"

We’ve created a wrapper, opened up the class and prepended the wrapper. The wrapper adds new functionality, whilst preserving the old.

The downside to this approach is that whenever you call Foo#bar, you will get your added functionality each time. This may, or may not, be an issue depending your circumstance.

Make fancy enums with `enum_for`

Did you know you can make your own enumerators in Ruby?

e = Enumerator.new do |y|
 [1,2,3].each { |i| y << i }
end

e.next    # => 1
e.next    # => 2
e.rewind
e.next => # => 1

Methods like each help convert things to an enumerable (using to_enum), but there is another way to create enums in Ruby. enum_for takes one argument which points to a method to bind to for enumeration. It allows make something enumerable when it technically isn’t, or when we can’t/don’t want to.

class DoReMi
  def sing
    lyrics.each { |l| yield l }
  end

  private

  def lyrics
    [
      "Doe, a deer, a female deer",
      ...
    ]
  end
end

Use enum_for to enumerate at your own pace. Let’s learn to shout the song backwards!

d = DoReMi.new
d.sing { |l| puts l }
# => Doe, a deer, a female deer
# => ...
e = d.enum_for :sing
e.map { |l| l.upcase }.reverse.each { |l| puts l }
# => ME, A NAME I CALL MYSELF
# => ...

# Wait, what's the last lyric again?
e.rewind
e.next.upcase # => "DOE, A DEER, A FEMALE DEER"

`[]` is smarter thank you think!

You’re probably very familiar with using [] on objects which are enumerable, but [] has a few more tricks up it’s sleeve when it comes to strings which you may not know about:

string = "Ruby is a cool language"

Did you know that you can use [] to fetch a substring from a string, and return nil if it doesn’t exist?

string["cool lang"] # => "cool lang"
string["foo"] # => nil

You can even throw a Regex in that bad boy and you’re off to the Regex races!

And for the final trick we’ll make a pesky substring DISAPPEAR!

string["cool"] = "great" # => "great"
string # => "Ruby is a great language"

Next time you’re munging with strings, remember that [] is smarter than you think!

Reference keyword args inside method signatures

Ruby’s keyword arguments are pretty flexible to begin with, but I recently discovered you can reference previously set keyword arguments inside your method definition.

Here’s an example:

def query(type:, response_field: "#{type}_response")
  response = run_query_against(type)
  extract_payload(response, response_field)
end

query(type: "accounts") # Will set response_field to "accounts_response"
query(type: "foo", response_field: "bar") # Will use foo as type, and bar as response_field

From my experiments, it seems that each keyword argument has the scope of the previously defined keyword arguments available to it. This means that you cannot reference a keyword defined later in the signature, e.g.

def query(response_field: "#{type}_response", type: "accounts")
 # This raises a NameError: undefined local variable or method `type'
end

The above will raise an exception, even if there is a default value supplied in the signature.

Find the result of an evaluation in an Enumberable

Ruby allows mapping through an array to return a transformed array based on an evaluation in the block. It also allows finding a value in an array.

Ruby unfortunately does not have a single method that allows you to do both, i.e. loop through a list and return the first transformed value that meets a truthy condition.

To achieve this, we can leverage Ruby’s lazy enumerability.

my_val = 5
arr    = [->(val) { val * 3 }, ->(val) { val * 2 }, ->(val) { val * 7 }]

arr.lazy
   .map  { |my_proc|  my_proc.call(my_val) }
   .find { |proc_val| proc_val % 10 == 0 }
 # => 10 (the returned value of the second element
 # in the list. Element 3 is not processed.

Many thanks to Dylan Bridgman (@dylanbr) from the #ruby channel on the ZATech Slack for sharing this neat method.

Browse papertrail changes without object_changes

If you are using PaperTrail to track changes to your data, but are not storing individual changesets, you can still see what changed from version to version using the snippet below:

def hash_diff(from, to)
  from
  .dup
  .delete_if { |k, v| to[k] == v }
  .merge!(to.dup.delete_if { |k, v| from.key?(k) })
end

def present_diff(from_diff, to_diff)
  [].tap do |arr|
    arr << "FROM: #{from_diff}"   if from_diff.present?
    arr << "TO:   #{to_diff}\n\n" if to_diff.present?
  end
end

def list_papertrail_changes_for(obj)
  obj.versions.each_with_index.flat_map do |ver, idx|
    next if ver.object.nil?

    this_ver     = YAML.load(ver.object)
    next_version = obj.versions[idx + 1] || ver
    next_ver     = YAML.load(next_version.object)

    from_diff = hash_diff(this_ver, next_ver)
    to_diff   = hash_diff(next_ver, this_ver)

    present_diff(from_diff, to_diff)
  end.compact
end

puts list_papertrail_changes_for(thing)

FROM: {"ip_address"=>"", "updated_at"=>"2021-04-06T07:32:26.432Z"}
TO:   {"ip_address"=>"10.27.57.194", "updated_at"=>"2021-04-06T08:14:03.506Z"}

FROM: {"ip_address"=>"10.27.57.194", "updated_at"=>"2021-04-06T08:14:03.506Z"}
TO:   {"ip_address"=>"", "updated_at"=>"2021-04-09T13:47:07.485Z"}

PG Gem fails on Mac OS 11.x (Big Sur)

If after installing pg gem in your ruby project and you get the following error with the pg gem

Library not loaded: libpq.5.dylib

You can fix the linking issues by running the following commands:

cd ~/[your ruby path]/versions/2.7.2/lib/ruby/gems/2.7.0/gems/pg-1.2.3/lib

install_name_tool -change libpq.5.dylib /Library/PostgreSQL/12/lib/libpq.5.12.dylib pg_ext.bundle

You may need to modify the commands above to suite your ruby manager and currently installed versions.