V2.2: Upgrade to 2.1


These notes cover an upgrade from 2.0.0 to 2.1.0.

Update the app

  • Edit Gemfile and update the versions of the Hanami gems to "~> 2.1".

  • Add the following gems to Gemfile:

gem "hanami-assets", "~> 2.1"
gem "hanami-view", "~> 2.1"

group :development do
  gem "hanami-webconsole", "~> 2.1"
end

group :test do
  gem "capybara"
end
  • Add the following lines to .gitignore:
public/
node_modules/
spec/examples.txt
  • Update Guardfile to match the following:
group :server do
  guard "puma", port: ENV.fetch("HANAMI_PORT", 2300) do
    # Edit the following regular expression for your needs.
    # See: https://guides.hanamirb.org/app/code-reloading/
    watch(%r{^(app|config|lib|slices)([\/][^\/]+)*.(rb|erb|haml|slim)$}i)
  end
end
  • Create Procfile.dev and add the following:
web: bundle exec hanami server
assets: bundle exec hanami assets watch
  • Create bin/dev, mark it as executable, (chmod a+x) and add the following:
#!/usr/bin/env sh

if ! gem list foreman -i --silent; then
  echo "Installing foreman..."
  gem install foreman
fi

exec foreman start -f Procfile.dev "$@"
  • If you are using the default config/puma.rb, update it to match the following (note the addition and use of the puma_concurrency and puma_cluster_mode variables):
# frozen_string_literal: true

#
# Environment and port
#
port ENV.fetch("HANAMI_PORT", 2300)
environment ENV.fetch("HANAMI_ENV", "development")

#
# Threads within each Puma/Ruby process (aka worker)
#

# Configure the minimum and maximum number of threads to use to answer requests.
max_threads_count = ENV.fetch("HANAMI_MAX_THREADS", 5)
min_threads_count = ENV.fetch("HANAMI_MIN_THREADS") { max_threads_count }

threads min_threads_count, max_threads_count

#
# Workers (aka Puma/Ruby processes)
#

puma_concurrency = Integer(ENV.fetch("HANAMI_WEB_CONCURRENCY", 0))
puma_cluster_mode = puma_concurrency > 1

# How many worker (Puma/Ruby) processes to run.
# Typically this is set to the number of available cores.
workers puma_concurrency

#
# Cluster mode (aka multiple workers)
#

if puma_cluster_mode
  # Preload the application before starting the workers. Only in cluster mode.
  preload_app!

  # Code to run immediately before master process forks workers (once on boot).
  #
  # These hooks can block if necessary to wait for background operations unknown
  # to puma to finish before the process terminates. This can be used to close
  # any connections to remote servers (database, redis, …) that were opened when
  # preloading the code.
  before_fork do
    Hanami.shutdown
  end
end
  • Add the following line to spec/spec_helper.rb, underneath the require_relative "support/rspec":
require_relative "support/features"
  • Create spec/support/features.rb:
# frozen_string_literal: true

require "capybara/rspec"

Capybara.app = Hanami.app
  • If you wish, update spec/support/rspec.rb to include the following configs:
config.example_status_persistence_file_path = "spec/examples.txt"

# Uncomment this to enable warnings. This is recommended, but in some cases
# may be too noisy due to issues in dependencies.
# config.warnings = true

Add views

The steps below apply to each of your app and slices. The code examples use Bookshelf as the enclosing Ruby module; replace this with the relevant module for your app or slice.

  • Add a view.rb (to app/ or your slice):
# auto_register: false
# frozen_string_literal: true

require "hanami/view"

module Bookshelf
  class View < Hanami::View
  end
end
  • Add a views/helpers.rb (to app/ or your slice):
# auto_register: false
# frozen_string_literal: true

module Bookshelf
  module Views
    module Helpers
      # Add your view helpers here
    end
  end
