Today I Learned

A Hashrocket project

166 posts about #ruby

Find a Capybara node with regex

My pair and I faced a situation yesterday where we had to find a node with two separated pieces of text.

<section>
    <div>Something</div>
    <div>Some Other Text</div>
    <div>Apple</div>
</section>
<section>
  <div>Different</div>
  <div>Some Other Text</div>
  <div>Apple</div>
</section>
<section>
  <div>Something</div>
  <div>Some Other Text</div>
  <div>Orange</div>
</section>

We wanted to find a node with text “Something” and text “Orange” in our Capybara test.

The find method has a text option that you can pass a regex to.

find("section", text: /Something.*Orange/)

And initially this wasn’t working because of a line break and so we had to use the multiline flag (m).

find("section", text: /Something.*Orange/m)

Yanking a Ruby Gem

Yanking (unpublishing) a Ruby gem is something that has to happen from time to time. Maybe your library has a known vulnerability. Maybe you pushed a gem by mistake. Sometimes the best code is no code.

Here’s how you permanently yank a gem published to Rubygems:

$ gem yank <gemname> -v <version>

What are the side effects? If somebody was locked to your exact version, they would not be able to download that package again and would have to upgrade or downgrade. Consider that when deciding whether to yank.

Policy change about gem yank / CLI reference

Install `capybara-webkit`

Turns out capybara-webkit’s has a dependency on qtwebkit which has been removed from the qt library package. You’ll have the pleasure of finding this out when you run into this grand error:

Project ERROR: Unknown module(s) in QT: webkitwidgets

To get around this, you can install a previous version of qt using brew via:

brew install qt@5.5

Now reload your terminal and ensure that you can run qmake. Once that is all good, go ahead and reinstall/bundle capybara-webkit.

Don’t let this stump you for longer than it did me!

Mocking Requests With Partial URIs Using Regex

Generally when mocking out requests with the webmock gem, we specify full request URIs like so:

stub_request(:post, 'http://localhost:4000/api/posts')

We may not want to specify the entire URI though. For instance, the host may change or be configurable. The stub_request method allows us to use regex.

stub_request(:post, %r|/api/posts|)

Using the %r regex literal syntax, we are able to avoid escaping all of the / characters in our URI.

h/t Brian Dunn

`nil.&to_s` evaluates to false

ruby 2.3 introducted the Safe Naviation Operator &. intended to make nil checks less verbose, simulating the functionality of the Rails introduced try method.

This helps to prevent unwanted NoMethodErrors

