Today I Learned

A Hashrocket project

Pretty Print JSON responses from `curl` - Part 2

After posting my last TIL , Vinicius showed me another tool that goes beyond just pretty printing: jq

If you don’t pass any args to jq it will just pretty print same as json_pp:

> curl 'https://til.hashrocket.com/api/developer_posts.json?username=gabrielreis' | jq

{
  "data": {
    "posts": [
      {
        "title": "Pretty Print JSON responses from `curl`",
        "slug": "pgyjvtuwba"
      },
      {
        "title": "Display line break content in React with just CSS",
        "slug": "mmzlajavna"
      },
      {
        "title": "Mutations with the graphql-client Ruby gem",
        "slug": "xej7xtsnit"
      }
    ]
  }
}

What if you only want to display the first post on the response? Just pass an argument to filter the keys you want. It’s like Xpath for JSON: jq '.data.posts[0]'

> curl 'https://til.hashrocket.com/api/developer_posts.json?username=gabrielreis' | jq '.data.posts[0]'

{
  "title": "Pretty Print JSON responses from `curl`",
  "slug": "pgyjvtuwba"
}

Pretty Print JSON responses from `curl`

When you use curl to manually make API calls, sometimes the response is not formatted:

> curl 'https://til.hashrocket.com/api/developer_posts.json?username=gabrielreis'`

{"data":{"posts":[{"title":"Display line break content in React with just CSS","slug":"mmzlajavna"},{"title":"Mutations with the graphql-client Ruby gem","slug":"xej7xtsnit"},{"title":"The rest of keyword arguments 🍕","slug":"o2wiclcyjf"}]}}%

You can pipe json_pp at the end so you have a prettier json response:

> curl 'https://til.hashrocket.com/api/developer_posts.json?username=gabrielreis' | json_pp

{
   "data" : {
      "posts" : [
         {
            "slug" : "mmzlajavna",
            "title" : "Display line break content in React with just CSS"
         },
         {
            "title" : "Mutations with the graphql-client Ruby gem",
            "slug" : "xej7xtsnit"
         },
         {
            "title" : "The rest of keyword arguments 🍕",
            "slug" : "o2wiclcyjf"
         }
      ]
   }
}

See Part 2

Modifying A String With blit_string

ReasonML’s Bytes module has a function called blit_string. This function allows you to copy portions of a string into a destination byte sequence. It is a fairly low-level operation, so you have to provide a source string and provide an offset of that source string to start copying from. You then have to provide a properly sized byte sequence as well as the destination’s starting offset and length of bytes to be copied.

Here is an example of how we can use blit_string to create a copy of the string with the first character removed.

let remove_first_char = (str: string): string => {
  let copy_len = String.length(str) - 1;
  let dst = Bytes.create(copy_len);
  Bytes.blit_string(str, 1, dst, 0, copy_len);
  Bytes.to_string(dst);
};

Notice that once the byte sequence has been copied over, we then need to convert it back into a string.

Generate A Native ReasonML Project With Pesy

Pesy is a CLI utility available from NPM that you can use to generate a ReasonML project that is ready for native compilation. It uses esy for the management of opam packages. It uses Dune for building your library code with the ReasonML and OCaml dependencies.

Assuming you already have pesy installed globally, create a directory for your project and then run:

$ pesy

A project will be generated that is out-of-the-box ready to compile native executables.

Using Optional Labeled Function Args In ReasonML

If you are constructing a function that takes some arguments, but one of those arguments has a reasonable default value, then you can use an optional labeled argument. Labeled arguments are those arguments prefixed with a ~. If you give the argument a default value, then it becomes optional.

let thing = (~a=1, b: int, c: int) => {
  a + b + c;
};

In this case ~a is a labeled argument. It is also optional and will default to 1 if not specified. The other two arguments, b and c, are positional arguments and thus required in order for the function to evaluate.

Here are two ways of using this function either by specifying ~a or excluding it so that it defaults to 1.

thing(~a=2, 1, 1)
|> string_of_int
|> print_endline /* 4 */

thing(1, 1)
|> string_of_int
|> print_endline /* 3 */

See more details here.

Serialize an Elixir pid

I was wondering how to serialize/deserialize a pid for some tricks with Ecto Sandbox and I found out some nice code on phoenix_ecto code. Here it goes my version:

defmodule MyApp.Serializer do
  @spec serialize(term) :: binary
  def serialize(term) do
    term
    |> :erlang.term_to_binary()
    |> Base.url_encode64()
  end

  @spec deserialize(binary) :: term
  def deserialize(str) when is_binary(str) do
    str
    |> Base.url_decode64!()
    |> :erlang.binary_to_term()
  end
end

And the test:

defmodule MyApp.SerializerTest do
  use ExUnit.Case, async: true

  alias MyApp.Serializer

  describe "serialize/1" do
    test "serializes a pid" do
      pid = self()
      assert pid |> Serializer.serialize() |> is_binary()
    end

    test "serializes a map" do
      map = %{foo: :bar}
      assert map |> Serializer.serialize() |> is_binary()
    end
  end

  describe "serialize/1, deserialize/1" do
    test "serializes and deserializes a pid" do
      pid = self()
      assert pid |> Serializer.serialize() |> Serializer.deserialize() == pid
    end

    test "serializes and deserializes a map" do
      map = %{foo: :bar}
      assert map |> Serializer.serialize() |> Serializer.deserialize() == map
    end
  end
end

Using vim-surround With A Visual Selection

The vim-surround plugin allows you to do a variety of actions that have to do with the surrounding characters of text objects.

The S keystroke allows you to surround a visual selection with the following character.

First, make a visual selection. Then hit S. Then hit a surround character such as ( or [ and the area of text that has been visually selected will be wrapped with the respective surround characters.

Duplicate The Current Tab In Chrome

Sometimes when viewing a page in Chrome, you realize you want to keep that page open but also go back to the previous page to view something else. An easy way of achieving this is to duplicate the current tab and then go back.

To duplicate the current tab hit Cmd+Enter while the focus is on the URL bar.

If the URL bar is not in focus, then first hit Cmd+L to focus followed by Cmd+Enter.

Two Ways To Find An Item In A List With ReasonML

The List module has the typical find function that you’d expect any enumerable type to include. It has a very similar find_opt function as well. The difference is in the return types.

When using List.find you’ll have to deal with the possibility of a Not_found exception.

switch (List.find(item => item.id == id, my_list)) {
| exception Not_found => print_endline("Not found!")
| item => print_endline("Found it: " ++ item.name)
}

The List.find_opt function has a more familiar interface that doesn’t require you to know the details of what exceptions could arise. All you want to know is if it was found or not. This is achieved by having an option('a) return type.

switch (List.find_opt(item => item.id == id, my_list)) {
| None => print_endline("Not found!")
| Some(item) => print_endline("Found it: " ++ item.name)
}

See the List module for more details.

Style A Background With CSS Linear Gradient

The linear-gradient function in its simplest form can be used to style the background of an element with a vertical, linear gradient between two colors.

gradient example See the Pen pQpypW by Josh Branchaud (@jbranchaud) on CodePen.

Here is what the CSS looks like:

.container {
  background: linear-gradient(#00449e, #e66465);
}

The background of any element with the container class will be styled with a linear gradient that transitions from #00449e to #e66465.

Check The Password Confirmation With Yup

The Yup library makes it easy to validate individual values in a JavaScript object. A common situation when implementing a Sign Up form is asking the user to input their password twice and then the app can make sure they match.

To do this, we need the validation of our passwordConfirmation value to reach outside of itself to make a comparison with the password value. This can be done with Yup’s ref function.

import * as Yup from 'yup';

validationSchema: Yup.object({
  password: Yup.string().required('Password is required'),
  passwordConfirmation: Yup.string()
     .oneOf([Yup.ref('password'), null], 'Passwords must match')
});

We are able to reference the value of password with ref. We use the oneOf function to ensure that passwordConfirmation either matches password or if it is left blank and matches null then it passes the validation for the time being. The second argument to oneOf is a custom validation message when this validation fails.

source

Using BCrypt To Create And Check Hashed Passwords

The BCrypt library is used under the hood by gems like Devise in order to work with passwords securely. You can use it to salt and hash a plain text password. You can also use it to check whether an encrypted password matches some input password.

> include BCrypt
=> Object

> encrypted_pass = Password.create('password')
=> "$2a$10$te3Y8wdSXf8/gWDeSP5z9eut7alThnuTvq1SvgQyJ1C57F.qit1uq"

> Password.new(encrypted_pass) == "not_my_pass"
=> false

> Password.new(encrypted_pass) == "password"
=> true

The Password.create method will salt and hash the given password. The resulting encrypted password, if it is an instance of Password, can be directly compared to a string. For good measure, in case the encrypted password is a string, you can wrap it in a call to Password.new to ensure you are working with a Password instance.

Generate Ruby Version and Gemset Files With RVM

RVM, the ruby version manager, is a fairly flexible tool that supports a number of workflows. The rvm utility can be used to generate both a .ruby-version file and a .ruby-gemset file for a given project.

$ rvm --ruby-version use 2.5.3@my_project

This will generate a .ruby-version file in your current project directory that points RVM to the 2.5.3 version of Ruby. It will also create a .ruby-gemset file that RVM will use for managing this project’s gemset.

Get Random Images From Unsplash

Here is an image from unsplash.com.

random images

I don’t know what image I am showing you though. That’s because the URL being sourced for the above image is https://source.unsplash.com/random/1200x600. This tells unsplash to randomly serve us a 1200x600 image. Try refreshing the page and you’ll see that it is different each time. Cool!

This is a bit of a novelty, but could be useful on occasion. The Gatsby docs use it to great effect in a tutorial so as to not get caught up with the details of serving a specific image.

Open Bitbucket PR's from the Command Line

I’m a fan of Git command line tools. The more familiar I become with Git hosting platform’s website, the more I want to control it with a CLI.

When you push a branch to Bitbucket, part of the response is a pull request URL. That URL is a create page for a pull request against the default branch.

I’ve taken this idea a step further and created a script for my project that visits this page:

#!/bin/sh

BRANCH=`git rev-parse --abbrev-ref HEAD`
open "http://bitbucket.org/<organization>/<project>/pull-requests/new?source=${BRANCH}&t=1#diff"

This script reads my current branch name and visits the Bitbucket ‘create PR’ page in a diff view.

Send an event to a Channel from outside Phoenix

So this one was non-obvious to me.

In the following example, any module on any process can call TopicChannel.send_to_channel/1and that will be handled by the handle_info call below and sent to the socket.

defmodule MyAppWeb.TopicChannel do
  use Phoenix.Channel

  def send_to_channel(data) do
    Phoenix.PubSub.broadcast(
      MyApp.PubSub, 
      "topic:subtopic",
      %{type: "action", payload: %{data: data}
    )
  end

  def join("topic:subtopic", message, socket) do
    {:ok, socket}
  end
  
  def handle_info(%{type: "action"}=info, socket) do
    push socket, "action", info

    {:noreply, socket}
  end
end

Easy Date Comparison With DayJS

Let’s say my application fetches dates from the server which come back in string form as "YYYY-MM-DD" and I’d like to know if those dates already passed. This can be done easily by wrapping dates in DayJS and using its comparison functions.

import dayjs from 'dayjs';

const today = dayjs(new Date());
const pastDate = dayjs("2018-10-22");
const futureDate = dayjs("2022-01-01");

console.log(pastDate.isBefore(today));
// => true
console.log(futureDate.isBefore(today));
// => false

The dayjs() function can be used to construct DayJS date objects from Date objects and strings. These can then be compared with functions like isBefore() and isAfter().

Make A Block Of Text Respect A New Line

Generally when we fill a div tag full of text, it will display it one long strand irrespective of any line breaks that are included. This is a great default, but not necessarily what we want when we are displaying text from another source, such as our users.

We can convince a block of text to respect new lines by adding a couple CSS properties.

.multiline-text {
  word-wrap: break-word;
  white-space: pre-line;
}

The first rule, word-wrap: break-word, ensures that long lines of text uninterrupted by new lines respect the boundaries of our wrapping element. The second rule, white-space: pre-line, handles squashing of extra white space and respecting of new lines.

See a working example here.

Work with a Gist locally

I like Gists, those fun-size Git repositories I fill with coding demos, scripts, and WIP markdown files. Today I learned you can edit these files locally and push them to a remote, just like any Git repo.

Grab your Gist URL:

https://gist.github.com/yourhandle/5bac61ee3fe0083

Alter it slightly, and clone:

$ git clone git@gist.github.com/5c61ee3fe0083.git
$ cd 5c61ee3fe0083/

Make changes, commit, and push away. Your commits will show up under the /revisions tab of your Gist.

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

This builds the sql:

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

Good to go!

Pass a Block on Console Load

Have some Ruby code you want to run as the Rails console loads? Here’s a technique.

In this example, I’m going to use a function from the hirb gem. Add the gem to your Gemfile and bundle.

Next, pass a block to console in your application configuration.

# config/application.rb

module MyApplication
  class Application < Rails::Application
    console do
      # Action(s) I want to run on console load...
      Hirb.enable
    end
  end
end

The next time you start the Rails console, Hirb.enable will run after load.

code / docs

The Outline Property 🖼

Today I learned about the CSS outline property.

Outlines are like borders, except they never take up space, because they’re drawn outside of an element’s content.

Some browsers have a default outline style for interactive elements (like buttons) on focus. This was how I came to learn about the property, because that style clashed visually with an element I was building.

Remove such an outline with either of these settings:

button:focus {
  outline: 0;
  outline: none;
}

Note that if an element can be interacted with, a11y best practices require a visible focus indicator. Provide obvious focus styling if the default focus style is removed.

Formik's Validation Schema As A Function

The most straightforward way to use Formik’s validationSchema is to provide it with a Yup object defining your form’s validations.

const MyComponent = withFormik({
  // ...

  validationSchema: yup.object().shape({
    email: yup.string().required(),
    feedback: yup.string().required(),
  }),
  
  // ...
})(MyForm);

There may be a point at which you need access to the props being passed to MyComponent in order to construct the proper set of validations. Formik supports this by allowing validationSchema to be a function.

const MyComponent = withFormik({
  // ...

  validationSchema: (props) => {
    let emailSchema;
    if(props.allowAnonymous) {
      emailSchema = yup.string();
    } else {
      emailSchema = yup.string().required();
    }

    return yup.object().shape({
      email: emailSchema,
      feedback: yup.string().required(),
    });
  },
  
  // ...
})(MyForm);

When validationSchema is a function, its first argument is the set of props passed to that component.

Convert A String To A Timestamp In PostgreSQL

If you have a string that represents a point in time, there are a couple ways that you can convert it to a PostgreSQL timestamptz value.

If the string is in ISO 8601 format, then it can be simply cast to timestamptz.

> select '2018-10-24'::timestamptz;
      timestamptz
------------------------
 2018-10-24 00:00:00-05

A more general purpose approach is to use the to_timestamp function.

> select to_timestamp('2018-10-24', 'YYYY-MM-DD');
      to_timestamp
------------------------
 2018-10-24 00:00:00-05

The first argument is our string-to-be-converted in whatever format. The second argument is another string describing in what format that string is.

Note: Both of these approaches produce a timestamptz value.

Open devtools when running a Selenium Chrome test

The Network tab in Chrome devtools doesn’t record requests unless devtools is open. This makes debugging specific issues in tests much harder. It’s great being able to see which api requests were made and what payloads they returned.

You can start Chrome with devtools open though with the the chrome option --auto-open-devtools-for-tabs.

If you are using Selenium with Chrome in a Ruby integration test, you can pass the option

opts = {
  browser: :chrome,
  options: Selenium::WebDriver::Chrome::Options.new(
  args: %w(--auto-open-devtools-for-tabs --window-size=2400,2400)
)
}

Capybara.register_driver :chrome do |app|
  Capybara::Selenium::Driver.new(app, opts)
end

Opening devtools automatically may restrict your window size enough to disrupt some of your tests in which case you can set -window-size to a value that accomodates your website.

Don't rerender if nothing changed in React 16.6.0!

React 16.6.0 came out today and React now provides a handy function to create a component that won’t rerender if it doesn’t get new props, React.memo.


const BlueComponent = () => {
  return <div>no props don't rerender</div>;
}

const MemoComponent = React.memo(BlueComponent);

BlueComponent is a component that will re-render every time it’s parent re-renders. It doesn’t take props though, so it won’t look any different based on new props. MemoComponent is a component created by passing BlueComponent to React.memo. It will not re-render when it’s parent re-renders.

Check out another example in the code sandbox below.

Edit znw4wjn914

Read more about React 16.6.0 here.

Compile ReasonML With An OCaml Package Using Dune

In Compile Reason To Native With Dune, I showed how to compile a basic ReasonML file as a native executable using Dune.

Any non-trivial program will likely involve pulling in an OCaml dependency. For example, you may want to pull in Lwt. Assuming this package is available, whether you’ve manually downloaded it via opam or used something like esy, you’ll want to let Dune know that Lwt is an available library.

;; dune
(executable
 (name hello_reason)
 (libraries lwt lwt.unix))

The modules in the Lwt package will now be globally available to your Reason code.

let () = {
  Lwt_main.run(
    Lwt_io.printf("Hello, Reason!\n")
  );
};

When Dune builds your code, it will include and compile Lwt.

See a full example here.

Start Zoom on Mute 📣

We’ve been using Zoom for a while now, and it’s proving to be an excellent video conferencing tool.

One setting I recommend enabling is ‘Always mute microphone when joining meeting’, under the ‘Audio’ settings. I’ve been using it for a week and love it. Pausing work in an open office to join a conference call can be a messy affair, and this setting helps me control how I enter the conversation (silently).

Show What Is In A Git Stash

Usually when I want to inspect anything in git, I’ll use git show with a specific ref. This can even be done with stash refs.

$ git stash list
stash@{0}: WIP on ...
stash@{1}: Some commit on ...

$ git show stash@{0}
# ...

The git-stash command has a built-in way of showing stashes that will save you from having to type out the somewhat awkward stash@{n} ref.

$ git stash show 1

This will show you the stash@{1} ref. You can also omit a number which will show you the latest stash (stash@{0}).

See man git-stash for more details.

Resize Tmux Pane 🖥

Sometimes, after a long day of coding, I resize a Tmux pane using my mouse instead of my keyboard. It’s a habit from my GUI-informed past.

Here’s how to accomplish this without a mouse.

To resize the focused pane left one cell (from the Tmux prompt):

:resize-pane -L

Resize pane number 3 right 10 cells:

:resize-pane -t 3 -R 10

Etc.

Jump Back To The Latest Jump Position In Vim

Any sort of jump motion, such as gg or 121G, will be recorded in your Vim session’s jump list. If you’d like to return to the latest position in your jump list, you can tap '' within normal mode.

You can then tap '' again and you’ll be returned to the position you were just at. This is great if you want to toggle back and forth between your current position and the previous position.

If the latest jump position is not within the current buffer, then you will be jumped to the initial cursor position at the top of the file.

You can learn more about this by reference :help jump-motions, :help jumplist, and :help ''.

h/t Jake Worth

Jump Back 🐇

A Vim motion I’ve really been loving lately is '' or ``, which returns you to the previous jump position. It references the previous jump, so using it twice in a row will toggle you back and forth between two locations.

I use this a lot after jumping to the next occurance of a word with *, and then changing that word in some way so I can’t return with *. If you’re using normal mode most of the time, this command can help you walk back through your thought process.

For more info:

:help ''

Compile ReasonML To Native With Dune

Dune is “a composable build system for OCaml” with out-of-the-box support for ReasonML. Dune can be used for a lot of things, but in simplest terms it can be used to compile ReasonML programs into native executables.

Considering the following ReasonML program.

/* hello_reason.re */
print_endline("Hello, Reason!")

We can then create the following Dune build file.

;; dune
(executable
 (name hello_reason))

If we then run dune build hello_reason.exe, then Dune will compile the hello_reason.re into a hello_reason.exe executable that can be found in build/default. Run it and see the output.

Read more in the Quickstart guide.

ES6 Nested Destructuring

I picked this up a while ago on Twitter, use it all the time in my React code, and wanted to document it here. If you’re destructuring a JS object like this:

const { username, zip } = props.user;

A valid (and slightly better, in my opinion) refactor is this:

const { user : { username, zip } } = props;

As you add more destructured variables pulled from props, the object you’re referencing stays the same.

Note that this doesn’t define user— if that’s something you want, you have to destructure it on its own:

const { user, user : { username, zip } } = props;

While a little more verbose, I still think this makes for more maintainable destructuring long-term.

Doing Date Math In MySQL

MySQL has an array of functions for interacting with date and datetime values. If you’d like to do math with a date to compute a date in the future or the past, you can use the DATE_ADD() and DATE_SUB() functions.

mysql> select now() Now, date_add(now(), interval 10 minute) '10 Minutes Later';
+---------------------+---------------------+
| Now                 | 10 Minutes Later    |
+---------------------+---------------------+
| 2018-10-18 15:53:29 | 2018-10-18 16:03:29 |
+---------------------+---------------------+

mysql> select now() Now, date_sub(now(), interval 9 day) '9 Days Earlier';
+---------------------+---------------------+
| Now                 | 9 Days Earlier      |
+---------------------+---------------------+
| 2018-10-18 15:54:01 | 2018-10-09 15:54:01 |
+---------------------+---------------------+

There are equivalent ADDDATE() and SUBDATE() functions if you prefer.

Check out the docs for more details.

Get The Time Zone Of The Client Computer

The resolvedOptions function on Intl.DateTimeFormat.prototype provides a number of pieces of information about the client computer. It includes information such as the locale and the numbering system. It also has the time zone for that machine.

Try running this line of JavaScript in your own browser.

$ Intl.DateTimeFormat().resolvedOptions().timeZone

When I run it, I get America/Chicago.

You can use this within your client-side code as a way of determining in which time zone your users are.

Catching errors in React (16 and up)

If an error is thrown while rendering React, React unmounts the entire tree.

In production, this might not be behaviour you want. The behaviour might be inconsequential to the user’s current path and why stop the user cold due to an unanticipated state?

React provides a function componentDidCatch to help manage exceptions and keep the consequence of the error localized to a specific part of your component tree.

This blog post describes the concept of an ErrorBoundary which can look like this:

class ErrorBoundary extends Component {
  componentDidCatch(error, {componentStack}) {
    console.log("error", error)
    console.log("componentStack", componentStack)
  }

  render() {
    return this.props.children;
  }  
}

Using the componentDidCatch lifecycle function this component will catch any error thrown by its children. If an error is thrown it will not render and none of it’s children will render, but all components in different sections of the component tree will render.

The second argumunent to componentDidCatch is an object containing a key called componentStack which is a nice stack provided by React.

H/T Josh Branchaud

Change Prompt in Z Shell

When I live code, or share terminal commands in a demonstration, I don’t want my customized terminal prompt included in that information. It’s noisy.

Right now I’m changing this in Z Shell via that PROMPT variable.

# Complex prompt
jake@computer-name: echo $PROMPT
%{%}%n@%m%{%}:

# Simple prompt
jake@computer-name: PROMPT="$ "
$ echo 'ready to live code'
ready to live code
$

Go Tests Uncached

Go caches test results, and this can be a problem if you have flickers, are benchmarking your tests, or just want to run all the tests every time.

Disable caching with GOCACHE:

$ go test ./...
ok      monkey/ast      (cached)
ok      monkey/lexer    (cached)
ok      monkey/parser   (cached)
$ 
$ GOCACHE=off go test ./...
ok      monkey/ast      0.005s
ok      monkey/lexer    0.005s
ok      monkey/parser   0.005s

Spreading nil Into a Ruby Array

Spreading nil into an array doesn’t add an entry into a Ruby array:

[*nil] # => []
a = [1, 2, 3]
[*nil, *a] # => [1, 2, 3]

One might expect this to insert nil in the array, but this isn’t the case. This feature could be useful when passing dynamically assigned, nullable items into an array.

def foo(arg)
  [*arg]
end

nullable_var = params[:foo]
foo(nullable_var).each { |x| act_on(x) } # => [] if nullable_var is nil

Outer join with ActiveRecord `references` method

I want to join posts to comments regardless if comments exist or not.

You can use includes for that:

Post.includes(:comments).all

But that results in 2 queries, one to get all the posts, and one to get all comments that have the relevant post_id.

With references you can turn this into an outer join:

Post.includes(:comments).references(:comments).all

Now we’re getting all the information we need with just 1 query.

Check out the Active Record guides here