Today I Learned

hashrocket A Hashrocket project

48 posts by marylee twitter @marybethlee11

Echo substrings of a variable

Say you have a variable set in a shell script:

export testPath='/Users/mary/code/thingy.rb'
Bash

You can echo that variable as usual:

echo $testPath
# /Users/mary/code/thingy.rb
Bash

But you can also get substrings of that variable as you echo it, using substring extraction. The substring extraction syntax is ${variable:offset:length}

echo ${testPath:11}
# /code/thingy.rb

echo ${testPath:12:4}
# code
Bash

You can also change the prefix (${variable#prefix}) and suffix (${variable%suffix}):

echo ${testPath#/Users/mary/code/}
# thingy.rb

echo ${testPath%.rb}
# /Users/mary/code/thingy
Bash

Build rows from jsonb data in PostgreSQL

PostgreSQL has a function called jsonb_to_recordset that will return your jsonb data as if it were rows in the database.

Say we have the following example table:

create table notes (title varchar, bullets jsonb);

insert into notes (title, bullets)
values ('Inspection', '[{"field": "Tires", "measured": "Tread Height", "value": "3mm"},{"field": "Brakes", "measured": "Caliper Boot", "value": "cracked"}]');
SQL

To use the jsonb_to_recordset function, we can do the following:

select title, field, measured, value
from notes
cross join lateral jsonb_to_recordset(notes.bullets) 
as temp(field varchar, measured varchar, value varchar);
SQL

The function gives us the following output:

   title    | field  |   measured   |  value  
------------+--------+--------------+---------
 Inspection | Tires  | Tread Height | 3mm
 Inspection | Brakes | Caliper Boot | cracked

ActiveRecord.invert_where

Ever been debugging in ActiveRecord and wanted to inverse a query? You can do that without changing the whole query!

User.where(active: true)
# => "select * from users where active = 'true'"

User.where(active: true).invert_where
# => "select * from users where active != 'true'"
Ruby

It also works with multiple conditions:

User.where(active: true, subscribed: false)
# => "select * from users where active = 'true' and subscribed = 'false'"

User.where(active: true, subscribed: false).invert_where
# => "select * from users where active != 'true' and subscribed != 'false'"
Ruby

It works on scopes, too:

class User < ActiveRecord::Base
  scope :active, -> { where(active: true) }
end

User.active.invert_where
# => "select * from users where active != 'true'"
Ruby

Ensure you're always using the same connection

In Rails, if you want to ensure that you are using the same database connection from the connection pool to execute a number of commands, you can use the ActiveRecord::Base.with_connection method.

This method yields a single connection from the pool for you to execute your commands against:

ActiveRecord::Base.with_connection do |conn|
  conn.execute("select * from sessions")
end
Ruby

It is important to note that the connection yielded is taken out of the pool until the processing in the block is complete.

Postgres comparison with null values

When Postgres is running comparisons, any null values will yield null at the end of the comparison. This is because the null is an unknown value that Postgres can't run the comparison against.

Take the following simple example of a users table and query:

create table users (name text, email text);

insert into users (name, email)
  values ('Joe', 'joe@hashrocket.com'),
  ('Rick', null);
  
select * from users where email not like '%gmail.com';
--  name |       email
-- ------+--------------------
--  Joe  | joe@hashrocket.com
-- (1 row)
SQL

You'll notice that the Rick user is not returned in the results.

If you want rows with the null value included in your results, you can coalesce the column to an empty string. This allows Postgres to run the comparison against two known values and return the rows with the null values.

select * from users where coalesce(email, '') not like '%gmail.com';
--  name |       email
-- ------+--------------------
--  Joe  | joe@hashrocket.com
--  Rick | ΓΈ
-- (2 rows)
SQL

Specify behavior for nulls in a unique index

Postgres 15 gave us the ability to specify how we want null values to be treated when dealing with unique indexes.

By default, nulls are considered unique values in Postgres:

create table users (name text, email text unique);
-- CREATE TABLE
insert into users values ('Joe', null), ('Jane', null);
-- INSERT 0 2
SQL

This default behavior can also be explicitly set using the nulls distinct clause:

create table users (name text, email text unique nulls distinct);
-- CREATE TABLE
insert into users values ('Joe', null), ('Jane', null);
-- INSERT 0 2
SQL

To change the default behavior and prevent nulls from being considered unique values, you can use the nulls not distinct clause:

create table users (name text, email text unique nulls not distinct);
-- CREATE TABLE
insert into users values ('Joe', null), ('Jane', null);
-- ERROR:  duplicate key value violates unique constraint "users_email_key"
-- DETAIL:  Key (email)=(null) already exists.
SQL

See this change in the Postgres 15 release notes

ActiveRecord allows you to view saved changes

Say you have an Activerecord object that you are making changes to.

user = User.last
user.update(first_name: "Joe", last_name: "Hashrocket")
Ruby

With ActiveModel's Dirty module, you can view the changes made to the model even after it has been saved using the saved_changes method

user.saved_changes
# {"first_name"=>[nil, "Joe"], "last_name"=>[nil, "Hashrocket"]]}
Ruby

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: ""}
  ]
]
Ruby

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
Ruby