> nil.do_something
NoMethodError (undefined method `do_something' for nil:NilClass)
> nil&.do_something
nil

What happens when you type the & on the right side of the .?

> nil.&do_something
NameError (undefined local variable or method `do_something' for main:Object)

Normally you’d get an error, because do_something isn’t defined, but what if you are calling a method available on everything like to_s?

> nil.&to_s
false

This breaks expectations. This statement is equivalent to nil & to_s.

> nil & to_s
false

Ruby defines & as a method on nil.

> nil.method(:&)
#<Method: NilClass#&>

This method in the documentation is defined as:

And—Returns false. obj is always evaluated as it is the argument to a method call—there is no short-circuit evaluation in this case.

So with nil.&to_s you’re calling the & operator as a method and passing the result of to_s called on the main object, but it doesn’t matter because nil.& always evaluates to false.

Create Listing Of All Middleman Pages

Middleman is a handy tool for quickly throwing together a bunch of static pages with layout and templating help at the ready. Once you have a handful of pages up and running, you’ll probably want a way to quickly navigate to them. You can add a quick listing of all the pages with a couple helpers provided by Middleman.

<ul>
  <% sitemap.resources.each do |resource| %>
    <li><%= link_to(resource.path, resource.path) %></li>
  <% end %>
</ul>

The sitemap.resources variable will contain a list of all the resources that get processed and served by the Middleman app. The link_to helper makes it easy to turn those into links.

Because resources includes images and other assets, you may want to filter down to just html files which could look something like the following:

<ul>
  <% sitemap.resources
       .select { |resource| resource.path =~ /html$/ }
       .each do |resource|
  %>
    <li><%= link_to(resource.path, resource.path) %></li>
  <% end %>
</ul>

Just add the snippet to whatever page you’d like the page listing to appear on.

Mutations with the graphql-client Ruby gem

The graphql-client is a really good library to consume Graphql APIs in Ruby.

You can execute a mutation the same way as if it was a regular query passing the variables you want to use:

require 'graphql/client'
require 'graphql/client/http'

module Api
  HTTP = GraphQL::Client::HTTP.new(ENV['GRAPHQL_API_URL'])
  Schema = GraphQL::Client.load_schema(HTTP)
  Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end

CreateCityMutation = Api::Client.parse(<<~'GRAPHQL')   
  mutation($name: String) {
    createCity(name: $name) {
      id
    }
  }
GRAPHQL

variables = {name: 'Jacksonville'}
result = Api::Client.query(CreateCityMutation, variables: variables)
puts result.data.create_city.id

Remove indentation from heredocs

Ruby heredocs are a convenient way to include a block of text in Ruby source. Unfortunately, if the text is indented properly with the rest of the code the actual string is also indented. This is often undesired. However, it can be fixed by using the squiggly heredoc.

lorem = <<~STR
  Lorem ipsum dolor sit amet,
  consectetur adipiscing elit,
  sed do eiusmod
STR

lorem #=> => "Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit,\nsed do eiusmod\n"

The squiggly heredoc automatically removes any indentation that is applied to all lines.

Azure key discovery provides modulus and exponent

To verify a JWT coming from Azure, you must provide the ruby JWT package a public key. That public key must be OpenSSL::PKey::RSA value.

The Azure key discovery url provides this public key definition:

{
  "keys": [
    {
      "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk",
      "nbf": 1493763266,
      "use": "sig",
      "kty": "RSA",
      "e": "AQAB",
      "n": "tVKUtcx_n9rt5afY_2WFNvU6PlFMggCatsZ3l4RjKxH0jgdLq6CScb0P3ZGXYbPzXvmmLiWZizpb-h0qup5jznOvOr-Dhw9908584BSgC83YacjWNqEK3urxhyE2jWjwRm2N95WGgb5mzE5XmZIvkvyXnn7X8dvgFPF5QwIngGsDG8LyHuJWlaDhr_EPLMW4wHvH0zZCuRMARIJmmqiMy3VD4ftq4nS5s8vJL0pVSrkuNojtokp84AtkADCDU_BUhrc2sIgfnvZ03koCQRoZmWiHu86SuJZYkDFstVTVSR0hiXudFlfQ2rOhPlpObmku68lXw-7V-P7jwrQRFfQVXw"
    }
  ]
}

First, I tried using the n value to create a ruby public key:

> OpenSSL::PKey::RSA.new(key['n'])
OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key: nested asn1 error

So n is not the public key? WTF is n. Via the wikipedia page for RSA.

Alice transmits her public key (n, e) to Bob via a reliable, but not necessarily secret, route

n is the modulus. e is the exponent (in this case AQAB), but the ruby library requires a pem. We must convert the modulus and exponent to a pem. Luckily there is a gem for that:

gem install rsa-pem-from-mod-exp

Convert to a pem then to a pubkey:

require 'rsa_pem'

pem = RsaPem.from(key['n'], key['e'])

public_key = OpenSSL::PKey::RSA.new(pem)

And verify your token

decoded_token = JWT.decode token, public_key, true, { :algorithm => 'RS256' }

Parse JSON into an OpenStruct

When you parse json in ruby it is placed into a hash, and you have to access the values with hash syntax:

parsed_data = JSON.parse('{"color": "blue"}')
puts parsed_data["color"] # prints 'blue'

But instead of a hash you can choose to parse it into an OpenStruct by using the object_class option. Then you can use ruby’s method syntax to access the data.

parsed_data = JSON.parse('{"color": "blue"}', object_class: OpenStruct)
puts parsed_data.color # prints 'blue'

H/T Dillon Hafer

Generate Absolute File Path In Ruby

Pass a string to File.expand_path to generate the path to that file or directory. Relative paths will reference your current working directory, and paths prepended with ~ will use the owner’s home directory.

File.expand_path('example.rb')
=> "/Users/avogel/My/Working/Directory/example.rb"

File.expand_path('~/example.rb')
=> "/Users/avogel/example.rb"

Limiting Mock API Restarts with Rerun

On a recent project, we built a Sinatra mock API. A feature we added early on was hot code reloading via the rerun gem. Every time a file in the API changes, Sinatra automatically restarts.

It’s a nice system but not perfect, because the server restarts on changes to any file ending in .rb, .html, .md, and number of other extensions. This captures the README, integration tests, and other undesirables, slowing everything down for no particular reason.

rerun’s --dir flag scopes it to just files in the given directory. We chose the src/ directory, which includes the API, mock database, and helpers.

$ rerun --dir src "ruby src/mock_api.rb"

Setting Struct properties with an index

It’s rare that I get a chance to use structs but yesterday while parsing some xml (!!!) I wrote an algorythm where it would be handy to set values of a struct with an incrementing number rather than a symbol.

Low and Behold! Ruby Structs allow you to set attributes with either the name of the property or the ordinal with which it was declared.

2.5.0 :001 > Apple = Struct.new(:color, :size)
 => Apple
2.5.0 :002 > apple = Apple.new
 => #<struct Apple color=nil, size=nil>
2.5.0 :003 > apple[0] = 'red'
 => "red"
2.5.0 :004 > apple[1] = 'huge'
 => "huge"
2.5.0 :005 > apple
 => #<struct Apple color="red", size="huge">

Structs are a great data structure that I’m going to try to use more in my daily programming.

Log rotation in ruby

Jake wrote a great til about linux log rotation last year. Ruby also has a log rotation alternative built into the stdlib.

Generally, ruby logging looks like this:

require 'logger'
logger = Logger.new('app.log')
logger.info('starting log')

You can also pass a ‘daily’, ‘weekly’ or ‘monthly’ to the logger:

logger = Logger.new('week.log', 'weekly')

If I use this log today, Tue Jan 9, and next week on Wed Jan 17, then ls will show two log files:

week.log
week.log.20180113

20180113 is Saturday the last day of the previous logging period, a new log is started every Sunday. NOTE: no files are deleted with this style of logging, you may potentially run out of space.

You can also choose to log with the number of files to keep and the maximum size of each file.

logger = Logger.new('size.log', 4, 1024000)

In this case after producing more than 4 MB of logging information ls will show you.

size.log
size.log.1
size.log.2
size.log.3

Compared to linux’s logrotate this is missing features, compression for example, but for adding log rotation to your logs quickly and simply it works great.

Documentation

Partial post body matching with webmock

When using webmock to stub out a post, you can specify that the post has a specific body. When dealing with web apis, that body is json and you want to compare the body to a ruby hash, from the webmock README, that looks like this:

stub_request(:post, "www.example.com").
  with(body: {a: '1', b: 'five'})

In the above example, the whole json body has to be represented in the hash. Webmock provides a way to just examine a portion of the json/hash with the hash_including method. This is not the rspec hash_including this is a webmock specific function using the WebMock::Matchers::HashIncludingMatcher class.

stub_request(:post, "www.example.com").
  with(body: hash_including({a: '1'}))

The rest of keyword arguments 🍕

Sometimes you pass some extra keyword arguments to a method that doesn’t handle them and you get the error ArgumentError: unknown keywords: ....

You can just use ** as the last argument to ignore the rest of the keyword arguments:

def make_pizza(cheese:, sauce:, **)
  puts "Making pizza with #{cheese} cheese and #{sauce} sauce"
end

make_pizza(cheese: 'muzzarella', sauce: 'tomato', chocolate: 'white', syrup: 'mapple')

=> Making pizza with muzzarella cheese and tomato sauce

You can also give it a name to group them:

def make_pizza(cheese:, sauce:, **rest)
  puts "Making pizza with #{cheese} cheese and #{sauce} sauce"
  rest.each {|k, v| puts "#{v.capitalize} #{k} is not good for you"}
end

make_pizza(cheese: 'muzzarella', sauce: 'tomato', chocolate: 'white', syrup: 'mapple')

=> Making pizza with muzzarella cheese and tomato sauce
=> White chocolate is not good for you
=> Mapple syrup is not good for you

Slow Down, Sinatra

Right now we’re using Sinatra to mock an API. An important consideration for our frontend experience: what happens when the API takes a long time to respond? How do the pages look when they are waiting on a slow, or absent, server?

Sinatra’s support of before blocks allow us to gauge this:

before do
  sleep(3)
end

get 'some/endpoint' do
  200
end

get 'some/other/endpoint' do
  404
end

With this block, every mocked endpoint will wait three seconds before responding, giving us plenty of time to consider what, in development, would otherwise be an instantaneous delay.

Remove namespaces for easier Xpath queries

Xpath can get strange when namespaces are involved

> doc = Nokogiri::XML(<<-XML)
  <a xmlns='http://www.example.com/xhtml'>
    <b>
      <c></c>
    </b>
  </a>
  XML
> doc.xpath("/a")
[] # returns empty array
> doc.xpath("/*[name()='a']")
# returns the first node

The [name()='a'] isn’t really clear and will become less clear as the query looks for elements deeper in the document. When the namespace doesn’t provide any value, for instance by helping to avoid collisions, or helping to validate the given xml document, then removing the namespace is entirely acceptable.

In Nokogiri you can remove namespaces from the document with, remove_namespaces!.

> doc.remove_namespaces!
> doc.xpath('/a')
[<Node a>]

Now traversing the document with xpath will be significantly more straightforward.

Ruby Binding Class

Ruby’s Binding class allows you to access classes, variables, and methods outside of the current scope.

class Foo
    def bar
        my_var = 20
        binding()
    end
end

Normally, if you made reference to my_var outside of that method, you’d get an error. Binding objects allow you to do this:

my_foo = Foo.new

foo_bar = my_foo.bar
puts foo_bar.eval('my_var')

# 20

Stacking HEREDOCS

I ran across this in a co-workers code yesterday. It made my head hurt for a second. Let’s say you need a a function that takes 4 lines as an argument and outputs each of those lines to stdout.

def print_stanza(line1, line2, line3, line4)
  puts line1, line2, line3, line4
end

print_stanza(<<-LINEA, <<-LINEB, <<-LINEC, <<-LINED)
  You can get all hung up
LINEA
  in a prickle-ly perch.
LINEB
  And your gang will fly on.
LINEC
  You'll be left in a Lurch.
LINED

The second HEREDOC starts where this first one ends, the third HEREDOC starts where the second one ends, etc. Its all valid Ruby and starts to make sense if you look at it long enough.

Most editors I’ve seen haven’t been able to highlight the syntax correctly though. Sorta leaves you in a Lurch.

H/T Brian Dunn

Use dotenv In A Non-Rails Project

Up to now I’ve only used dotenv in a Rails context. It can just as easily be used in a plain old Ruby project.

Install the non-Rails version of the gem.

$ gem install dotenv

Then add the following lines wherever you want dotenv included and loaded. In my case, I want it pulled in as part of my RSpec setup in spec_helper.rb.

require 'dotenv'
Dotenv.load

Your environment variables declared in .env are now accessible via fetches against the ENV object.

ENV.fetch('my_env_var')

`github` as source block in Gemfile

source blocks in a Ruby Gemfile help group gems together that come from the same source. In addition, the Gemfile supports a github block for multiple gems that are coming from the same github repository. In my specific case, there are two gemspecs in the Brian Dunn’s flatware repo.

github 'briandunn/flatware', branch: master do
  gem 'flatware-rspec'
  gem 'flatware-cucumber'
end

With this example, only one change is needed to change the branch that both of those gems will come from.

H/T Brian Dunn

Ruby srand returns the previous seed

srand is a method on Kernel that seeds the pseudo random number generator. It takes a new seed as an argument or calls Random.new_seed if you don’t pass an argument. What’s interesting about it is that it returns the old seed. This has the effect of return a new large random number every time you call srand.

2.4.1 :007 > srand
 => 94673047677259675797540834050294260538
2.4.1 :008 > srand
 => 314698890309676898144014783014808654061
2.4.1 :009 > srand
 => 102609070680693453063563677087702518073
2.4.1 :010 > srand
 => 81598494819438432908893265364593292061

Which can come in handy if you’re playing some Ruby golf and need to generate a huge random number in as few characters as possible.

H/T Dillon Hafer

Capturing stderr when shelling out in ruby

All of the usual methods of shelling out in ruby (backticks, system, etc.) don’t capture stderr, but there’s an oddly named library open3 that can help with that.

require 'open3'

result_or_err, process_status = Open3.capture2e('cat abc')
puts result_or_err
# 'cat: abc: No such file or directory'
puts process_status
# <Process::Status: pid 6729 exit 1>]

In this case cat abc errors out because there is no abc file, so the command writes to stderr. If there was an abc file, result_or_err would contain the contents of the abc file.

source

Non-ActiveRecord objects in FactoryGirl

Creating non-ActiveRecord objects with FactoryGirl is possible. Classically, a constructor is used to set all the data attributes of an object.

class ParsedString
  attr_reader :abc, :def
  def initialize(string)
    @abc, @def = string.split(?|)
  end
end

In the above class the attributes are set in the constructor by passing in a string that gets split into two parts on \|. Lets use instantiate_with to determine how the object gets instantiated in FactoryGirl.

factory :parsed_string do
  initial_value "123|456"

  initialize_with { new(initial_value) }
end

And then in the test we can FactoryGirl.build, like:

ps = FactoryGirl.build(:parsed_string)
expect(ps.abc).to eq 123
expect(ps.def).to eq 456

Pass name of function as a block

In Ruby it is possible to pass a function name instead of a block to a function expecting a block using &method in the last argument.

For example:

def blocky_fun(foo)
  bar = "hello #{foo}"
  yield(bar)
end

blocky_fun('blocky', &method(:puts))

The result of the above would be that the puts function will be called with “hello blocky” as the first argument.

Expect page to have a link... an exact link

Sometimes, while writing integration tests, we expect there to not be a link on a page, for instance if our page looked like this:

<a href="#create_jetpack">Create Jetpack</a>

And we want to make sure there is no general create link so we expect this:

expect(page).to_not have_link('Create')

But the expectation fails! By default capybara will match if the substring is anywhere in the content.

To avoid this problem use the exact flag like:

expect(page).to_not have_link('Create', exact: true)

Ruby Object Reference in blocks

Usually when I want to set the value of a variable by yielding to a block, I will explicity set it

  x = 'something'
  x = yield(x)
  #....

Turns out ruby will treat different types of objects different when yielded to a block (remember everything is an object). If you are yielding an Integer, or String for (a contrived) example:


def do_stuff_by_val
  do_block_stuff_by_val do |name|
    name += 'rocket'
  end
end

def do_block_stuff_by_val
  company_name = 'hash'
  yield(company_name)
  puts company_name #prints 'hash', not 'hashrocket'
end

If we wanted the company name to change to “hashrocket”, we would need to explicitly set the variable to the result of yielding:

company_name = yield(company_name)

Other types of objects do not behave this way (hash or array). If we yield a hash and set a key, the hash in the calling method will be updated:


def do_stuff_by_object_reference
  do_block_stuff_by_object_reference do |company|
    company[:name] = 'hashrocket'
  end
end

def do_block_stuff_by_object_reference
  company = {}
  yield(company)
  puts company # { name: 'hashrocket'}
end

Resetting the bundler --path

When running bundler install you can pass an option --path <dir>:

bundle install --path ./gems

Which will specify where to install the gems. This is a one time option. Everytime you invoke bundle from here on in you’ll see that its using the directory you specificed with the —path variable as the gem directory.

Bundler keeps track of this option in the .bundler/config file in the folder from where bundler was initially run. That file looks like this:

---
BUNDLE_PATH: "./gems"
BUNDLE_DISABLE_SHARED_GEMS: "true"

To reset the gems path back to the default, remove the BUNDLE_PATH line from the bundle/config file.f

Reinstall all rubies with rvm

When compiling ruby the executable is attached to various C system libs like libreadline. If libreadline is updated then each ruby on the system attached to the old libreadline must be re-compiled. If you are using rvm then this can be done with one command:

rvm reinstall all --force

The --force flag will skip any question the process asks.

Be careful, if you are working on a machine that has installed many rubies and has had many projects for those rubies this could take an extraordinarily long time.

Webmock assertion that an http request was made

Webmock is a library that helps you manager the http requests that are made during a test. Its not advisable to hit production systems with test-generated requests and with webmock you can stub those requests. Sometimes to ensure functionality of your system you might want to assert that the request was made.

stub_request(:get, 'http://www.google.com')
assert_requested(:get, 'http://www.google.com')

The stub_request method also returns a variable that can be passed to the assert function.

get_google = stub_request(:get, 'http://www.google.com')
assert_requested get_google

Ins And Outs Of Pry

When executing commands during a Pry session, you’ll see an incrementing number for each prompt as you enter each statement. These numbers can be used to look up the inputs and outputs of each statement executed during the session. The statements and their results are made available in the array-like _in_ and _out_ objects.

[1] pry(main)> :one
=> :one
[2] pry(main)> 1 + 1
=> 2
[3] pry(main)> ["t", "h", "r", "e", "e"].join
=> "three"
[4] pry(main)> _in_.to_a
=> [nil, ":one\n", "1 + 1\n", "[\"t\", \"h\", \"r\", \"e\", \"e\"].join\n"]
[5] pry(main)> _out_.to_a
=> [nil, :one, 2, "three", [nil, ":one\n", "1 + 1\n", "[\"t\", \"h\", \"r\", \"e\", \"e\"].join\n"]]
[6] pry(main)> _out_[2]
=> 2
[7] pry(main)> _in_[2]
=> "1 + 1\n"

source

Rails/PG Statement Timeout 🐘⏰

By default, Rails does not set a timeout on database statements. For example, this will run for a full day, even if your ruby process goes away.

ActiveRecord::Base.connection.execute(<<~SQL)
  select pg_sleep(86400);
SQL

The good news is that Rails provides a way to prevent this from happening from the database.yml with the statement_timeout variable.

default: &default
  adapter: postgresql
  ...
  variables:
    statement_timeout: 5000

I’m starting to think that this should always be set low. And then explicitly set higher on a per-query basis, when one is expecting something to take a long time.

Chaining Multiple RSpec Change Matchers

It can be handy to use RSpec’s change matchers to determine if some method or process creates a new record.

expect{ Registration.create(attrs) }.to change{ User.count }.by(1)

But what if we are testing a method that creates a couple different records in the system?

RSpec allows us to chain together change matchers with and. Consider this additional contrived example.

expect {
  Project.generate(attrs)
}.to change{ Project.count }.by(1).and \
     change{ User.count }.by(1)

In addition to keeping our tests tight and concise, this approach gives some pretty nice output on failure.

If we were just beginning our implementation with a failing test, we’d see a multi-part failure like the following.

Failure/Error:
  expect {
    Project.generate(attrs)
  }.to change{ Project.count }.by(1).and \
       change{ User.count }.by(1)

     expected result to have changed by 1, but was changed by 0

  ...and:

     expected result to have changed by 1, but was changed by 0

Rails on ruby 2.4: Silence Fixnum/Bignum warnings

Wanna use the latest ruby, but don’t really need to be perpetually reminded that rails hasn’t caught up with this latest ruby design change? Change the top of your config/application.rb thusly:

require_relative 'boot'

verbose = $VERBOSE
$VERBOSE = nil
require 'rails/all'
require 'active_support/core_ext/numeric/conversions'
require 'active_job/arguments'
$VERBOSE = verbose
Bundler.require(*Rails.groups)

module MyApp
  class Application < Rails::Application
  end
end

Yes, this will also suppress any other ruby warnings from the loading of rails code. ¯\_(ツ)_/¯

Rerun Only Failures With RSpec

After running a big test suite, I may have a bunch of output on the screen including the results of a couple test failures. I like to bring the context of the test failures front and center and make sure they are consistent test failures (not flickering failures). Instead of copying and pasting each failure, I can rerun rspec in a way that executes only the test cases that failed.

$ rspec --only-failures

This feature requires that you set a file for RSpec to persist some state between runs. Do this in the spec/spec_helper.rb file. For example:

RSpec.configure do |config|
  config.example_status_persistence_file_path = "spec/examples.txt"
end

See more details here.

h/t Brian Dunn

Use A Case Statement As A Cond Statement

Many languages come with a feature that usually takes the name cond statement. It is essentially another way of writing an if-elsif-else statement. The first conditional in the cond statement to evaluate to true will then have its block evaluated.

Ruby doesn’t have a cond statement, but it does have a case statement. By using a case statement with no arguments, we get a cond statement. If we exclude arguments and then put arbitrary conditional statements after the when keywords, we get a construct that acts like a cond statement. Check out the following example:

some_string = "What"

case
when some_string.downcase == some_string
  puts "The string is all lowercase."
when some_string.upcase == some_string
  puts "The string is all uppercase."
else
  puts "The string is mixed case."
end

#=> The string is mixed case.

source

Ruby Arguments Can Reference Other Arguments

I love the dark corners of Ruby. Today we discovered this one by basically guessing that it might work:

def foos(foo, bar = foo.upcase + "!")
  puts foo
  puts bar
end
2.3.1 :001 > foos('foo')
foo
FOO!
 => nil

That’s right; bar references another argument, foo. And we can also call a method on it. And concatenate it with a string, because, why not? The ‘principle of least surprise’ strikes again.

h/t Brian Dunn

Ruby $!

This week I enjoyed using Ruby’s built-in global variables, including $!. $! refers to the last error that was raised, and is useful in lots of different contexts such as Rake tasks.

Here it is in action:

:001 > begin; raise NameError; rescue; puts "The error we raised was #$!."; end
The error we raised was NameError.

And a list of these global variables:

https://github.com/ruby/ruby/blob/trunk/lib/English.rb

For a slightly less bare-metal implementation, require this library and utitlize the friendly names:

:001 > require 'english'
 => true
:002 > begin; raise NameError; rescue; puts "The error we raised was #$ERROR_INFO."; end
The error we raised was NameError.