v2.1.0

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.