← See all notes

Table of Contents

Ruby

Useful blog posts 🔗

Books 🔗

Tricks 🔗

class Review < ActiveRecord::Base
  belongs_to :restaurant

  scope :positive, -> { where("rating > 3.0") }
end

class Restaurant < ActiveRecord::Base
  has_many :reviews
  has_many :positive_reviews, -> { positive }, class_name: "Review"
end

# Avoid returning record from a model's method
class User < ApplicationRecord
  has_many :subscriptions

  # Bad because a SQL query is triggered on each call
  def active subscription
    subscriptions.active.first
  end
end

user = User.first
user active subscription # SELECT * FROM subscriptions ...
user active subscription # SELECT * FROM subscriptions ...

# Memoizing the result is not necessarily better since it won't be uncached when reload is called.
# Instead, switch that method to a relation:
class User < ApplicationRecord
  has_many :subscriptions

  # Good because the relation is cached until reload is called
  has_one :active_subscription, -> { active }, class_name: "Subscription"
end

user = User.first
user.active subscription # SELECT * FROM subscriptions ...
user.active_subscription # Cached, no queries
user.reload
user.active_subscription # SELECT * FROM subscriptions ...

How to be a top 10% Ruby perf team 🔗

  1. SLO-based queues (within_6_hours, within_0_seconds, etc.)
  2. RMP in all environments
  3. RUM in browser
  4. Autoscale web and worker
  5. Instrument request queue time
  6. Automated alerts/SLOs in Terraform
  7. Prosopite/strictloading in tests
  8. Latest Ruby w jemalloc
  9. Turbo

https://x.com/nateberkopec/status/1844849691760214041

Ensure all controller public methods have a corresponding route 🔗

https://gist.github.com/fractaledmind/410e519ccd51445cc10c3408b5f24d77

class RoutingTest < ActionDispatch::IntegrationTest
  IGNORED_CONTROLLERS = Set[
    "Rails::MailersController"
  ]

  test "no unrouted actions (public controller methods)" do
    actions_by_controller.each do |controller_path, actions|
      controller_name = "#{controller_path.camelize}Controller"
      next if IGNORED_CONTROLLERS.include?(controller_name)

      controller = Object.const_get(controller_name)
      public_methods = controller.public_instance_methods(_include_super = false).map(&:to_s)
      unrouted_actions = public_methods - actions

      assert_empty(
        unrouted_actions,
        "#{controller_name} has unrouted actions (public methods). These should probably be private"
      )
    end
  end

  private

  def actions_by_controller
    {}.tap do |controllers|
      @routes.routes.each do |route|
        controller = route.requirements[:controller]
        action = route.requirements[:action]
        next unless controller && action

        (controllers[controller] ||= []) << action
      end
    end
  end
end

Install a gem and load it directly in the console without changing the Gemfile 🔗

$ gem install "ruby-progressbar"
$ gem which "ruby-progressbar"
...
$ bin/rails c
> $: << path.strip.toutf8.gsub("ruby-progressbar.rb", "")

Ruby exception inheritance 🔗

module Phlex
  Error = Module.new

  NameError = Class.new(NameError) { include Error }
  ArgumentError = Class.new(ArgumentError) { include Error }
  StandardError = Class.new(StandardError) { include Error }
end

Form object concerns 🔗

https://speakerdeck.com/palkan/kaigi-on-rails-2024-rails-way-or-the-highway

https://speakerdeck.com/palkan/kaigi-on-rails-2024-rails-way-or-the-highway
class ApplicationForm
  include ActiveModel::API
  include ActiveModel::Attributes

  define_callbacks :save, only: :after
  define_callbacks :commit, only: :after

  class << self
    def after_save(...)
      set_callback(:save, :after, ...)
    end

    def after_commit(...)
      set_callback(:commit, :after, ...)
    end

    def from(params)
      new(params.permit(attribute_names.map(&:to_sym)))
    end
  end

   def save
    return false unless valid?

    with_transaction do
      AfterCommitEverywhere.after_commit { run_callbacks(:commit) }
      run_callbacks(:save) { submit! }
    end
  end

  def model_name
    ActiveModel::Name.new(nil, nil, self.class.name.sub(/Form$/, ""))
  end

  private

  def with_transaction(&) = ApplicationRecord.transaction(&)

  def submit!
    raise NotImplementedError
  end