This code essentially boils down to:

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

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
Ruby

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"
Ruby

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"
Ruby

Rails scopes might just return all resources

ActiveRecord's scopes are meant to be composable and intended to only ever return an ActiveRecord relation.

If you make a mistake with your scope and have it return something like a nil or false, Rails will return all records for that class in order to maintain composability.

If you are intentionally writing something that might return an empty value, use a class method rather than adding a scope in order to prevent bugs

Rename a local git branch

Ever wanted to rename a local git branch? The git branch -m command is your friend

Want to rename a branch that's not currently checked out?

git branch -m <original_name> <new_name>
Bash

Or to rename your current local branch, you can exclude the original name in the git branch args:

git branch -m <new_name>
Bash

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
Ruby

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"
Ruby

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>
Ruby

Append items to an array

Today I came across yet another way to add items to an array in ruby

I already knew about push and <<, but did you know that there's also an append?

[1,2,3].push(4)
# => [1,2,3,4]

[1,2,3] << 4
# => [1,2,3,4]

[1,2,3].append(4)
# => [1,2,3,4]
Ruby

append is ultimately just an alias for push, but always good to know!

Postgres regex matching with squiggles

Postgres supports POSIX regex pattern matching using the squiggle (~) operator

-- Check for a match
select 'wibble' ~ 'ubb';
-- returns false
select 'wibble' ~ 'ibb';
-- returns true

-- Case insensitive match checking
select 'wibble' ~ 'IBB';
-- returns false
select 'wibble' ~* 'IBB';
-- returns true

-- Check for no match
select 'wibble' !~ 'ibb';
-- returns false
select 'wibble' !~ 'ubb';
-- returns true
SQL

Full postgres pattern matching documentation can be found here

Find the position of a substring in postgres

Postgres has a strpos function for finding the position of a substring in a given string:

select strpos('wibble', 'ibb');
SQL

