Today I Learned

hashrocket A Hashrocket project

280 posts about #ruby surprise

Using slice_after to split arrays by a value

Given you have an array of objects that you may want to split apart based on a value on one of the objects, you can use slice_after (there's also slice_before, which behaves the same way).

array = [
  {activity: "traveling", ticket: "123"},
  {activity: "working", ticket: "123"},
  {activity: "awaiting_assignment", ticket: ""},
  {activity: "traveling", ticket: "234"},
  {activity: "refueling", ticket: "234"},
  {activity: "traveling", ticket: "234"},
  {activity: "working", ticket: "234"},
  {activity: "awaiting_assignment", ticket: ""}
]

array.slice_after { |i| i.activity == "awaiting_assignment" }
# Returns:
[
  [
    {activity: "traveling", ticket: "123"},
    {activity: "working", ticket: "123"},
    {activity: "awaiting_assignment", ticket: ""}
  ],
  [
    {activity: "traveling", ticket: "234"},
    {activity: "refueling", ticket: "234"},
    {activity: "traveling", ticket: "234"},
    {activity: "working", ticket: "234"},
    {activity: "awaiting_assignment", ticket: ""}
  ]
]

Decomposing Nested Arrays

As you probably already know, in Ruby, you can decompose a nested array into variables like so:

letters_and_numbers = [["a", "b", "c", "d", "e"], [1, 2, 3, 4, 5]]
letters, numbers = letters_and_numbers

>> letters
=> ["a", "b", "c", "d", "e"]

>> numbers
=> [1, 2, 3, 4, 5]

However, did you also know that you can add parentheses () to decompose specific values from a nested array?

letters_and_numbers = [["a", "b", "c", "d", "e"], [1, 2, 3, 4, 5]]
(a, b, *other_letters), numbers = letters_and_numbers

>> a
=> "a"

>> b
=> "b"

>> other_letters
=> ["c", "d", "e"]

>> numbers
=> [1, 2, 3, 4, 5]

Note: You can also grab values from either the beginning or end of the array!

letters_and_numbers = [["a", "b", "c", "d", "e"], [1, 2, 3, 4, 5]]
(a, *other, d, e), _ = letters_and_numbers

>> a
=> "a"

>> other
=> ["b", "c"]

>> d
=> "d"

>> e
=> "e"

Ruby Rightward Assignment -> JS-like Destructuring

It's not often there's a javascript feature I wish was available in ruby, but here we are. But, it turns out ruby has the functionality as of 2.7 and I was just out of the loop.

In javascript you can use destructuring assignment to unpack a bunch of variables in a single line:

const obj = { a: 1, b: 2, c: 3, d: 4 }
const { a, b, d: newName } = obj
console.log([a, b, newName]) 
// => [1, 2, 4]

With rightward assignment you can do a similar thing with hashes, though with slightly different syntax:

hsh = { a: 1, b: 2, c: 3, d: 4 }
hsh => { a:, b:, d: new_name }
puts [a, b, new_name]
# => [1, 2, 4]

Nice!

Gem pristine <gem_name> restores installed gems

If you have been directly working or debugging within your gems and wish to revert any changes made, instead of manually undoing all of the changes in each file, you can simply run gem pristine <gem_name>. This command restores installed gems to their original, pristine state using the files stored in the gem cache.

If you want to check out what options you can pass to it, here is some documentation.
H/T Matt Polito

FactoryBot skip_create

factory_bot has an option to skip calling save! on create:

FactoryBot.define do
  factory :model_without_table do
    skip_create
    an_attribute { "An Attribute" }
  end
end

This will build the object in memory, but not persist it. Useful if you want to create a factory for a model that isn't backed by a database table, where trying to persist the record would result in an exception.

Docs

RSpec Spies 🕵️‍♀️

Most of the time when I write an RSpec test to see if a message was received, I'll write the expectation first using a mock, then exercise the subject under test:

class SomeJob
  def perform
    SomeService.call
  end

end

Rspec.describe SomeJob do
  it "makes the call" do
    expect(SomeService).to receive(:call)
    SomeJob.new.perform
  end
end

Sometimes it's useful to flip this around, with the expectation after the action was performed. We can do this with a spy - we first stub the call with allow, exercise the subject under test, then assert with have_received:

class SomeJob
  attr_reader :some_attribute

  def perform
    set_an_instance_var
    SomeService.call(some_attribute)
  end

  def set_an_instance_var
    @some_attribute = :something
  end
end

Rspec.describe SomeJob do
  subject { SomeJob.new }

  it "makes the call with an argument" do
    allow(SomeService).to receive(:call)
    subject.perform
    expect(SomeService).to have_received(:call).with(subject.some_attribute)
  end
end

This is particularly useful if you want to assert against something (say an instance variable) that doesn't get set until the subject is exercised. There's no way to test the above example with the assertion first, since we won't know what some_attribute is until we perform - a perfect use case for a spy.

h/t Matt Polito

Don't auto generate Gem documentation in Ruby

I always forget to disable generation of gem documenatation until I see it getting generated during install :-(

Do yourself a favor and create a .gemrc if you don't already have one and add:

gem: --no-document

Now all of your gem installs will be speedier and take up less space.

Some of you may remember --no-ri & --no-rdoc, however --no-document takes care of both.

View the gem documentation for more info

Create a Date object for a specific day

Say you have some date-specific functionality, and you want to test for a specific day of the week.

Date#commercial is what you're looking for! It will create a Date object for you based on the year,week, and day that you give it.

require 'date'

# Wednesday (3) of week 1 of year 2023
Date.commercial(2023, 1, 3)

In Rails we can take this a step further, for example, to get Friday of this week:

Date.commercial(Date.current.year, Date.current.cweek, 5)

In your testing you can simply make use of the Rails TimeHelpers to travel to that specific date you need:

next_friday = Date.commercial(Date.current.year, Date.current.cweek + 1, 5)

travel_to next_friday do
  # Friday specific code
end

Pretty-print JSON in Ruby

When receiving a JSON payload it can, of course, be useful to see it in a more readable way. Turns out there is a built in utility in Ruby that can help with this.

Kernal#j & Kernal#jj

Utilizing the Kernal#j method:

foo = {name: "Matt", company: "Hashrocket"}

=> j foo
{"name": "Matt", "company": "Hashrocket"}

Utilizing the Kernal#jj method:

foo = {name: "Matt", company: "Hashrocket"}

=> jj foo
{
  "name": "Matt",
  "company": "Hashrocket"
}

Curl `-T/--upload-file` in Faraday

I had a bit of trouble trying to find docs on how to do a curl --upload-file request with ruby. This flag is a special flag that tells curl to generate a PUT request with the body being the file(s) to upload to the remote server.

In my case, I wanted to upload a single file, and I accomplished this with the faraday and faraday-multipart gem:

require 'faraday'
require 'faraday-multipart'

conn = Faraday.new("https://example.com") do |f|
  f.request :multipart
end

upload_file = File.open("./path/to/image.jpg")

conn.put("/file-upload") do |req|
  req['Content-Type'] = 'image/jpeg'
  req['Content-Length'] = upload_file.size.to_s
  req.body = Faraday::Multipart::FilePart.new(
    upload_file,
    'image/jpeg'
  )
end

Part of the magic here is that you need to explicitly set the Content-Type and the Content-Length header.

https://github.com/lostisland/faraday-multipart

The difference between %w and %W in Ruby

%w can construct space delimited word arrays like this

%w(my cool word array)
#=> ["my", "cool", "word", "array"]

%W works similarly, however it offers ways to interpolate with variables and escape special characters in the assignment, while %w does not.

street_name = 'Sesame Street'
%W(I live on #{street_name})
#=> ["I", "live", "on", "Sesame Street"]

Find Unused Cucumber Step Definitions

One of the challenges of using cucumber is properly managing your step definitions. Left unchecked, you will eventually have many unused steps. It's extremely cumbersome to prune these manually. Luckily, you can use cucumber's -f / --format flag to get feedback on unused step_definitions and their locations:

bundle exec cucumber --dry-run --format=stepdefs

If your step definition is unused, it will be annotated with a line under that says NOT MATCHED BY ANY STEPS. See the example -

/^I submit the proposal request form$/     # features/step_definitions/contact_steps.rb:39
  NOT MATCHED BY ANY STEPS

Ruby memoization with nil values

As Ruby developers, we're often looking for ways to reduce time consuming lookups in our code. A lot of times, that leads us to memoizing those lookups with the common ||= operator.

However, if our lookups return a nil or falsey value, our memo will actually keep executing the lookup:

def ticket
  @ticket ||= Ticket.find_by(owner:)
end

This code essentially boils down to:

def ticket
  @ticket = @ticket || Ticket.find_by(owner:)
end

If our find_by in the example above returns nil, the code will continue to run the find_by every time we call the ticket method.

To avoid this, we can shift our pattern a bit, and look to see if we have already set our instance variable or not:

def ticket
  return @ticket if defined?(@ticket)
  @ticket = Ticket.find_by(owner:)
end

Quickly find module inclusion in Ruby

Given I have a class like so:

class Location < ActiveEnum::Base
  include WithLabel
end

Normally I would check for inclusion of something via a declarative method like:

Location.ancestors.include?(ActiveEnum::Base)
=> true

Location.ancestors.include?(String)
=> false

Location.included_modules.include?(WithLabel)
=> true

However it never occured to me that < is defined on Class and returns true if is a subclass of the requested module.

So we can do something like this now:

Location < ActiveEnum::Base
=> true

Location < String
=> nil

Location < WithLabel
=> true

Subtle difference is that the 'falsey' case returns nil instead of false.

Also the definition of this method states that it returns true if module is a subclass of other but I've found that it returns true for methods that are included as well. Take that as you will.

Endless Range

If you have a Range that you want to extend infinitely in either direction, just simply leave it blank.

Here's a simple example:

def age_category(age)
  case age
  when (...0)
    "unborn"
  when (0..12)
    "youngling"
  when (13..17)
    "teenager"
  when (18..64)
    "adult"
  when (65..)
    "old"
  end
end
>> age_category    0 => "youngling"
>> age_category   13 => "teenager"
>> age_category   18 => "adult"
>> age_category   65 => "old"
>> age_category  999 => "old"
>> age_category -999 => "unborn"

In a situation like this it's nice to extend infinitely, rather than having to come up with some kind of arbitrary cutoff age like 100, that could in rare cases cause problems.

H/T Matt Polito for showing me this.

Nil is actually NilClass

If for whatever reason you wanted to modify something inside nil, the class name is NilClass

🤯 Yes it's very mind blowing, I know 🤯

Example of bad code I couldn't figure out why it wasn't working

class Nil
  def to_i
    -1
  end
end

The fix needed to make it behave as expected

class NilClass
  def to_i
    -1
  end
end

Pathnames using division in Ruby

This will probably seem like a common pattern to you:

Rails.root.join("spec/support")

or

Rails.root.join("spec", "support")

Did you know that the division operator on Pathname is aliased to the addition operator? So you can do this:

Rails.root / "app" / "views"

I know it will probably frazzle a bunch of people, but I kinda love it.

Match strings with regular expressions

In Ruby you can use String#=~ to compare a string with regexp, returning the first index where it is found. For example let's search for the first ? in this string:

"www.example.com/search?meatloaf" =~ /\?/ 
=> 22

If there is no match, it returns nil

"www.example.com" =~ /\?/ 
=> nil

BONUS: When using Regexp#=~, which functions very similarly, you can use a regexp with named captures to store them in local variables.

/(?<search_params>\?.+)/ =~ "www.example.com/search?lasagna"
=> 22
search_params
=> "?lasagna"

Write a warning message to $stderr

While debugging, if you ever need to write to $stderr you might use $stderr#puts, but you can use Warning#warn which is better called from Kernel, because Kernel appends newlines and respects the -W0 flag:

$stderr.puts "you have been warned"
Warning.warn "you have been warned"
Kernel.warn "you have been warned"
warn "you have been warned"

Output:

you have been warned
you have been warnedyou have been warned
you have been warned

Avoid time discrepancies when benchmarking

You can avoid time discrepancies due to gc and allocs by using Benchmark#bmbm:

require 'benchmark'
array = (1..1000000).map { rand }
Benchmark.bmbm do |x|
  x.report("sort!") { array.dup.sort! }
  x.report("sort")  { array.dup.sort  }
end
Rehearsal -----------------------------------------
sort!   1.490000   0.010000   1.500000 (  1.490520)
sort    1.460000   0.000000   1.460000 (  1.463025)
-------------------------------- total: 2.960000sec
            user     system      total        real
sort!   1.460000   0.000000   1.460000 (  1.460465)
sort    1.450000   0.010000   1.460000 (  1.448327)

String concatenation in Ruby

My first instinct when it comes to concatenating strings would be to use the += operator:

name = "Peter"
=> "Peter"

name.object_id
=> 15320

name += " Parker"
=> "Peter Parker"

name.object_id
=> 34480

However, as you can see, doing this creates a new object. Instead, << should be used to maintain the same string object and also improve performance when working on larger batches of strings.

name = "Peter"
=> "Peter"

name.object_id
=> 54960

name << " Parker"
=> "Peter Parker"

name.object_id
=> 54960

Invoke procs with brackets

You can invoke a <Proc (lambda)> with brackets Proc[]:

UriService = lambda do |username:, password:|
  def scream(n) n.upcase end

  "http://#{scream(username)}:#{password}@api.example.com"
end

UriService.call(username: "password", password: "123")
=> "http://PASSWORD:123@api.example.com"

UriService[username: "password", password: "123"]
=> "http://PASSWORD:123@api.example.com"

Ruby Private Class Methods

Today I learner that Ruby Module has private_class_method, this way we can for example make the new method as private on service objects:

class MyServiceObject
  private_class_method :new

  def self.call(*args)
    new(*args).call
  end

  def initialize(foo:)
    @foo = foo
  end

  def call
    @foo
  end
end

Note that in this example the new method is private, so the .call will work, but the .new().call will raise an error:

irb> MyServiceObject.call(foo: "FOO")
=> "FOO"

irb> MyServiceObject.new(foo: "FOO").call
NoMethodError: private method `new' called for MyServiceObject:Class

Reject blank input with graphql-ruby

The graphql-ruby gem has a built-in blank validator:

class Mutations::UserUpdate < Mutations::BaseMutation
  null true

  argument :user_id,
    String,
    "Identifier of user",
    required: true,
    validates: {allow_blank: false}

  field :user_id, String, null: false

  def resolve(user_id:)
    {user_id:}
  end
end

So now a mutation with a user_id of " " will cause the graphql response to have an error:

mutation UserUpdate($userId: String!) {
  userUpdate(userId: $userId) {
    userId
  }
}

Ruby Squeeze

Ruby has a method to remove repeating characters in a string called squeeze

"foobar".squeeze
# => "fobar"

"foo foo bar".squeeze
# => "fo fo bar"

It also accepts args to narrow down the specific characters for which you would want to remove repeats:

"foobar hello world".squeeze("o")
# => "fobar hello world"

"foobar  hello  world".squeeze(" ")
# => "foobar hello world"

Create Struct with Keyword Args in Ruby

I use Ruby Structs all the time. They're great... if you don't, check them out!

However I have found them a bit cumbersome to set up because they are generally used with positional arguments:

Money = Struct.new(:price, :currency)
Money.new(1.23, "USD")

Be cumbered no more! I have found a different approach.

Money = Struct.new(:price, :currency, keyword_init: true)
Money.new(currency: "USD", price: 1.23)

Using the keyword_init argument allows the new Struct instantiation to accept keyword arguments which, I find, clearer to read and also do not need to be positional.

Compact a Hash in Ruby

I use Array#compact all the time... you know the one that gets rid of all the nils.

Well I have no idea why it never occured to me that Hash ALSO has a #compact method.

It removes all the key/value pairs where the value is nil.

{
  a: "Alluro",
  b: nil,
  c: "Cheetara"
}.compact

=> {a: "Alluro", c: "Cheetara"}

Ensure Ruby returns the correct value

Ruby has implicit returns for any possible block that I can think of, except ensure. There might be more, but this is the only one that I can think of now.

So in order to return something from inside the ensure block we must use the return reserved word explicitly. Check this out:

def check_this_out
  yield if block_given?
  :ok
rescue
  :error
ensure
  :ensured
end

irb()> check_this_out { "should work" }
=> :ok

irb()> check_this_out { raise "should fail" }
=> :error

As we can see even though the ensure code runs on all calls it does not return :ensured.

Here's the code with the explicit return:

def check_this_out_explicit_ensure_return
  yield if block_given?
  :ok
rescue
  :error
ensure
  return :ensured
end

irb()> check_this_out_explicit_ensure_return { "should work" }
=> :ensured

irb()> check_this_out_explicit_ensure_return { raise "should fail" }
=> :ensured

Return difference between Lambda and Proc in Ruby

More differences between Procs & Lambdas

If a Proc has an explicit return then that return bubbles up to where it is being used.

Let's take a look at a Proc's behavior:

# Explicit return
def work_it
  p = Proc.new { puts "Workout"; return }
  puts "Pre workout"
  p.call
  puts "Post workout"
end

=> work_it
Pre workout
Workout
=> nil
# No explicit return
def work_it
  p = Proc.new { puts "Workout" }
  puts "Pre workout"
  p.call
  puts "Post workout"
end

=> work_it
Pre workout
Workout
Post workout
=> nil

Now let's look at lambdas:

# Explicit return
def work_it
  l = -> { puts "Workout"; return }
  puts "Pre workout"
  l.call
  puts "Post workout"
end

=> work_it
Pre workout
Workout
Post workout
=> nil

Now even with the explicit return in the lambda, the method is able to complete its own logic path.

Arity difference between Lambda and Proc in Ruby

Many devs think Procs & Lambas in Ruby are interchangable... and it a lot of cases they can be.

However I did come across a difference to be aware of.

Procs do not enforce arity where a Lambda will.

Let's take a look at a Proc's behavior:

# Argument provided
p = Proc.new { |arg| puts arg }
p.call("TEST")
TEST
=> nil
# No argument provided
p = Proc.new { |arg| puts arg }
p.call

=> nil

Now let's look at lambdas:

# Argument provided
l = ->(arg) { puts arg }
l.call("TEST")
TEST
=> nil
# No argument provided
l = ->(arg) { puts arg }
l.call

=> wrong number of arguments (given 0, expected 1) (ArgumentError)

See how there is strict arity on a lambda where the proc will not complain.

Ruby's ENV::[]= only accepts strings

You cannot set the value of an environment variable to something that is not a string

# THIS DOES NOT WORK
ENV["SKIP_AUTH"] = true
=> `[]=': no implicit conversion of true into String (TypeError)

You can, however, pass an object that implements #to_str

class User < ApplicationRecord
  def to_str
    to_global_id.to_s
  end
end

ENV["user"] = User.first
ENV["user"]
=> "gid://rails-app/User/3f565b9c-0899-49f6-ab20-aa2724235ff5"

Be careful when stubbing ENV in specs:

# ENV could never return a boolean, your tests will lie to you.
RSpec.describe "ENV" do
  before do
    stub_const("ENV", {"SKIP_AUTH" => true})
  end
end