end

class ApplicationWorkflow
  include Workflow
end

class Cable
  class CreateForm < ApplicationForm
    class Wizard < ApplicationWorkflow
      workflow do
        state :name do
          event :submit, transitions_to: :framework
        end

        state :framework do
          event :submit, transitions_to: :rpc, if: :needs_rpc?
          event :submit, transitions_to: :secrets
          event :back, transitions_to: :name
        end

        state :rpc do
          event :submit, transitions_to: :secrets
          event :back, transitions_to: :framework
        end

        state :secrets do
          event :submit, transitions_to: :region
          event :back, transitions_to: :rpc, if: :needs_rpc?
          event :back, transitions_to: :framework
        end

        state :complete
      end
    end

    self.model_name = "Cable"

    attribute :name
    attribute :region, default: -> { "sea" }
    attribute :framework, default: -> { "rails" }
    attributes :secret, :rpc_host, :rpc_secret, :turbo_secret, :jwt_secret

    attribute :wizard_state, default: -> { "name" }
    attribute :wizard_action

    validates :cable_is_valid
    validates :rpc_host, format: %r{\\Ahttps?://}, allow_blank: true

    after_commit :enqueue_provisioning

    attr_reader :cable

    def initialize(...)
      super
      @cable = Cable.new(
        name:, region:,
        metadata: {framework:},
        configuration: {
          secret:, rpc_host:, rpc_secret:,
          turbo_secret:, jwt_secret:
        }
      )
    end

    def submit!
      if wizard_action == "back"
        wizard.back!
      else
        wizard.submit!
      end

      return false unless wizard.complete?

      cable.save!
    end

    def wizard = @wizard ||= Wizard.new(self)

    private

    def cable_is_valid
      return if cable.valid?
      merge_errors!(cable)
    end

    def enqueue_provisioning = cable.provision_later
  end
end

Scopeable Query Objects 🔗

https://speakerdeck.com/palkan/kaigi-on-rails-2024-rails-way-or-the-highway

class ApplicationQuery
  private attr_reader :relation

  def initialize(relation = self.class.query_model.all) = @relation = relation

  def resolve(...) = relation

  class << self
    # To pin `query_model_name` you may:
    # 1. Set `self.query_model_name = "MyModel"` in the query object
    # 2. Pass the model name whenever you use the query object MyQueryObject[MyModel]
    # 3. Let the fallback (`ApplicationQuery.query_model_name`) do the work
    #
    # I personally don't like 3 and prefer to always declare it in the query object or pass it explicitly.
    attr_writer :query_model_name

    def query_model_name
      @query_model_name ||= name.sub(/::[^\:]+$/, "")
    end

    def query_model
      query_model_name.constantize
    end

    # Useful for when the query object can be reused by two base model relation
    def [](model)
      klass_name = "Generated__#{model.name}#{self.name}"
      
      return klass_name.constantize if Object.const_defined?(klass_name)
      
      klass = Class.new(self).tap {
        _1.query_model_name = model.name
      }
      
      Object.const_set(klass_name, klass)
    end

    def resolve(...) = new.resolve(...)
    alias_method :call, :resolve
  end
end

class LeadListEntry::WithBlacklistedEmailsQuery < ApplicationQuery
  self.query_model_name = "LeadListEntry"

  def resolve(user)
    excluded = ExcludedEmail.where("excluded_emails.email = lead_list_entries.email")
      .where(account_id: user.account_id)
      .select("1")
      .arel.exists

    relation.where(excluded)
  end
end


class LeadListEntry < ApplicationRecord
  scope :with_blacklisted_emails, WithBlacklistedEmailsQuery[self]
  # Or: 
  # scope :with_blacklisted_emails, WithBlacklistedEmailsQuery

  # Instead of:
  # scope :with_blacklisted_emails, ->(user) { ... }
end

Service Object 🔗

class ApplicationCallable
  def initialize(...) = raise NoMethodError
  def call = raise NoMethodError
  def to_proc = method(:call).to_proc

  class << self
    def call(...) = new(...).()
  end
end

class IntegerIncrementer < ApplicationCallable
  def initialize(i)
    @i = i
  end

  def call
    @i + 1
  end
end

# Or:
module Callable
  def call = raise NoMethodError
  def to_proc = method(:call).to_proc
end

module CallableOperation
  include Callable

  module ClassMethods
    include Callable

    def call(...) = new(...).()
  end

  def self.included(klass)
    klass.extend(ClassMethods)
  end
end

class IntegerIncrementer
  include CallableOperation

  def initialize(i)
    @i = i
  end

  def call
    @i + 1
  end
end

def call_inside_block(&block)
  block.(1)
end

IntegerIncrementer.(1)
IntegerIncrementer.new(1).()
call_inside_block(&IntegerIncrementer)

List gems with C extensions 🔗

require "rubygems"

gems = Gem::Specification.each.select { |spec| spec.extensions.any?  }

puts gems.map(&:full_name).map {|x| x.split("-", 2).first }.uniq

Ruby jemalloc 🔗

Adding jemalloc via LD_PRELOAD 🔗

FROM ruby:3.2-slim

RUN apt-get update; \\
    apt-get install -y --no-install-recommends libjemalloc2; \\
    rm -rf /var/lib/apt/lists/*

# TODO: figure out how to LD_PRELOAD on arbitrary architectures dynamically
# /usr/lib/x86_64-linux-gnu/libjemalloc.so.2
# /usr/lib/aarch64-linux-gnu/libjemalloc.so.2
# /usr/lib/arm-linux-gnueabihf/libjemalloc.so.2
# /usr/lib/i386-linux-gnu/libjemalloc.so.2
# /usr/lib/powerpc64le-linux-gnu/libjemalloc.so.2
# /usr/lib/s390x-linux-gnu/libjemalloc.so.2
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2

Adding jemalloc at ruby compile time 🔗

$ apt-get install -y --no-install-recommends libjemalloc-dev libjemalloc2
$ RUBY_CONFIGURE_OPTS="--with-jemalloc" rbenv install 3.2

Patching Ruby binary to use jemalloc 🔗

FROM ruby:3.2-slim
RUN apt-get update  && \\\\
  apt-get upgrade -y && \\\\
  apt-get install libjemalloc2 patchelf && \\\\
  patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby && \\\\
  apt-get purge patchelf -y

Detecting if jemalloc is being used 🔗

$ MALLOC_CONF=stats_print:true /opt/rbenv/shims/ruby -e "exit" |& grep "jemalloc statistics"

Detecting thread unsafe code 🔗

require "concurrent-ruby"

def run_forever(pool_size: 10)
  pool = Concurrent::FixedThreadPool.new(pool_size)
  i = Concurrent::AtomicFixnum.new

  loop do
    pool.post do
      yield i.increment
    end
  end
end

class Repeater
  class << self
    attr_accessor :result

    def repeat_by(content, n)
      self.result = [content] * n
    end
  end
end

run_forever do |iteration|
  n = rand(100)
  Repeater.repeat_by("hello", n)
  array_size = Repeater.result.size
  if array_size != n
    raise "[#{array_size}] should be [#{n}]?"
  end
rescue StandardError => e
  puts "Iteration[#{iteration}] #{e.message}"
end

Test seeds 🔗

# frozen_string_literal: true
require "test_helper"

class SeedTest < ActiveSupport::TestCase
  def total_count
    ActiveRecord::Base.connection.execute(
      "SELECT SUM((SELECT COUNT(*) FROM #{ActiveRecord::Base.connection.quote_table_name(t)})) FROM (SELECT tablename as t FROM pg_tables WHERE schemaname = 'public') tabs"
    ).first["sum"].to_i
  end

  def test_seeding_successfully
    ActiveRecord::Base.connection.execute(
      "TRUNCATE TABLE #{ActiveRecord::Base.connection.tables.join(',')} RESTART IDENTITY CASCADE"
    )
    assert_difference "total_count", 1000..Float::INFINITY do
      Rails.application.load_seed
    end
  end

  def test_idempotent
    Rails.application.load_seed
    assert_no_difference "total_count" do
      Rails.application.load_seed
    end
  end
end

ActiveRecord enable query logs in production rails console 🔗

Rails.logger.level = :debug
# Or:
ActiveRecord::Base.logger.level = Logger::DEBUG

Using Object#tap to debug chained methods 🔗

require "debug"

def process(arr)
  arr
    .map { |n| n * 2}
    .tap { |arr| debugger(pre: "arr") }
    .select { |n| n > 5 }
end

Gems 🔗