Count Occurrences in Ruby
Ruby has a new-ish method to count occurrences in an array. So since ruby 2.7.0 you can use the Enum tally to do:
["a", "b", "c", "b"].tally
# => {"a"=>1, "b"=>2, "c"=>1}
Here's a similar function in Elixir
Ruby has a new-ish method to count occurrences in an array. So since ruby 2.7.0 you can use the Enum tally to do:
["a", "b", "c", "b"].tally
# => {"a"=>1, "b"=>2, "c"=>1}
Here's a similar function in Elixir
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)
Today I learned that Ruby has an alias for Array slice method, basically you can use the []
the same way you'd use the slice
method:
array = [ "a", "b", "c", "d", "e" ]
array[3..5]
# => ["d", "e"]
array.slice(3..5)
# => ["d", "e"]
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.
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
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!
Today I learned RSpec has a be_within matcher.
It does what it says - it verifies if the actual is within a delta (<=
)
of the expected.
expect(3.0).to be_within(0.5).of(3.0) # ✅
expect(3.2).to be_within(0.5).of(3.0) # ✅
expect(3.5).to be_within(0.5).of(3.0) # ✅
expect(4.3).to be_within(0.5).of(3.0) # ❌
Bundler allows you to pass group options when running commands in the terminal:
bundle outdated --group test
bundle upgrade --group test
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"]
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]]
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#offset
returns 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.
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]
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
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
Ruby blocks offer a shorthand to positional block variables.
Normally you'd see a block like this:
[1, 2, 3].each { |number| do_something(number) }
However with Numbered Parameters, introduced in Ruby 2.7, we can express this like so:
[1, 2, 3].each { do_something(_1) }
The _1
takes the place of an explicitly defined block variable.
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!)
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: ""}
]
]
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"
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!
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
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.
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
So you've done some configuration of your rubygems via the .gemrc
file but now you want to bypass that.
The --norc
flag will do just that:
gem install decent_exposure --norc
Now any configuration in your .gemrc
will be ignored.
For more info, check out the Rubygems documentation
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
Ever run into ruby's multiline autocomplete getting in the way while you're in a Heroku console?
Try opening your console this way:
heroku run "rails console -- --nomultiline"
Now it'll be disabled for that console session.
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
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.
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"
}
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.
When using the rackup gem, you can set the port with the --port
flag
rackup --port 4000
[2023-02-18 14:28:17] INFO WEBrick 1.8.1
[2023-02-18 14:28:17] INFO ruby 3.1.2 (2022-04-12) [x86_64-linux]
[2023-02-18 14:28:17] INFO WEBrick::HTTPServer#start: pid=62732 port=4000
%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"]
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
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
Enumerable#partition is a method that returns two arrays.
The first array is everything the block evaluated to true, and the second array contains everything that was false.
numbers = [2, 95, 24, 27, 85, 4]
even, odd = numbers.partition { |num| num.even? }
=> [[2, 24, 4], [95, 27, 85]]
even
=> [2, 24, 4]
odd
=> [95, 27, 85]
In Ruby you can use the rindex
method on an array to return the last index the value argument is stored at. For Example:
arr= ['b', 'a', 'a', 'a']
arr.rindex('a')
#=> 3
arr.rindex('b')
#=> 0
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.
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.
If for whatever reason you wanted to modify something inside nil
, the class name is NilClass
class Nil
def to_i
-1
end
end
class NilClass
def to_i
-1
end
end
In Ruby there is this neat little method String#start_with? that takes in as many prefixes as you want, and returns true if the string starts with any of them.
greeting = "hello there"
greeting.start_with?("general", "kenobi") # => false
greeting.start_with?("heaven", "hell") # => true
Side notes:
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.
Hash#invert
returns a new Hash object with the each key-value pair inverted:
{first: 1, last: 2}.invert
=> {
1 => :first,
2 => :last
}
{first: 1, last: 2}.invert[2]
=> :last
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"
You can split a string on the first occurrence of pattern by setting the limit to 2
"I am, a, string, of, many, many, commas".split(",", 2)
=> ["I am", " a, string, of, many, many, commas"]
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
You can conditionlly eagerload relationships in graphql-ruby
by looking at look aheads:
module Resolvers
class Customer < Resolvers::BaseResolver
extras [:lookahead]
argument :id, ID, required: true
def resolve(lookahead:, id:)
scope = ::Customer
if lookahead.selects?(:orders)
scope = scope.includes(:orders)
end
scope.find_by(id)
end
end
end
You can map keys by giving a map of new keys:
data = {
isAdmin: true,
firstName: "Luke",
lastName: "Walker"
}
data.transform_keys({
isAdmin: :admin?,
firstName: :first_name,
lastName: :last_name
})
=> {
:admin? => true,
:first_name => "Luke",
:last_ame => "Walker"
}
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)
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
You can get the terminal size with IO
class' #winsize
method
require "io/console"
p IO.console.winsize
=> [26, 46]
IO.console
is just the file /dev/tty
https://ruby-doc.org/stdlib-3.1.1/libdoc/io/console/rdoc/IO.html#method-i-winsize
Ruby's Array#first
will have very different return types based on what arguments you provide. Without an argument, it will return nil
or object
. With an argument, it will always return a new array; even if your argument is 1
or 0
.
a = [:foo, 'bar', 2]
a.first
# => :foo
a.first(1)
# => [:foo]
a.first(0)
# => []
With an empty array:
[].first
# => nil
[].first(1)
# => []
[].first(0)
# => []
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"