Today I Learned

hashrocket A Hashrocket project

301 posts about #ruby surprise

casecmp to Compare Strings

Today I learned about casecmp and casecmp? to compare strings in ruby.

casecmp compares the downcase of both strings and returns 1 if the compared string is smaller, -1 if it's larger, and 0 if they are equal (and nil if they can't be compared).

"hashrocket".casecmp("hashrocket") # => 0
"hashrocket".casecmp("hAsHrOcKeT") # => 0
"hashrocket".casecmp("hashrocket123") # => -1
"hashrocket".casecmp("hashrock") # => 1
"hashrocket".casecmp(123) # => nil

casecmp? does the same comparison but just returns a boolean.

"hashrocket".casecmp("hAsHrOcKeT") # => true
"hashrocket".casecmp("hashrock") # => false
"hashrocket".casecmp(123) # => nil

h/t Brian Dunn

Use ri to Lookup Ruby Docs from the Command Line

Did you know you can look up ruby documentation on Classes and methods from the command line? The slightly elusive ri command does just that. You can pass it an argument of the class/method you want to look up, or you can enter interactive mode without arguments.

$ ri uniq

$ ri Array#compact

$ ri Hash

You can also use it to browse all the pre-defined ruby global variables:

$ ri ruby:globals

Check out the docs or run ri --help to see all it can do.

h/t Brian Dunn

Rerun Only Failed Tests with RSpec

Say you run your entire rspec suite and a couple of tests fail. You make a change that should fix them. How can you quickly rerun those failed tests to see if they're green? It could take minutes to run the whole suite again, and all you care about is 2 tests.

That's where the --next-failure (-n) flag comes in handy. According to the docs it is "Equivalent to --only-failures --fail-fast --order defined)". So you can rerun only your failed specs, and exit immediately if one does fail. You could of course just use --only-failures too, but sometimes it's nice to fail fast.

bundle exec rspec -n

h/t Brian Dunn

Two Types of Ranges in Ruby

Today I learned there are two ways to construct a range in ruby. Using two dots .. creates a range including the start and end values.

(2..5).include?(2) # => true
(2..5).include?(5) # => true

Using three dots ... creates a range including the start value, but not the end value.

(2...5).include?(2) # => true
(2...5).include?(5) # => false

So if we think of them in terms of intervals, (a..b) is a closed interval ([a, b]), and (a...b) is a right half-open interval ([a, b)).

Ruby Data class to replace Struct

Ruby has a new-ish class to build "immutable" structs, check this out:

Measure = Data.define(:amount, :unit)
distance = Measure.new(100, 'km')

distance.amount
#=> 100

distance.unit
#=> "km"

And if you try to use a setter, then it will fail:

 distance.amount = 101
(irb):7:in `<main>': undefined method `amount=' for an instance of Measure (NoMethodError)

Regex in RSpec Argument Matchers

Today I learned you can use regular expressions in RSpec method argument expectations.

Suppose I have a method that takes an email, and a registered boolean as parameters:

def some_method(email:, registered:)
end

In a spec, it's easy enough to verify that it was called with set parameters, like test@example.com and true:

expect(subject)
  .to receive(:some_method)
  .with(email: 'test@example.com', registered: true)

But what if I want to verify that the email address just belongs to example.com? We can use a regex for that!

expect(subject)
  .to receive(:some_method)
  .with(email: /@example.com$/, registered: true)

Change creation strategy in FactoryBot

I found out that's possible to change the FactoryBot strategy by invoking the to_create method inside the factory definition.

We had to do that to make factory bot linting to work on a factory that acts like an enum. So we did something like this:

FactoryBot.define do
  factory :role do
    to_create do |instance|
      instance.attributes = instance.class
        .create_with(instance.attributes)
        .find_or_create_by(slug: instance.slug)
        .attributes

      instance.instance_variable_set(:@new_record, false)
    end
  end
end

The said part here is that FactoryBot expects us to mutate that instance in order to work.

Handle Julian Dates in Ruby

I recently had to deal with parsing Julian Dates on a project. This was my first time seeing this in the wild and to my surprise, Ruby has some handy utilities on Date for converting to/from Julian Date Numbers.

Parse a Julian Date Number into a Date

Date.jd(2459936)
#=> #<Date: 2022-12-22 ((2459936j,0s,0n),+0s,2299161j)>

Convert a Date Object to a Julian Date Number

Date.new(2024, 02, 29).jd
#=> 2460370

https://en.wikipedia.org/wiki/Julian_day

Remove padding from values in strftime

By default, some numbers in strftime are padded, either with 0 or ' '.

For example:

best_moment_ever = DateTime.new(1996, 2, 15, 19, 21, 0, '-05:00')
=> Thu, 15 Feb 1996 19:21:00 -0500

best_moment_ever.strftime("%m/%e/%Y at %l:%M%P")
=> "02/15/1996 at  7:21pm"

As we can see, there is a big gap between at and 7:21pm. This is because the hour is being padded with empty string. Sometimes this is fine, but if you ever wanted to remove any padding, just add a - flag to the directive:

best_moment_ever.strftime("%-m/%e/%Y at %-l:%M%P")
=> "2/15/1996 at 7:21pm"

Notice how we also removed other built in padding, like the 0 in the month

There's a few other ways you can manipulate the results. Learn more here!

Grab first N elements from an array

Have you ever wanted to grab the first n elements from an array?

You might think to do something like this:

fruit = ["apple", "banana", "blueberry", "cherry", "dragonfruit"]

# Grab the first 3 elements of the array
fruit[0...3]
=> ["apple", "banana", "blueberry"]

Well you could just use Array#take and tell it how many elements you want to take:

fruit = ["apple", "banana", "blueberry", "cherry", "dragonfruit"]

# Grab the first 3 elements of the array
fruit.take(3)
=> ["apple", "banana", "blueberry"]

Bonus: There is also Array#take_while which takes a block and passes elements until the block returns nil or false

fruit.take_while {|f| f.length < 8 }
=> ["apple", "banana"]
fruit.take_while {|f| f.length < 10 }
=> ["apple", "banana", "blueberry", "cherry"]

Map with Index

Ever wanted map_with_index, like each_with_index except for map instead of each? Turns out you can, with just a 1 character change. with_index is a method on Enumerator that lets you do just that:

['a', 'b', 'c'].map.with_index do |x, index|
  [x, index]
end
#=> [["a", 0], ["b", 1], ["c", 2]]

Docs

Ruby Scan with Index

If you want to search for a pattern in a string and get back all the matches of that pattern, you can use String#scan:

irb(main)> "..123...456...123".scan(/\d+/)
=> ["123", "456", "123"]

This is super useful. But sometimes, it would be even more useful to also know the index of where the match starts. Turns out, you can do this with $~

irb(main)> matches_with_index = []
irb(main)* "..123...456...123".scan(/\d+/).map do |x|
irb(main)*   [x, $~.offset(0)[0]]
irb(main)> end
irb(main)> matches_with_index
=> [["123", 2], ["456", 8], ["123", 14]]

$~ is a global variable that's equivalent to Regexp.last_match, which is the MatchData for the last successful pattern match - it basically lets you get some data about the last thing Regexp matched.

MatchData#offsetreturns an array with the starting and ending offsets of the match. So $~.offset(0)[0] -> the offset to the start of the match, and $~.offset(0)[1] -> the offset to the end of the match.

Ruby's Abbreviated Assignment Operators

Today I Learned ruby has a lot of abbreviated assignment operators.

The best known are += and -= to increment and decrement values:

x = 2
x += 1
x #=> 3

And of course there's ||=, to assign only if the value is nil or false:

x = nil
x ||= 4 #=> 4
x ||= 5 #=> 4

But these abbreviations can be applied to a lot more operators!

It works with all of the following: +, -, *, /, %, **, &, |, ^, <<, >>, &&, ||.

So we could use |= to union two arrays and assign the result to the variable:

x = [1, 2, 3]
x |= [2, 3, 4, 4]
x #=> [1, 2, 3, 4]

Using Symbol name in Ruby

Symbols are an integral part of Ruby… we use them everyday. However sometimes they can be used for identification where we use their stringified version for comparison.

input = “wasabi”
:wasabi.to_s == input
=> true[[]]

Every time we do this, a new string is allocated in memory as the representation of :wasabi. For a trivial example like this, it’s not a big deal but consider how often Ruby on Rails uses symbols (HashWithIndifferentAccess anyone?). Then the bloat becomes very real.

Introduced in Ruby 3.0, Symbol#name(https://github.com/ruby/ruby/pull/3514) aims to help. Utilizing this method returns a frozen string version of the symbol.

:wasabi.name
=> “wasabi”

This looks like we’re reproducing the same result and in a way we are. However due to the returned string being frozen, there is only one immutable instance of it being used in memory!

It can be used the same way as well now with less memory bloat.

input = “wasabi”
:wasabi.name == input
=> true

Endless Method Definition in Ruby

A new method definition was introduced in Ruby 3.0, the endless definition.

You're probably familiar with:

def do_something(number)
  number * 2
end

Of course, we can express this as a one-liner previously as:

def do_something(number); number * 2; end

Now you have the option to write it like this:

def do_something(number) = number * 2

Or another example:

def thing(x) = @thing = x

If you'd like to know more, here is where the spec was discussed

Yield a double to a Block in RSpec

TIL in rspec you can yield a double to a block with and_yield, similar to how you return a double with and_return.

With and_return you can write a test like this:

sftp = Net::SFTP.start(args)
sftp.upload!(content, path)

# Test
client = double
allow(Net::SFTP).to receive(:start).and_return(client)
expect(client).to receive(:upload!)

However, if your code has a block like below and_return won't work. Instead, you can use and_yield to yield the double to the block:

Net::SFTP.start(args) do |sftp|
  sftp.upload!(content, path)
end

# Test
client = double
allow(Net::SFTP).to receive(:start).and_yield(client)
expect(client).to receive(:upload!)

Docs

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