Today I Learned

hashrocket A Hashrocket project

275 posts about #ruby surprise

Disable "Try it out" feature of Swagger UI

Swagger UI is a great way to view API documentation. It's even got a cool feature where you can excercise the api right from the documentation.

However that may not be wanted or necessary in every occasion. Turning it off wasn't as straightforward as I'd have expected.

The Swagger UI documentation shows a configuration option called tryItOutEnabled. This sounds promising and the description even states:

Controls whether the "Try it out" section should be enabled by default.

So using our knowledge on how to configure Swagger UI through rswag, we try:

Rswag::Ui.configure do |c|
  c.config_object["tryItOutEnabled"] = false
end

However we'll find that this doesn't do anything 😕

Later I found that this particular option sets whether or not the "Try it out" feature is open by default.

If we utilize the config option of supportedSubmitMethods and provide an empty array, we'll get something more usable.

Rswag::Ui.configure do |c|
  c.config_object["supportedSubmitMethods"] = []
end

Now the button will completely disappear!

Configure Swagger UI when using rubygem rswag

The swagger documentation gem rswag contains the library Swagger UI. This allows your generated documentation to be viewable from your webserver.

It's great out of the box but is also more configurable than the gem's documentation would lead you to believe.

You have direct access to things like authentication but anything deeper than that can be controlled via a configuation object.

Rswag::Ui.configure do |c|
  c.config_object["showExtensions"] = true
end

Utilize the configuration's config_object. It is just a hash that you can set with keys that match available options found in Swagger UI's configuration docs

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

Pass keyword arguments when using send

Don't use a hash, just pass send with a comma-separated list of keyword arguments:

class Animal < Struct.new(:name)
  def greet(name:, catch_phrase:)
    puts "Heya #{name}! What's new, #{catch_phrase}?"
  end
end

Animal.new("Rex").send(:greet, name: "Dillon", catch_phrase: "cool cat")
=> "Heya Dillon! What's new, cool cat?"

Sending with a hash will fail:

Animal.new("Rex").send(:greet, {name: "Dillon", catch_phrase: "cool cat"})
=> wrong number of arguments (given 1, expected 0; required keywords: name, catch_phrase) (ArgumentError)

undef_method vs remove_method

Ruby's undef_method and remove_method are both methods for removing a method from a class, but there are subtle differences between the two.

Say we have two classes that both define the method name, with one class inheriting from the other:

class Human
  def name
    "homo sapien"
  end
end

class Child < Human
  def name
    "small homo sapien"
  end
end

remove_method fully deletes a method from a particular class, but will still look for the method on parent classes or modules when called on the particular class:

child = Child.new
child.name
# => "small homo sapien"

class Child
  remove_method :name
end

child.name
# => "homo sapien"

undef_method in contrast will prevent Ruby from looking up the method on parent classes

child = Child.new
child.name
# => "small homo sapien"

class Child
  undef_method :name
end

child.name
# => raises NoMethodError
# undefined method `name' for #<Child:0x00007ffd91a007a8>