end
  • Add a templates/layout/app.html.erb (to app/ or your slice):
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bookshelf</title>
    <%= favicon_tag %>
    <%= stylesheet_tag "app" %>
  </head>
  <body>
    <%= yield %>
    <%= javascript_tag "app" %>
  </body>
</html>
  • Create public/404.html with the following:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>The page you were looking for doesn’t exist (404)</title>
  <style>
    :root {
      --foreground-rgb: 0, 0, 0;
      --background-rgb: 255, 255, 255;
      --font-sans: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
    }

    @media (prefers-color-scheme: dark) {
      :root {
        --foreground-rgb: 255, 255, 255;
        --background-rgb: 0, 0, 0;
      }
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body,
    html {
      max-width: 100vw;
      overflow-x: hidden;
      font-size: 100%;
    }

    body {
      color: rgb(var(--foreground-rgb));
      background: rgb(var(--background-rgb));
      font-family: var(--font-sans);
      font-style: normal;
    }

    main {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      padding: 0 4vw;
    }

    .message {
      display: flex;
      gap: 1rem;
      flex-direction: column;
      text-align: center;
    }

    .message h1 {
      font-size: 2rem;
      font-weight: 500;
    }

    p {
      line-height: 1.6;
    }

    @media (prefers-color-scheme: dark) {
      html {
        color-scheme: dark;
      }
    }
  </style>
  </head>
  <body>
    <!-- This file lives in public/404.html -->
    <main>
      <div class="message">
        <h1>404</h1>
        <p>The page you were looking for doesn’t exist.</p>
      </div>
    </main>
  </body>
</html>
  • Create public/500.html with the following:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>We’re sorry, but something went wrong (500)</title>
  <style>
    :root {
      --foreground-rgb: 0, 0, 0;
      --background-rgb: 255, 255, 255;
      --font-sans: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
    }

    @media (prefers-color-scheme: dark) {
      :root {
        --foreground-rgb: 255, 255, 255;
        --background-rgb: 0, 0, 0;
      }
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body,
    html {
      max-width: 100vw;
      overflow-x: hidden;
      font-size: 100%;
    }

    body {
      color: rgb(var(--foreground-rgb));
      background: rgb(var(--background-rgb));
      font-family: var(--font-sans);
      font-style: normal;
    }

    main {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      padding: 0 4vw;
    }

    .message {
      display: flex;
      gap: 1rem;
      flex-direction: column;
      text-align: center;
    }

    .message h1 {
      font-size: 2rem;
      font-weight: 500;
    }

    p {
      line-height: 1.6;
    }

    @media (prefers-color-scheme: dark) {
      html {
        color-scheme: dark;
      }
    }
  </style>
  </head>
  <body>
    <!-- This file lives in public/500.html -->
    <main>
      <div class="message">
        <h1>500</h1>
        <p>We’re sorry, but something went wrong.</p>
      </div>
    </main>
  </body>
</html>

Add assets

  • Create package.json with the following:
{
  "name": "rc3fresh",
  "private": true,
  "type": "module",
  "dependencies": {
    "hanami-assets": "^2.1.0"
  }
}
  • Run npm install

  • Append the following to Procfile.dev:

assets: bundle exec hanami assets watch
  • Create config/assets.js with the following:
import * as assets from "hanami-assets";

await assets.run();

// To provide additional esbuild (https://esbuild.github.io) options, use the following:
//
// Read more at: https://guides.hanamirb.org/assets/overview/
//
// await assets.run({
//   esbuildOptionsFn: (args, esbuildOptions) => {
//     // Add to esbuildOptions here. Use `args.watch` as a condition for different options for
//     // compile vs watch.
//
//     return esbuildOptions;
//   }
// });
  • Create app/assets/css/app.css (or assets/css/app.css in your slice) with the following:
body {
  background-color: #fff;
  color: #000;
  font-family: sans-serif;
}
  • Create app/assets/js/app.js (or assets/js/app.js in your slice) with the following:
import "../css/app.css";
  • Create app/assets/images/favicon.ico (or assets/images/favicon.ico in your slice) with the contents of this file.