The function returns an integer representing the location of the substring in the provided string, or a 0 if the substring cannot be found in the provided string (the locations are 1-based indexes, so you don't have to worry about collisions!).

Set JSON.parse returned object and array classes

By default, the Ruby JSON.parse method returns a ruby Hash for any json object, and a ruby Array for any json array.

However, you can customize the returned object classes using the object_class and array_class options:

source = JSON.dump({ wibble: "wubble", data: [1,2,3] })

result = JSON.parse(
  source, 
  object_class: OpenStruct,
  array_class: Set
)
# => #<OpenStruct wibble="wubble", data=#<Set: {1, 2, 3}>>

result.data # => #<Set: {1, 2, 3}>
result.wibble # => "wubble"
Ruby

There's a "whereami" alias in Pry

Since version 0.10.0, the pry gem has shipped with a built in alias for whereami: the extremely convenient @

Other useful aliases can be found using the help command:

[1] pry(main)> help

Aliases
  !!!                Alias for `exit-program`
  !!@                Alias for `exit-all`
  $                  Alias for `show-source`
  ?                  Alias for `show-doc`
  @                  Alias for `whereami`
  clipit             Alias for `gist --clip`
  file-mode          Alias for `shell-mode`
  history            Alias for `hist`
  quit               Alias for `exit`
  quit-program       Alias for `exit-program`
  reload-method      Alias for `reload-code`
  show-method        Alias for `show-source`
Bash

Check for members in Ruby

Ruby's Enumerable class has a member? method that returns a boolean.

For arrays, the method checks if the supplied value is included (similar to ['a'].include?('a')):

[:a, :b].member?(:b) # => true
[:a, :b].member?(:c) # => false
Ruby

For hashes, the method checks if the supplied value is included in the keys for the hash:

{ a: 'b' }.member?(:a) # => true
{ a: 'b' }.member?(:c) # => false
Ruby

Rails has an Array#exclude? method

In my vendetta against the unless expression in ruby, I came across a use case where I wanted to execute code only if an item was missing from an array.

I could easily do:

unless ['a', 'b', 'c'].include?('d')
  # do a thing
end
Ruby

But I wanted to replace the unless with an if, which led me to wonder if there was an exclude? method for arrays in ruby.

Turns out, ActiveSupport extended the Enumerable class to introduce exactly what I was looking for!

if ['a', 'b', 'c'].exclude?('d')
  # do a thing
end
Ruby

Hooray!

Swap between STI classes in Rails

Rails allows you to swap between single table inheritance classes with the becomes method. This method creates a new instance of the desired class, and passes the attributes from the original record to the new instance.

So if I have two classes, User and Admin, where Admin inherits from User:

class User
  # ...
end

class Admin < User
  # ...
end
Ruby

I can change between the two classes using becomes:

user = User.first # returns an instance of the User class
user.class # => User
user.id # => 1
user.name # => Joe Hashrocket

admin = user.becomes(Admin) # returns an instance of the Admin class
admin.class # => Admin
admin.id # => 1
admin.name # => Joe Hashrocket
Ruby

Remove newlines from strings in Rails

Rails has a method called squish that will remove newlines from strings. It works very nicely with heredocs, where you may want readability but don't really want the newlines.

Without squish:

<<~SQL
  update posts
  set status = 'public'
  where status is null;
SQL
# "update posts\nset status = 'public'\nwhere status is null;\n"
Ruby

With squish:

<<~SQL.squish
  update posts
  set status = 'public'
  where status is null;
SQL
# "update posts set status = 'public' where status is null;"
Ruby

PostgreSQL query with an array of regexes

I recently wanted to query my Postgres database by matching a column based on an array of regular expressions:

To query where the column matches all expressions in the array:

select * 
from my_table 
where my_column ilike all (array['%some%', '%words%'])
SQL

To query where the column matches at least one, but not necessarily all, of the expressions in the array:

select * 
from my_table 
where my_column ilike any (array['%some%', '%words%'])
SQL

Reversible integer to string migrations in Rails

I recently needed to convert an integer column in Rails to a string, and wanted to make sure that the migration would be reversible. I specified the up and down methods, but found that I couldn't reverse the migration because the column type couldn't be automatically cast back into an integer.

As it turns out, Rails allows us to specify how to cast the column with the using option:

def up
  change_column :users, :zip, :string
end

def down
  change_column :users, :zip, :integer, 
    using: "zip::integer"
end
Ruby

This builds the sql:

ALTER TABLE users 
ALTER COLUMN zip 
TYPE integer
USING zip::integer
SQL

Good to go!

How to check Rails migration statuses

You can check the status of your migrations in Rails by running rails db:migrate:status

This command returns your database name, as well as a list of all your migrations, with their name and status

database: my-database-dev

Status | Migration ID | Migration Name
--------------------------------------
  up   | 201803131234 | Create users
  up   | 201803201234 | Create blogs
 down  | 201804031234 | Create posts

Postgres locale settings are inherited from OS

By default, postgres inherits locale settings from operating system, which can greatly affect sort. Comparing linux and unix, both using the locale en_US.UTF-8, we see the following sort outputs:

Unix

select name from unnest(array['No one', 'None', ' Not']) name order by name;
name
--------
 Not
No one
None

Linux

select name from unnest(array['No one', 'None', ' Not']) name order by name;
name
--------
None
No one
 Not

You'll notice that on the linux system, whitespaces are ignored during sorting.

To get consistent behavior across operating systems, you can use the postgres provided C locale:

select name from unnest(array['No one', 'None', ' Not']) name order by name collate 'C';
name
--------
 Not
No one
None