<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aestimo K.</title>
    <description>The latest articles on DEV Community by Aestimo K. (@iamaestimo).</description>
    <link>https://dev.to/iamaestimo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1040973%2F08cd90eb-156c-4014-a45e-1ebd4acc6e72.png</url>
      <title>DEV Community: Aestimo K.</title>
      <link>https://dev.to/iamaestimo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/iamaestimo"/>
    <language>en</language>
    <item>
      <title>How to Track Errors in Oban for Elixir Using AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Tue, 26 Nov 2024 11:14:49 +0000</pubDate>
      <link>https://dev.to/appsignal/how-to-track-errors-in-oban-for-elixir-using-appsignal-1p4o</link>
      <guid>https://dev.to/appsignal/how-to-track-errors-in-oban-for-elixir-using-appsignal-1p4o</guid>
      <description>&lt;p&gt;When developing an Elixir app, you'll often need to handle tasks in a way that does not interrupt the normal user request-response cycle.&lt;/p&gt;

&lt;p&gt;Tasks like sending emails are great examples of jobs that should be delegated to a capable background job processing service. In the Elixir ecosystem, Oban is one such background job processing library.&lt;/p&gt;

&lt;p&gt;In this article, we'll learn what Oban is, how it works, and how to instrument it using AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, I recommend you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AppSignal account - &lt;a href="https://appsignal.com/users/sign_up" rel="noopener noreferrer"&gt;you can sign up for a free trial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;An Elixir app. In my case, I am using a Phoenix LiveView app with a landing page where a user can subscribe to an email newsletter with a couple of background jobs to schedule and send out emails. You can go ahead and &lt;a href="https://github.com/iamaestimo/phoenix_liveview_email_subscription_app" rel="noopener noreferrer"&gt;fork the app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://docs.appsignal.com/elixir/installation.html" rel="noopener noreferrer"&gt;AppSignal package added and configured for your app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Elixir, Phoenix, and PostgreSQL installed on your local development environment.&lt;/li&gt;
&lt;li&gt;Some basic experience of working with Elixir and Phoenix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And with that, we can get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Oban
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sorentwo/oban" rel="noopener noreferrer"&gt;Oban&lt;/a&gt; is a stable and high-performing background job processing library for Elixir applications. Unlike other job processing systems that might require their own infrastructure to run background jobs, such as RabbitMQ and Sidekiq (which needs a key-value store like Redis), Oban uses your app's existing PostgreSQL or SQLite database.&lt;/p&gt;

&lt;p&gt;With Oban, you can define, enqueue, and process jobs in the background. Another important thing to note is that Oban jobs are persistent, which means that even if something unexpected happens in the server environment, jobs will be retried for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing and Configuring Oban in Elixir
&lt;/h2&gt;

&lt;p&gt;First, add Oban to your app's &lt;code&gt;mix.exs&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mix.exs&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;MixProject&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:oban&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.17"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;mix deps.get&lt;/code&gt; in the terminal to install the package and its dependencies.&lt;/p&gt;

&lt;p&gt;Before moving on, it's important to cover the database your app is using since it will determine how you configure Oban.&lt;/p&gt;

&lt;p&gt;If you've already configured PostgreSQL for your app, the Postgres package is likely already installed, and you'll not have to add it manually. However, if your app uses SQLite, you'll have to add the &lt;a href="https://hex.pm/packages/ecto_sqlite3" rel="noopener noreferrer"&gt;EctoSQLite3&lt;/a&gt; package to &lt;code&gt;mix.exs&lt;/code&gt; and configure it accordingly. In this article, the featured app uses PostgreSQL for the database, and Oban is installed to run on top of that.&lt;/p&gt;

&lt;p&gt;Next, generate a migration file that will create all the database tables Oban needs to run and keep tabs on job queues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix ecto.gen.migration add_oban_jobs_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then modify the generated file to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# priv/repo/migrations/timestamp_add_oban_jobs_table.exs&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;AddObanJobsTable&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;version:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;version:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migration with &lt;code&gt;mix ecto.migrate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With that done, you next need to configure Oban to run with the Postgres engine by adding the following lines to &lt;code&gt;config.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/config.exs&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:email_subscription_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;engine:&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Engines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;queues:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;default:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;repo:&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also recommended that you prevent Oban from running jobs in testing mode by adding the lines below to &lt;code&gt;text.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/text.exs&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:email_subscription_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;testing:&lt;/span&gt; &lt;span class="ss"&gt;:inline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we'll need to add Oban to the app's list of supervised children since they run as isolated supervision trees. To do this, add Oban to the app supervisor like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
      &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch_env!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:email_subscription_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
      &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, to finish up the installation and configuration, open up a new interactive Elixir shell with &lt;code&gt;iex -S mix&lt;/code&gt;. Run the command &lt;code&gt;Oban.config()&lt;/code&gt; to return an output similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iex&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&amp;gt;&lt;/span&gt; Oban.config&lt;span class="o"&gt;()&lt;/span&gt;
%Oban.Config&lt;span class="o"&gt;{&lt;/span&gt;
  dispatch_cooldown: 5,
  engine: Oban.Engines.Basic,
  get_dynamic_repo: nil,
  insert_trigger: &lt;span class="nb"&gt;true&lt;/span&gt;,
  log: &lt;span class="nb"&gt;false&lt;/span&gt;,
  name: Oban,
  node: &lt;span class="s2"&gt;"..."&lt;/span&gt;,
  notifier: &lt;span class="o"&gt;{&lt;/span&gt;Oban.Notifiers.Postgres, &lt;span class="o"&gt;[]}&lt;/span&gt;,
  peer: &lt;span class="o"&gt;{&lt;/span&gt;Oban.Peers.Postgres, &lt;span class="o"&gt;[]}&lt;/span&gt;,
  plugins: &lt;span class="o"&gt;[]&lt;/span&gt;,
  prefix: &lt;span class="s2"&gt;"public"&lt;/span&gt;,
  queues: &lt;span class="o"&gt;[&lt;/span&gt;default: &lt;span class="o"&gt;[&lt;/span&gt;limit: 10]],
  repo: EmailSubscriptionApp.Repo,
  shutdown_grace_period: 15000,
  stage_interval: 1000,
  testing: :disabled
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before moving on to defining a few jobs and instrumenting them using AppSignal, let's touch on how instrumentation actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic Instrumentation of Oban Using AppSignal
&lt;/h2&gt;

&lt;p&gt;When you added the AppSignal package to your app, Oban instrumentation was automatically added. The AppSignal package provides instrumentation for Oban workers and will also collect metrics on how background jobs are performing.&lt;/p&gt;

&lt;p&gt;With that in mind, let's write a few jobs and see how they appear on the AppSignal dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the First Background Job
&lt;/h2&gt;

&lt;p&gt;Our practice Phoenix application is all about receiving a user's email in an input on the homepage, then sending that user a series of emails (starting with a welcome email that is sent immediately when the user subscribes, a follow-up email sent ten minutes later, and then a final email sent thirty minutes later).&lt;/p&gt;

&lt;p&gt;Let's start by defining the background job that will trigger the first email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/email_subscription_app/workers/welcome_email_worker.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Workers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WelcomeEmailWorker&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Emails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Mailer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;args:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EmailSender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;welcome_email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mailer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="ss"&gt;:ok&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's dig into this example code a bit to understand how Oban workers work. This information will prove helpful when troubleshooting job errors.&lt;/p&gt;

&lt;p&gt;You have the module definition — in this case, the &lt;code&gt;WelcomeEmailWorker&lt;/code&gt; — then one or more job-performing functions, usually called &lt;code&gt;perform/1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;perform/1&lt;/code&gt; function receives an &lt;code&gt;Oban.Job&lt;/code&gt; struct containing the specific job's arguments which, in our case, is the &lt;code&gt;email&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;Even though we haven't shown it in this example code, Oban also allows for job scheduling using the &lt;code&gt;schedule_at&lt;/code&gt; and &lt;code&gt;schedule_in&lt;/code&gt; options.&lt;/p&gt;

&lt;p&gt;And like we mentioned before, AppSignal automatically instruments Oban and displays some default dashboards, as you can see in the examples below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07nr079idfhrdbpom6j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07nr079idfhrdbpom6j6.png" alt="Automatic instrumentation of Oban on AppSignal" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are our Oban workers listed on AppSignal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6d6r5tkwr3zoncqi0j9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6d6r5tkwr3zoncqi0j9.png" alt="Oban workers listed on AppSignal" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And details of a worker's instrumentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffuma6bozomdoethd01em.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffuma6bozomdoethd01em.png" alt="Worker instrumentation" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we've installed Oban, defined a simple background worker job, instrumented Oban with AppSignal, and visualized the output. Let's turn to when things go wrong with Oban and how we can use AppSignal to help us track errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Oban Errors
&lt;/h2&gt;

&lt;p&gt;Although Elixir systems are well-known for their fault tolerance, errors and bugs can affect them, resulting in a poor user experience if not addressed. In this section, we'll look at some common errors that could affect your Oban workers, as well as how you can instrument errors with AppSignal and see them on your dashboard.&lt;/p&gt;

&lt;p&gt;Many different errors could affect an Oban worker, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database connection errors&lt;/strong&gt; - Since Oban depends on accessing a PostgreSQL or SQLite 3 database, any connection issues between your app and its database will definitely affect any workers you have defined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External service failures&lt;/strong&gt; - For example, if there is an issue that affects emails being sent through a third-party email service provider in an email subscription app, the background jobs we've defined could fail to send emails, which would result in Oban job errors. Further, if you call third-party API services from your app, any rate-limiting efforts by the API service provider could result in connected Oban jobs failing to complete and resulting in errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job timeout errors&lt;/strong&gt; - These errors occur if a job's actual execution time exceeds the configured time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job queue congestion errors&lt;/strong&gt; - These happen when there are too many jobs in the queue, which could result in delays in job execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Instrumenting Oban Errors Using AppSignal for Elixir
&lt;/h2&gt;

&lt;p&gt;One of the major benefits of AppSignal is that most common errors are automatically instrumented for you. For example, see the code snippet below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Workers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WelcomeEmailWorker&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;queue:&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;EmailSubscriptionApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Emails&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Mailer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Worker&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Oban&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;args:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EmailSender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;welcome_email&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mailer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:ok&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Mailer delivery error: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an error occurs within the &lt;code&gt;perform/1&lt;/code&gt; function, AppSignal will catch it and give you a dashboard view with details of the error, as you can see in the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakiay54j4a7l7ra1835x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakiay54j4a7l7ra1835x.png" alt="Error instrumentation" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before wrapping up this article, let's take a look at the error details AppSignal shows you in the dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error message&lt;/strong&gt; - The actual error message associated with the raised error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backtrace&lt;/strong&gt; - The backtrace gives you fine-grained context on what happens before an error occurs in your app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt; - AppSignal also shows you any parameters involved when the error occurred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; - Finally, you also get information on whether the error occurred during a deployment or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at the background job processing package Oban. We learned how to install it, configure background workers, simulate an error, and instrument errors using AppSignal. I recommend using this information as a foundation for using Oban and AppSignal in your Elixir apps.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>oban</category>
    </item>
    <item>
      <title>A Complete Guide to Phoenix for Elixir Monitoring with AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Tue, 01 Oct 2024 12:20:29 +0000</pubDate>
      <link>https://dev.to/appsignal/a-complete-guide-to-phoenix-for-elixir-monitoring-with-appsignal-49ig</link>
      <guid>https://dev.to/appsignal/a-complete-guide-to-phoenix-for-elixir-monitoring-with-appsignal-49ig</guid>
      <description>&lt;p&gt;For Phoenix developers, maintaining the health of your applications is critical. AppSignal offers a powerful solution to gain deep insights into your application's performance and stability.&lt;/p&gt;

&lt;p&gt;In this introductory guide, we'll walk through the process of setting up AppSignal in your Phoenix app, instrumenting your code for detailed monitoring, handling errors effectively, and utilizing AppSignal's features to maintain and improve your application's performance. The lessons you learn from this article will help you get started using AppSignal to monitor your Phoenix app.&lt;/p&gt;

&lt;p&gt;But before proceeding, let's see what you'll need to follow along effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://appsignal.com/users/sign_up" rel="noopener noreferrer"&gt;AppSignal account — you can sign up for a free 30-day trial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A Phoenix app to work with. If you don't have one, you can fork &lt;a href="https://github.com/iamaestimo/intro-to-phoenix-monitoring-with-appsignal-example-app" rel="noopener noreferrer"&gt;this one&lt;/a&gt; that we'll use throughout the tutorial.&lt;/li&gt;
&lt;li&gt;Some experience working with Elixir/Phoenix applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started by learning how to add AppSignal to an existing Phoenix application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding AppSignal to a Phoenix Application
&lt;/h2&gt;

&lt;p&gt;Adding AppSignal's Phoenix package is very easy — just open up &lt;code&gt;mix.exs&lt;/code&gt; and add the package as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mix.exs&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:phoenix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.7.14"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:phoenix_ecto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 4.5"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:appsignal_phoenix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, run &lt;code&gt;mix deps.get&lt;/code&gt; to add the package to the application, then run the command below to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix appsignal.install &amp;lt;YOUR API KEY HERE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip: You can find your API key in the AppSignal dashboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you run the installer, you'll be prompted for an application name — go ahead and enter an appropriate name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Validating Push API key: Valid! 🎉
What is your application&lt;span class="s1"&gt;'s name? [counter_live_app]: Counter Live App
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up, you'll need to choose how you want the AppSignal configuration handled for your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;There are two methods of configuring AppSignal &lt;span class="k"&gt;in &lt;/span&gt;your application.
  Option 1: Using a &lt;span class="s2"&gt;"config/appsignal.exs"&lt;/span&gt; file. &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
  Option 2: Using system environment variables.  &lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can choose whatever suits you, but I prefer option 1, which uses a config file that you can customize to your liking. Once you make your choice, the AppSignal configuration should now be working, and if you visit the dashboard, you can see some default graphs (these might be empty initially as it takes a little bit of time for your app's data to be sent over).&lt;/p&gt;

&lt;p&gt;Before moving on, let's take a look at how you can customize the AppSignal config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing the AppSignal Config File
&lt;/h2&gt;

&lt;p&gt;A config file tells AppSignal which app and environment to instrument.&lt;/p&gt;

&lt;p&gt;The code snippet below shows a minimal configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/appsignal.exs&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:appsignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;otp_app:&lt;/span&gt; &lt;span class="ss"&gt;:counter_live_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Counter Live App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;push_api_key:&lt;/span&gt; &lt;span class="s2"&gt;"API KEY HERE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;env:&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the most important settings include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your application's OTP name.&lt;/li&gt;
&lt;li&gt;The name of the app as you want it to appear on AppSignal.&lt;/li&gt;
&lt;li&gt;Your AppSignal API key.&lt;/li&gt;
&lt;li&gt;The environment you want to instrument for, which could be development, production, or test environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The config file can be further customized using a &lt;a href="https://docs.appsignal.com/elixir/configuration/options.html" rel="noopener noreferrer"&gt;variety of other options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With the installation and configuration working, let's shift gears to monitor our Phoenix app's performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring the Performance of a Phoenix App
&lt;/h2&gt;

&lt;p&gt;When you add the AppSignal package to your Phoenix application, the package will take care of default HTTP instrumentation needs and send data to a dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs21fwfip58qj2m932fel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs21fwfip58qj2m932fel.png" alt="Default instrumentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And part of the automatic instrumentation includes performance monitoring:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54nzlyptzfkrkfbqx58i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54nzlyptzfkrkfbqx58i.png" alt="Default performance instrumentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default instrumentation will also include important details for slow-performing requests, like what's shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznfxm5sldad5bvgythn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznfxm5sldad5bvgythn6.png" alt="Automatic detailed performance dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And all of this is possible without any extra work from you.&lt;/p&gt;

&lt;p&gt;Next up, let's see how you can customize some of the performance instrumentation. For example, we can add a custom slow function to the Posts index and see the same on the AppSignal dashboard.&lt;/p&gt;

&lt;p&gt;First, let's edit the index action in the posts controller as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/counter_live_app_web/controllers/posts_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CounterLiveAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PostController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;really_slow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;really_slow&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"really slow index request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, we can visualize the instrumentation on the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffo1n4u8ecr0d9pw35gx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffo1n4u8ecr0d9pw35gx6.png" alt="Custom performance instrumentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Phoenix App Errors Using AppSignal
&lt;/h2&gt;

&lt;p&gt;In the previous section, we saw how adding AppSignal to our Phoenix app provides us with a lot of information about app performance. We also went ahead and added some simple custom instrumentation to simulate a really slow request to the posts index action, and visualized the results on the AppSignal dashboard. Now, let's see how we can view and track Phoenix errors using AppSignal.&lt;/p&gt;

&lt;p&gt;Our first example will be a simple call to a non-existent route, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzjwj1wtu83pi5xmrawj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzjwj1wtu83pi5xmrawj.png" alt="Ecto no results error"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, as with the default performance metrics, without extra work on our part, AppSignal is able to pick up this error and display it on the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cte7vo47airq7aytw6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cte7vo47airq7aytw6x.png" alt="Ecto no results error on AppSignal dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click on the error, you can see more detailed information, which is really helpful for debugging purposes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkn13jbj7tohrq6os9zq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkn13jbj7tohrq6os9zq3.png" alt="Ecto no results error details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I won't go into more detail on how you can customize exception instrumentation in the scope of this post. If you are interested in going down that rabbit hole, &lt;a href="https://docs.appsignal.com/elixir/instrumentation/exception-handling.html" rel="noopener noreferrer"&gt;check out the AppSignal for Elixir documentation on exception handling&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next up, let's set up alerts and notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Effective Alerts and Notifications
&lt;/h2&gt;

&lt;p&gt;As useful as an application performance monitoring solution like AppSignal is, you wouldn't want to spend all your time staring at dashboards. Instead, it would be better if you received automated alerts for any significant events.&lt;/p&gt;

&lt;p&gt;By default, whenever an error or performance event happens, it's recorded as an error or performance incident by AppSignal. Depending on the notification settings you've set up, you'll get an email (the default notification channel) or a message through any of the other &lt;a href="https://docs.appsignal.com/application/integrations" rel="noopener noreferrer"&gt;available notification channels&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can customize how AppSignal will notify you of events and errors by setting up any of the five notification defaults available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never notify&lt;/strong&gt; - when set to this, AppSignal will not notify you whenever an error or incident occurs. This setting is useful for non-critical incidents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every occurrence&lt;/strong&gt; - Let's say you'd like to be notified every time an incident happens. This is the setting you would use. You can use this setting for incidents that are critical in nature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First in deploy&lt;/strong&gt; - Here, a notification will be sent whenever an incident occurs when you first deploy your app. You will need to set up deploy markers that will tell AppSignal when a deployment occurs. For example, let's say you've set up post-deploy hooks of some kind. In that case, this setting helps you catch errors if these hooks don't work as expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First after close&lt;/strong&gt; - This setting tells AppSignal to send a notification after an incident recurs, immediately after you close a previous similar one. This setting would be useful for incidents that are not entirely due to a bug in your code, (for example, third-party API connection issues and so forth).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every nth hour or day&lt;/strong&gt; - Here, you tell AppSignal not to send a notification every nth hour or day. This could be very useful if you are facing an increased bill due to an increase in notification events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, everything we've looked at is quite basic, but your AppSignal setup is capable of a lot more.&lt;/p&gt;

&lt;p&gt;Next, let's briefly look at one or two examples of custom monitoring and alert notification setups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Alert Notification Setup
&lt;/h2&gt;

&lt;p&gt;By default, every application that is being monitored by AppSignal will have an email notifier set up automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farjgorrp91hjblcq9jxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farjgorrp91hjblcq9jxd.png" alt="Default email notification dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even so, you might want to be notified via other channels, and with AppSignal, these are a breeze to set up. The first step is to click on the "Add integration" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukchfqhfu6t0wr0n0vjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fukchfqhfu6t0wr0n0vjd.png" alt="Setting up a custom notifier - 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, if you wanted a Discord notifier, just choose it from the list of integrations and input the required information in the resulting dialog like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze3syvc7rc13ewt1shxk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fze3syvc7rc13ewt1shxk.png" alt="Setting up a Discord notifier"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even with all the bells and whistles that AppSignal makes available for alerting and notifications, it's important to really think about the kinds of notifications you would like to receive and the ones you'd be safe ignoring.&lt;/p&gt;

&lt;p&gt;Some good rules of thumb to guide your decision in this would include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consider error thresholds for critical versus non-critical errors. For non-critical errors, you could set very high thresholds. For critical errors, you might set finer thresholds (for example, send an alert if a critical error exceeds 5% and lasts for more than 3 minutes).&lt;/li&gt;
&lt;li&gt;Set up alerts and notifications for memory consumption. One of the key resources any app uses is memory. If an app is consuming more-than-normal memory, you want to make sure an alert is sent to you early.&lt;/li&gt;
&lt;li&gt;Uptime and availability notifications. This is self-explanatory, but you definitely want to be notified any time your app is unavailable.&lt;/li&gt;
&lt;li&gt;It would also make sense to set up alerts for custom events that are key to the functionality of your app, or to the user experience (for example, a bunch of failed payments over a certain amount of time).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these rules in mind, and considering AppSignal's robust alerting infrastructure, you have all you need to be notified whenever things go wrong. If you need further information, go through &lt;a href="https://docs.appsignal.com/application/integrations.html" rel="noopener noreferrer"&gt;AppSignal's documentation on setting up notifications and alerts&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring of a Phoenix App Using AppSignal: An Example
&lt;/h2&gt;

&lt;p&gt;We'll use AppSignal's function decorators to set up a custom error span that will be fired when we try to request a non-existent post.&lt;/p&gt;

&lt;p&gt;First, we modify the posts controller to use AppSignal decorators like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/counter_live_app_web/controllers/post_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CounterLiveAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PostController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;CounterLiveAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Instrumentation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Decorators&lt;/span&gt; &lt;span class="c1"&gt;# add this line&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we modify the show action with the custom instrumentation as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/counter_live_app_web/controllers/post_controller.ex&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Blog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_post!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;post:&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt;
      &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;NoResultsError&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;__STACKTRACE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Custom Post Not Found Error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_sample_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"custom_data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;
            &lt;span class="ss"&gt;post_id:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;controller:&lt;/span&gt; &lt;span class="s2"&gt;"PostController"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="s2"&gt;"show"&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;conn&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:not_found&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CounterLiveAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ErrorView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"404.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use AppSignal's &lt;a href="https://hexdocs.pm/appsignal/Appsignal.Instrumentation.html#send_error/3" rel="noopener noreferrer"&gt;&lt;code&gt;send_error/3&lt;/code&gt;&lt;/a&gt; to define a custom span with the name "Custom Post Not Found Error", which is called if we make a request to a non-existent post.&lt;/p&gt;

&lt;p&gt;As you can see from the screenshots below, the error is captured as expected on AppSignal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1blpaxc8wsdu9iscybd3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1blpaxc8wsdu9iscybd3.png" alt="Custom error with instrumentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the error link gives you access to the error details:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8jnsbcjh52d97w0xnoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8jnsbcjh52d97w0xnoa.png" alt="Custom error with instrumentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this article, we've looked at how to set up AppSignal to monitor a Phoenix application. We also covered how errors and performance incidences are instrumented and displayed on the dashboard.&lt;/p&gt;

&lt;p&gt;Even though this is enough to get you started with monitoring your Phoenix application, AppSignal offers a lot more options for you to explore (such as Ecto-specific monitoring to cover your app's Ecto queries, plug monitoring, and even in-depth custom instrumentation to cover almost any monitoring scenario you might have).&lt;/p&gt;

&lt;p&gt;Until next time, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>Custom Instrumentation for a Phoenix App in Elixir with AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Tue, 20 Aug 2024 12:04:14 +0000</pubDate>
      <link>https://dev.to/appsignal/custom-instrumentation-for-a-phoenix-app-in-elixir-with-appsignal-1087</link>
      <guid>https://dev.to/appsignal/custom-instrumentation-for-a-phoenix-app-in-elixir-with-appsignal-1087</guid>
      <description>&lt;p&gt;In the first part of this series, we saw that even if you just use AppSignal’s default application monitoring, you can get a lot of information about how your Phoenix application is running.&lt;/p&gt;

&lt;p&gt;Even so, there are many ways in which a Phoenix application may exhibit performance issues, such as slow database queries, poorly engineered LiveView components, views that are too heavy, or non-optimized assets.&lt;/p&gt;

&lt;p&gt;To get a grasp on such issues and peer even more closely into your app’s internals, you’ll need something that packs more punch than the default dashboards: custom instrumentation.&lt;/p&gt;

&lt;p&gt;In this article, we'll go through a step-by-step process to build custom instrumentation using AppSignal for a Phoenix application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;Before we get into custom instrumentation, here's what you need to follow along:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Phoenix LiveView app with the AppSignal package installed. If you have an app ready but haven't installed the AppSignal package, just &lt;a href="https://docs.appsignal.com/elixir/installation.html" rel="noopener noreferrer"&gt;follow this guide&lt;/a&gt;. Or you can &lt;a href="https://github.com/iamaestimo/game_reviews_liveview" rel="noopener noreferrer"&gt;clone the Phoenix app we'll be using&lt;/a&gt; throughout the tutorial.&lt;/li&gt;
&lt;li&gt;Basic experience working with Elixir and the Phoenix framework.&lt;/li&gt;
&lt;li&gt;An AppSignal account. &lt;a href="https://appsignal.com/users/sign_up" rel="noopener noreferrer"&gt;Sign up for a free trial&lt;/a&gt; if you don't have one already.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Is Custom Instrumentation, and Why Do We Need It?
&lt;/h2&gt;

&lt;p&gt;Custom instrumentation means adding additional telemetry emission and monitoring functionality to your Phoenix app beyond what is provided by the default instrumentation. Specifically, it involves the use of functions and macros (provided through the AppSignal package) in specific places in your codebase that you want to investigate further. You can then view the collected data on a custom AppSignal dashboard.&lt;/p&gt;

&lt;p&gt;If you have a complex or mission-critical Phoenix app, it might not be possible to identify all its potential performance issues using the default instrumentation. It's important to have deeper insights into what's going on inside your app.&lt;/p&gt;

&lt;p&gt;With custom instrumentation, you can easily structure exactly what you need to identify and visualize these custom data collections within the AppSignal dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with Custom Instrumentation in AppSignal
&lt;/h2&gt;

&lt;p&gt;You can implement &lt;a href="https://docs.appsignal.com/elixir/instrumentation/instrumentation.html" rel="noopener noreferrer"&gt;custom instrumentation using AppSignal&lt;/a&gt; in two ways: through function decorators or instrumentation helpers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Function Decorators
&lt;/h3&gt;

&lt;p&gt;A function decorator is a higher-order function that you wrap around a function from which you want to collect data. They give you more granular control than instrumentation helpers, but compared to the latter, they are not as flexible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrumentation Helpers
&lt;/h3&gt;

&lt;p&gt;AppSignal also provides instrumentation helper functions, which you can use to instrument specific parts of your code manually. These helper functions can start and stop custom measurements, help you track custom events, and add custom metadata to metrics reported via AppSignal dashboards. This approach gives you more flexibility in instrumenting your code but requires more manual intervention.&lt;/p&gt;

&lt;p&gt;In the following sections, we'll use what we've learned to implement custom instrumentation for a Phoenix application.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use an Instrumentation Helper
&lt;/h2&gt;

&lt;p&gt;To implement the first custom instrumentation, we'll consider a controller action that calls a potentially slow function in a simple Phoenix app featuring games and reviews.&lt;/p&gt;

&lt;p&gt;As you can see below, the games context has a method for fetching an individual game and preloading any reviews:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app/games.ex&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_game_with_reviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:reviews&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the subsequent action using this method in the game controller is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="no"&gt;Games&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_game_with_reviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game:&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's modify this controller action with a custom instrumentation helper to check how many times it is called and its response times. We can do this using AppSignal's &lt;a href="https://hexdocs.pm/appsignal/Appsignal.Instrumentation.Helpers.html#summary" rel="noopener noreferrer"&gt;&lt;code&gt;instrument/2&lt;/code&gt;&lt;/a&gt; function, which takes two parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function name&lt;/li&gt;
&lt;li&gt;The function being instrumented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; If you use &lt;code&gt;instrument/3&lt;/code&gt; instead, it's possible to add an event group name as an optional parameter. Usually, whenever you use &lt;code&gt;instrument/2&lt;/code&gt;, measurements will be collected and categorized under the "other" event group within the event groups section. But if you wanted another more descriptive name, &lt;code&gt;instrument/3&lt;/code&gt; allows you to pass in an additional parameter for the event group name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Games&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_game_with_reviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;am_i_slow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# add this line&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;game:&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's define the &lt;code&gt;am_i_slow&lt;/code&gt; function as a private module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;am_i_slow&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Check if am slow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifically, we're defining a custom trace span with an event sample called &lt;code&gt;"Check if am slow"&lt;/code&gt;. This will show up in our dashboard under the &lt;em&gt;other&lt;/em&gt; (background) event namespace, as you can see in the screenshots below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S4kBch5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S4kBch5K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-1.png" alt="Custom instrumentation using an instrumentation helper - 1" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_C2HLkFi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_C2HLkFi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-2.png" alt="Custom instrumentation using an instrumentation helper - 2" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WJejoB8z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WJejoB8z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-3.png" alt="Custom instrumentation using an instrumentation helper - 3" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what if you wanted a different event group name from &lt;code&gt;"other"&lt;/code&gt; or &lt;code&gt;"background"&lt;/code&gt; for the event namespace? Just use the &lt;code&gt;instrument/3&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;am_i_slow&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Really Slow Queries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"First Slow Query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we give the span a category name of &lt;code&gt;"Really Slow Queries"&lt;/code&gt; and a span name of &lt;code&gt;"First Slow Query"&lt;/code&gt;, for a sample view like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cWN-597J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cWN-597J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-4.png" alt="Instrumentation helper with custom category name" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's another example of using function helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;games&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Fetch games"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="no"&gt;Games&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_games&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;games:&lt;/span&gt; &lt;span class="n"&gt;games&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;instrument/2&lt;/code&gt; function, we define a span called &lt;code&gt;"Fetch games"&lt;/code&gt; which results in this event trace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kpLVzCQs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kpLVzCQs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrument-helper-5.png" alt="Instrumentation helper - 5" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, you can easily visualize the function's response time and throughput.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://hexdocs.pm/appsignal/Appsignal.Span.html" rel="noopener noreferrer"&gt;more options available to customize AppSignal's instrumentation helpers&lt;/a&gt; than what I've shown here. I highly encourage you to check out the possibilities.&lt;/p&gt;

&lt;p&gt;Next, let's see how you can use function decorators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Function Decorators
&lt;/h2&gt;

&lt;p&gt;As you've probably noticed when using instrumentation helpers, you end up modifying existing functions with the helper code you add. If you don't want to do this, you can use function decorators instead.&lt;/p&gt;

&lt;p&gt;Let's continue working with the game controller and instrument the index method using a decorator. First, we will add the decorator module &lt;code&gt;Appsignal.Instrumentation.Decorators&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Instrumentation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Decorators&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have access to the decorator's functions. Let's decorate the index method as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app_web/controllers/game_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Instrumentation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Decorators&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;am_i_slow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Games&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_game_with_reviews&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;game:&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# add the decorator function&lt;/span&gt;
  &lt;span class="nv"&gt;@decorate&lt;/span&gt; &lt;span class="n"&gt;transaction_event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;am_i_slow&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a transaction event, which you can visualize in your &lt;em&gt;Events&lt;/em&gt; dashboard, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sIWq98Fp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/function-decorator-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sIWq98Fp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/function-decorator-1.png" alt="Function decorator - 1" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You get all the information you need, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a. The resource where the function decorator was called&lt;/li&gt;
&lt;li&gt;b. A sample breakdown showing how long Ecto queries took, how long the templates took to load, and so forth.&lt;/li&gt;
&lt;li&gt;c. An event timeline with a breakdown of the transaction time of everything involved in that function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, let's take a look at instrumenting Phoenix channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumenting Phoenix LiveViews and Channels
&lt;/h2&gt;

&lt;p&gt;In the example below, we have the welcome live view as shown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# game_reviews_app_web/live/welcome_live.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WelcomeLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;current_time:&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
      &amp;lt;div class="&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
        &amp;lt;h1&amp;gt;Welcome to my LiveView App&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Current time: &amp;lt;%= @current_time %&amp;gt;&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's use an instrumentation helper to see how this live view performs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# game_reviews_app_web/live/welcome_live.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WelcomeLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;only:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;instrument:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Liveview instrumentation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_interval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="ss"&gt;:tick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="ss"&gt;current_time:&lt;/span&gt;&lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as you can see, the transaction times and throughput are now available for inspection on our dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OGu8xVu5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrumenting-liveviews.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OGu8xVu5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-08/instrumenting-liveviews.png" alt="Instrumenting Liveviews" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also worth noting that the AppSignal for Elixir package enables you to instrument Phoenix channels using a custom function decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;VideoChannel&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:channel&lt;/span&gt;

  &lt;span class="c1"&gt;# first add the instrumentation decorators module&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Instrumentation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Decorators&lt;/span&gt;

  &lt;span class="c1"&gt;# then add the decorator function&lt;/span&gt;
  &lt;span class="nv"&gt;@decorate&lt;/span&gt; &lt;span class="n"&gt;channel_action&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"videos:"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:video_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In part one of this series, we looked at how to set up AppSignal for an Elixir app and AppSignal's error tracking functionality.&lt;/p&gt;

&lt;p&gt;In this article, we've seen how easy it is to use AppSignal's Elixir package to implement custom instrumentation for a Phoenix application. We've also learned how to use instrumentation helpers and function decorators.&lt;/p&gt;

&lt;p&gt;With this information, you can now easily decide when to use a decorator versus an instrumentation helper in your next Phoenix app.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
    <item>
      <title>Setting Up Custom Metrics with Effective Alerts for a Ruby App in AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Wed, 14 Aug 2024 07:45:23 +0000</pubDate>
      <link>https://dev.to/appsignal/setting-up-custom-metrics-with-effective-alerts-for-a-ruby-app-in-appsignal-44je</link>
      <guid>https://dev.to/appsignal/setting-up-custom-metrics-with-effective-alerts-for-a-ruby-app-in-appsignal-44je</guid>
      <description>&lt;p&gt;Most of the time, the default application monitoring metrics, graphs, and visualizations provided by AppSignal will do for your Ruby app. However, you might be the kind of user who likes a bit of control over what is measured, how it’s displayed, and how critical information about your app should be relayed.&lt;/p&gt;

&lt;p&gt;AppSignal allows you to customize app metrics and dashboards as you wish. In this guide, we’ll learn all about AppSignal's custom metrics, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What custom metrics are&lt;/li&gt;
&lt;li&gt;The different types of custom metrics you can set up&lt;/li&gt;
&lt;li&gt;How to customize graph visualizations&lt;/li&gt;
&lt;li&gt;How to set up effective alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more!&lt;/p&gt;

&lt;p&gt;But before we dive in, you'll need a couple of things to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An AppSignal account:&lt;/strong&gt; If you don't have one, &lt;a href="https://appsignal.com/users/sign_up" rel="noopener noreferrer"&gt;sign up for a 30-day free trial&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Ruby application:&lt;/strong&gt; This app can be based on any supported Ruby framework, like Rails, Sinatra, or just plain Ruby. Additionally, it can be a production or development app. If you don't want to spin up your own app, &lt;a href="https://github.com/iamaestimo/sinatra_api_backend_app" rel="noopener noreferrer"&gt;clone the code for the example Sinatra app we'll be using in this tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: If you're using your own app to follow along with this tutorial, make sure your app is configured to use the latest AppSignal Ruby gem, as the examples used in this tutorial assume this is the case.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Custom Metrics?
&lt;/h2&gt;

&lt;p&gt;Other than measuring your app's error rates, throughput, and performance, you might be interested in measuring custom data specifically tailored for your own app. For example, you could be interested in how many visitors signed up to your app in a particular time period, how your app's websocket layer is performing, and so forth.&lt;/p&gt;

&lt;p&gt;For such customized cases, you might be hard-pressed to find a standard measuring tool within AppSignal. Instead, you'll need to use a &lt;a href="https://docs.appsignal.com/ruby/instrumentation/index.html" rel="noopener noreferrer"&gt;custom metric&lt;/a&gt;. Custom metrics are additional metrics that you define alongside the default ones for deeper context on how your app is running.&lt;/p&gt;

&lt;p&gt;Next up, let's learn how to set up our first custom metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Custom Metrics
&lt;/h2&gt;

&lt;p&gt;You can set up a custom metric for almost any use case within your application. Let's start with a simple example to help you understand how everything fits together.&lt;/p&gt;

&lt;p&gt;The first step is to define a custom metric that will be tracked on AppSignal. You can define a custom metric by using the different metric types available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gauges&lt;/li&gt;
&lt;li&gt;Counters&lt;/li&gt;
&lt;li&gt;Distributions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Gauge Custom Metric
&lt;/h3&gt;

&lt;p&gt;In AppSignal, a &lt;em&gt;gauge&lt;/em&gt; custom metric is useful for measuring metrics that increase and decrease over time.&lt;/p&gt;

&lt;p&gt;Let's set up a simple gauge custom metric to measure the total number of posts in our example Sinatra app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/base'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'dotenv/load'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/reloader'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sinatra/activerecord'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'./models/post.rb'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'appsignal/integrations/sinatra'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sinatra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;

    &lt;span class="c1"&gt;# set a gauge custom metric&lt;/span&gt;
    &lt;span class="n"&gt;posts_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
    &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_gauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"all_posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code shown above, we use the &lt;code&gt;Appsignal::Helpers::Metrics&lt;/code&gt; module and call the &lt;code&gt;set_gauge&lt;/code&gt; method, which accepts three arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;key&lt;/code&gt;: the name of the custom metric. In the example, this would be &lt;em&gt;all_posts&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;value&lt;/code&gt; - The metric or "thing" to be measured. In the example shown above, this is simply the total post count.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tags&lt;/code&gt; - Additional and optional metadata that can be added to a custom metric and are useful for labeling the data being measured however you want. For example, we could easily tag the &lt;code&gt;posts_count&lt;/code&gt; metric to account for the environment, as shown below:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.rb&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="n"&gt;posts_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;
  &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_gauge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"all_posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posts_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;environment: &lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we've just added our first custom gauge metric! But if you were to go back to AppSignal, your new custom metric will not be visible. Instead, you're likely to see the default dashboard, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N_lVdARU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/default-appsignal-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N_lVdARU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/default-appsignal-dashboard.png" alt="Default Appsignal dashboard" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So what do you need to do to make the custom metric appear? You need to add a dashboard. Start with creating a new dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6uYrLKUo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/create-new-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6uYrLKUo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/create-new-dashboard.png" alt="Create new dashboard" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then name your new dashboard with a descriptive title and description:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9lmHbOGk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/naming-custom-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9lmHbOGk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/naming-custom-dashboard.png" alt="Name the new dashboard" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the custom dashboard added, you'll now need to add a graph for the custom metric:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G39NjB1i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/adding-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G39NjB1i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/adding-graph.png" alt="Adding a graph to the custom dashboard" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then define the new graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lc6d_e14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/defining-new-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lc6d_e14--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/defining-new-graph.png" alt="Define the new graph" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a breakdown of the fields to set up the new graph:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;a. Title&lt;/strong&gt; - Enter a descriptive title for the new graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;b. Description&lt;/strong&gt; - This is optional, but you can enter a description for the new graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;c. Metrics&lt;/strong&gt; - Here is where you define the metric that will be measured and displayed by the new graph. This is the name of the custom metric, or the first argument defined in the &lt;code&gt;set_gauge&lt;/code&gt; method: &lt;code&gt;all_posts&lt;/code&gt;. In this section, you can also define tags (for example, the tag &lt;em&gt;environment&lt;/em&gt; is also included as shown).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;d. Graph display&lt;/strong&gt; - This is where you choose the type of graph display for your new graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;e. Legend label&lt;/strong&gt; - You can customize the label for the chart legend here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;f. Data format&lt;/strong&gt; - Define the data type used for the graph display. You can choose from a number of formats, including number, percentage, throughput (in requests/minute or hour), duration (in milliseconds), or file size (in bytes).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you've properly defined the new graph's properties, you should get a graph for the custom metric. This is similar to what is shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MQd4f5b1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/finished-custom-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MQd4f5b1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/finished-custom-graph.png" alt="Finished custom graph" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving forward, let's look at the next custom metric type: the counter.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Counter Custom Metric
&lt;/h3&gt;

&lt;p&gt;A counter custom metric is great for measuring how many times an event occurs. Using the example application, we can apply a counter metric to measure every time the home (root) page is visited.&lt;/p&gt;

&lt;p&gt;To begin with, edit the root method to include the code shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.rb&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment_counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"visits_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use AppSignal's &lt;code&gt;increment_counter&lt;/code&gt; method and pass it &lt;code&gt;visits_count&lt;/code&gt; as the first argument. The increment step is the integer 1, passed as the second argument. You could also add a &lt;code&gt;tags&lt;/code&gt; hash as the third argument, but we'll leave it as is (since this was covered in the previous section).&lt;/p&gt;

&lt;p&gt;Now go ahead and follow the steps as outlined for the gauge metric type. Add a custom graph for this counter metric to give you a similar graph to the one shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vksv7mC_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/custom-counter-metric.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vksv7mC_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/custom-counter-metric.png" alt="Custom graph for counter metric" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's switch gears to the distribution custom metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Distribution Custom Metric
&lt;/h3&gt;

&lt;p&gt;The AppSignal distribution custom metric is useful for measuring something per unit of time: for example, how many seconds it takes to generate a PDF report, or how long a background job takes to execute.&lt;/p&gt;

&lt;p&gt;Using the example application, let's modify the main file to include a call to an open API endpoint. Then, we'll use a custom distribution to measure how long the API call takes in milliseconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.rb&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'httparty'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sinatra&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;
      &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://openlibrary.org/search/authors.json?q=clavell'&lt;/span&gt;
      &lt;span class="vg"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
      &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_distribution_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"fetch_books_duration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="vg"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we go back to AppSignal, we can view the custom distribution as a graph.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip: You can follow the steps outlined in the gauge section to set up the custom graph visualization.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PDB6Uf07--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/distribution-metric.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PDB6Uf07--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/distribution-metric.png" alt="Distribution metric" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that you've learned how to create custom metrics and the accompanying graph visualizations, you might have noticed that it's not very convenient to keep going back to the AppSignal dashboards to see what's going on with your app. Instead, it would be very handy if you could get a notification for your custom metrics, right?&lt;/p&gt;

&lt;p&gt;Let's learn how to set up notifications for your custom metrics next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notification Alerts
&lt;/h2&gt;

&lt;p&gt;By default, whenever an error or performance event occurs, AppSignal will open an incident for that event and place it in the relevant section. For example, if it's an error, you'll find it in the errors list, while performance incidents will be in the performance list.&lt;/p&gt;

&lt;p&gt;Additionally, &lt;a href="https://www.appsignal.com/tour/anomaly-detection" rel="noopener noreferrer"&gt;AppSignal sends out incident notifications&lt;/a&gt; via email (the default notification channel). You can also set up other notification channels, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discord&lt;/li&gt;
&lt;li&gt;Google Hangouts&lt;/li&gt;
&lt;li&gt;Intercom&lt;/li&gt;
&lt;li&gt;Microsoft Teams&lt;/li&gt;
&lt;li&gt;Slack&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more.&lt;/p&gt;

&lt;p&gt;But before we set up a notification trigger for one of our custom metrics, it's important to be aware of the various notification options available to you.&lt;/p&gt;

&lt;p&gt;To begin with, you can set up a notification for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every time&lt;/strong&gt; - Here, a notification will be sent out every time an incident occurs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First deploy&lt;/strong&gt; - This indicates that a notification will be sent the first time an incident occurs after a deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First after close&lt;/strong&gt; - Here, a notification is sent out whenever an incident re-occurs after the previous one was closed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never notify&lt;/strong&gt; - As the name suggests, in this case, a notification will never be sent, but the error or performance incident will still be tracked on AppSignal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every nth hour or day&lt;/strong&gt; - With this option, you can specify how many alerts will be sent to you within an hour or a day. This option is perfect for keeping a balance between getting notified of important events and having too many notifications (which could easily overwhelm you or your team).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I highly suggest you dig into &lt;a href="https://docs.appsignal.com/application/notification-settings.html#notification-options" rel="noopener noreferrer"&gt;AppSignal's notification settings documentation&lt;/a&gt; to get more information on these options.&lt;/p&gt;

&lt;p&gt;Let's see how to set up a notification for one of the custom metrics we made earlier. This will be a trivial example, but it will illustrate the steps you need to go through for your own use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Notification Alerts for Your Custom Metrics
&lt;/h3&gt;

&lt;p&gt;For this example, we'll use the distribution metric that measured the API call's duration earlier in this post. Let's say we'd like to get an email alert whenever the mean duration exceeds a certain number (in milliseconds).&lt;/p&gt;

&lt;p&gt;The steps for setting this up are shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wmm7W1Zl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wmm7W1Zl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-1.png" alt="Setting up notifications for a custom metric - 1" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firstly, begin by hitting the &lt;em&gt;Triggers&lt;/em&gt; link in the left-hand menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UHJTV0H8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UHJTV0H8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-2.png" alt="Setting up notifications for a custom metric - 2" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your trigger a relevant name, then select the measurement for which this notification is intended. In this example, we are using the &lt;code&gt;fetch_books_duration&lt;/code&gt; distribution custom metric. You can also add tags if you wish to.&lt;/p&gt;

&lt;p&gt;Next, define the comparison operator and the value to check for. For example, let's say we want to receive an alert whenever the duration exceeds 1600 milliseconds. For this, we would select the comparison operator &lt;em&gt;more than&lt;/em&gt;, then, a value of 1600.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FIyY0iFM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FIyY0iFM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-3.png" alt="Setting up notification for a custom metric - 3" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, you'll need to define the &lt;a href="https://docs.appsignal.com/anomaly-detection.html#warm-up" rel="noopener noreferrer"&gt;alert warm-up and cooldown settings&lt;/a&gt;. Provide a description for the alert, a link to the dashboard to be included in the alert message (if needed), and, finally, the notification method (with email being the default).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yTP6NTdI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yTP6NTdI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-07/setting-up-notifications-step-4.png" alt="Setting up notification for a custom metric - 4" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that done, you'll receive a notification whenever an incident occurs matching the settings you input here.&lt;/p&gt;

&lt;p&gt;And that's it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this article, we learned how to set up custom metrics for a Ruby application with matching dashboards and graph visualizations on AppSignal.&lt;/p&gt;

&lt;p&gt;The custom metrics functionality that AppSignal offers can be fine-tuned for very powerful applications. Take a deep dive into &lt;a href="https://docs.appsignal.com/metrics/custom.html" rel="noopener noreferrer"&gt;AppSignal's custom metrics documentation&lt;/a&gt; to discover more possibilities.&lt;/p&gt;

&lt;p&gt;Until next time, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Track Errors in Phoenix for Elixir with AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Tue, 23 Jul 2024 12:38:29 +0000</pubDate>
      <link>https://dev.to/appsignal/track-errors-in-phoenix-for-elixir-with-appsignal-44gb</link>
      <guid>https://dev.to/appsignal/track-errors-in-phoenix-for-elixir-with-appsignal-44gb</guid>
      <description>&lt;p&gt;AppSignal is a powerful error tracking and performance monitoring tool that can help you maintain reliability and speed in your Elixir applications.&lt;/p&gt;

&lt;p&gt;In this tutorial, the first of a two-part series, you'll learn how to integrate AppSignal into your Elixir application, configure it for error tracking, interpret error reports, and leverage AppSignal's features to debug and resolve issues.&lt;/p&gt;

&lt;p&gt;Whether you're a seasoned Elixir developer looking to improve your application's error handling or a beginner who wants to understand how error tracking works, this step-by-step tutorial will give you a firm foundation for using AppSignal to track errors in your Elixir app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Elixir development environment. If you don't have Elixir installed, follow the steps in &lt;a href="https://elixir-lang.org/install.html" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; for your operating system.&lt;/li&gt;
&lt;li&gt;An existing Elixir app. If you don't have one, go ahead and &lt;a href="https://github.com/iamaestimo/game_reviews_liveview" rel="noopener noreferrer"&gt;fork this app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Some experience working with Elixir/Phoenix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating AppSignal Into a Phoenix App
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Quick tip: As of writing this article, AppSignal currently supports pure Elixir, Plug, and Phoenix apps. The examples used for this tutorial are centered around a Phoenix app featuring video games, users, and reviews, but the steps taken should be relatively similar for the other supported flavors.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AppSignal provides excellent support for the &lt;a href="https://www.appsignal.com/elixir/phoenix-monitoring" rel="noopener noreferrer"&gt;Phoenix framework&lt;/a&gt; and the installation process is very seamless. First, &lt;a href="https://appsignal.com/users/sign_up" rel="noopener noreferrer"&gt;sign up for a free 30-day trial&lt;/a&gt;, no card details required.&lt;/p&gt;

&lt;p&gt;Then open the Phoenix app's dependencies file and add AppSignal's Phoenix package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mix.exs&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:appsignal_phoenix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;mix deps.get&lt;/code&gt; to add the package to your app. Once it's added successfully, install it by running the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix appsignal.install &amp;lt;PUSH_API_KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: You can get your PUSH_API_KEY from the AppSignal dashboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you run this command, you'll be presented with some options to customize the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Validating Push API key: Valid! 🎉
What is your application&lt;span class="s1"&gt;'s name? [game_reviews_app]: &amp;lt;APP NAME&amp;gt;

There are two methods of configuring AppSignal in your application.
  Option 1: Using a "config/appsignal.exs" file. (1)
  Option 2: Using system environment variables.  (2)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The first option is your app's name as you want it to appear on the AppSignal dashboard.&lt;/li&gt;
&lt;li&gt;The second option is how to configure AppSignal: via a configuration file or a system variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring AppSignal
&lt;/h2&gt;

&lt;p&gt;For your app to send data to AppSignal, you should have a minimal configuration in &lt;code&gt;config/appsignal.exs&lt;/code&gt;, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/appsignal.exs&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:appsignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;otp_app:&lt;/span&gt; &lt;span class="ss"&gt;:game_reviews_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"Phoenix API app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;push_api_key:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;AppSignal&lt;/span&gt; &lt;span class="no"&gt;Push&lt;/span&gt; &lt;span class="no"&gt;API&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;env:&lt;/span&gt; &lt;span class="no"&gt;Mix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can set this up using system environment variables. One thing to note is that this file can be configured to your liking using a &lt;a href="https://docs.appsignal.com/elixir/configuration/options.html" rel="noopener noreferrer"&gt;variety of options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Assuming the configuration proceeds without a hitch, you should now see your app automatically added to the AppSignal dashboard, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fphoenix-app-added-to-appsignal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fphoenix-app-added-to-appsignal.png" alt="Phoenix app added to AppSignal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: It may take a couple of minutes for your app's data to start showing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Great, now let's see how we can instrument an error in AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumenting Elixir Errors
&lt;/h2&gt;

&lt;p&gt;This example app is a simple Phoenix LiveView app featuring a video game and reviews.&lt;/p&gt;

&lt;p&gt;Once you've &lt;a href="https://github.com/iamaestimo/game_reviews_liveview" rel="noopener noreferrer"&gt;cloned the application&lt;/a&gt; to your development environment, run &lt;code&gt;mix deps.get&lt;/code&gt; to install all the dependencies, then you can run the app with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make the app available on &lt;code&gt;http://localhost:4000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Go ahead and test it by creating a few games with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="s1"&gt;'http://localhost:4000/api/games'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Accept: */*'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Host: localhost:4000'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Connection: keep-alive'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{
    "game": {
        "title": "Hell divers",
        "genre": "Action",
        "publisher": "Arrowhead Studios"
    }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having made a couple of games this way, you can now make a GET request for the games list (I use Insomnia for this, but you can use whatever you like):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Ftesting-the-api-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Ftesting-the-api-app.png" alt="Requesting the games list"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which should execute successfully, as you can see above.&lt;/p&gt;

&lt;p&gt;Now, let's deliberately cause an error to see how it will be shown on the AppSignal dashboard.&lt;/p&gt;

&lt;p&gt;Firstly, create a function defining a custom exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/exceptions/plug_exception.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PlugException&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;defexception&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:plug_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, modify the index action in the games controller to call this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;GameController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;GameReviewsApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PlugException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;plug_status:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message:&lt;/span&gt; &lt;span class="s2"&gt;"You're not allowed!"&lt;/span&gt;
    &lt;span class="n"&gt;games&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Games&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_games&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;games:&lt;/span&gt; &lt;span class="n"&gt;games&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we make a request to the games list, this custom exception is raised:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fcustom-exception.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fcustom-exception.png" alt="Custom exception"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the same is shown on the AppSignal dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fcustom-exception-on-appsignal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fcustom-exception-on-appsignal.png" alt="Custom exception on AppSignal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click on the displayed error link, AppSignal gives you more context on the error, which should help you resolve it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Ferror-details.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Ferror-details.png" alt="Error details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up, let's learn how we can track Ecto queries inside AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrumenting Ecto queries in AppSignal
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hexdocs.pm/ecto/Ecto.html" rel="noopener noreferrer"&gt;Ecto&lt;/a&gt; is the most popular database abstraction library for Elixir apps.&lt;/p&gt;

&lt;p&gt;AppSignal provides native support for Ecto by hooking into the Ecto query telemetry layer, giving you query profiling, identification of slow queries, and more, with no extra work on your part (as long as the &lt;code&gt;otp_app&lt;/code&gt; name within the AppSignal configuration file matches the name of your app in &lt;code&gt;config.exs&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/config.exs&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;# General application configuration&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:game_reviews_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/appsignal.exs&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:appsignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;otp_app:&lt;/span&gt; &lt;span class="ss"&gt;:game_reviews_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With our configuration done, AppSignal's instrumentation will now automatically pick up Ecto queries and display them on this dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fdefault-ecto-instrumentation.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fdefault-ecto-instrumentation.png" alt="Default Ecto instrumentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now this might be good enough for normal Ecto queries. But to pick up query types such as preloads in the right way, you'll need to edit your app's repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app/repo.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;PhxRestApiApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# use Ecto.Repo,       #...replace this default line with the one below...&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;otp_app:&lt;/span&gt; &lt;span class="ss"&gt;:game_reviews_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;adapter:&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SQLite3&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's learn how to track Phoenix LiveViews using AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument Phoenix LiveView with AppSignal
&lt;/h2&gt;

&lt;p&gt;With the latest AppSignal for Elixir package (version 2.12.0 as of writing), you can instrument LiveView dashboards in AppSignal.&lt;/p&gt;

&lt;p&gt;Simply adding the package is enough to automatically add your app's live views using the AppSignal package's telemetry handlers. Or you can configure the instrumentation manually using helpers made available by the package.&lt;/p&gt;

&lt;p&gt;The code below shows how easy it is to manually configure LiveView instrumentation by adding a single line within &lt;code&gt;application.ex&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/game_reviews_app/application.ex&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;= ...added this line&lt;/span&gt;

  &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, you're able to view LiveView &lt;code&gt;mount&lt;/code&gt;, &lt;code&gt;handle_event&lt;/code&gt;, and &lt;code&gt;handle_param&lt;/code&gt; events in the AppSignal dashboard, like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fliveview-instrumentation-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fliveview-instrumentation-1.png" alt="Liveview instrumentation - 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fliveview-instrumentation-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fliveview-instrumentation-2.png" alt="Liveview instrumentation - 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fliveview-instrumentation-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fliveview-instrumentation-3.png" alt="Liveview instrumentation - 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, with the AppSignal LiveView instrumentation helpers, you can wrap LiveView action definitions within an &lt;code&gt;instrument/3&lt;/code&gt; block for even more granular visualizations, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# live/welcome_live.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;WelcomeLive&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GameReviewsAppWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:live_view&lt;/span&gt;

  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LiveView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;instrument:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;__MODULE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"timer instrumentation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="ss"&gt;:timer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_interval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="ss"&gt;:tick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;current_time:&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc_now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sx"&gt;~H""&lt;/span&gt;&lt;span class="s2"&gt;"
    &amp;lt;div class="&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
      &amp;lt;h1&amp;gt;Welcome to my LiveView App&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Current time: &amp;lt;%= @current_time %&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    """&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When sending metrics to AppSignal, you can use &lt;a href="https://docs.appsignal.com/elixir/instrumentation/instrumentation.html" rel="noopener noreferrer"&gt;a gauge, a counter, or a distribution&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the code above, we define a counter measurement called "timer instrumentation" to send current time measurements to the AppSignal dashboard continuously.&lt;/p&gt;

&lt;p&gt;As you can see below, by switching the dashboard view from "web" to "live_view", "timer instrumentation" is now available on the dashboard as a sample (with all the details you need to dig further):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Finstrument-3-in-action.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Finstrument-3-in-action.png" alt="Instrumenting an AppSignal counter in Elixir"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, I'll highlight a nifty feature that makes AppSignal really stand out in the application performance monitoring space: automated dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  AppSignal's Automated Dashboards for Elixir
&lt;/h2&gt;

&lt;p&gt;Whenever you add AppSignal to an app built using Elixir, it automatically creates a dashboard.&lt;/p&gt;

&lt;p&gt;When we add the AppSignal package to the Phoenix app, we get an automated dashboard and notification — in this case, an Erlang Virtual Machine dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fautomated-dashboard-notification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fautomated-dashboard-notification.png" alt="Automated dashboard creation notification"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fautomated-dashboard-link.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fautomated-dashboard-link.png" alt="Automated dashboard link"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fautomated-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-07%2Fautomated-dashboard.png" alt="Automated dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://docs.appsignal.com/elixir.html" rel="noopener noreferrer"&gt;AppSignal for Elixir documentation&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this article, we've seen how to track Ecto queries and Phoenix LiveViews using AppSignal for Elixir. Consider this an introduction to what is possible when it comes to error tracking in Elixir with AppSignal.&lt;/p&gt;

&lt;p&gt;Next time, we'll deep dive into custom instrumentation using Phoenix and Ecto's in-built telemetry features, learning how you can craft and send custom metrics to AppSignal for monitoring even more powerful use cases.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Monitor the Performance of Your Ruby on Rails Application Using AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Wed, 26 Jun 2024 12:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/monitor-the-performance-of-your-ruby-on-rails-application-using-appsignal-2bih</link>
      <guid>https://dev.to/appsignal/monitor-the-performance-of-your-ruby-on-rails-application-using-appsignal-2bih</guid>
      <description>&lt;p&gt;In the first part of this article series, we deployed a simple Ruby on Rails application to DigitalOcean's app platform. We also hooked up a Rails app to AppSignal, seeing how simple errors are tracked and displayed in AppSignal's Errors dashboard.&lt;/p&gt;

&lt;p&gt;In this part of the series, we'll dive into how to set up the following for your Ruby on Rails application using AppSignal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance monitoring&lt;/li&gt;
&lt;li&gt;Rails background jobs monitoring, including how to monitor simple API calls&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Notification alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get into it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Rails App Performance Using AppSignal
&lt;/h2&gt;

&lt;p&gt;When your uptime monitor shows that your app is up, it may be tempting to think that everything is okay. But, in reality, trouble could be brewing under the hood in the form of slow processes, unoptimized database queries, and long-running service calls.&lt;/p&gt;

&lt;p&gt;This is a very important matter when you consider that there's a &lt;a href="https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/mobile-page-speed-conversion-data/" rel="noopener noreferrer"&gt;correlation between slow-loading web pages and a low visitor conversion rate&lt;/a&gt;. In a nutshell, those slow-running processes will cost you a lot if left unchecked. But with so many moving parts to a Ruby on Rails app, the important questions are: what should you look for, and where?&lt;/p&gt;

&lt;p&gt;That's where AppSignal comes into play. Let's look at how you can use AppSignal to keep track of your Rails app's performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking Response Times
&lt;/h3&gt;

&lt;p&gt;From the default dashboard, AppSignal provides two graphs that can give you a quick overview of how slow (or fast) your Rails app is running: the &lt;em&gt;Throughput&lt;/em&gt; and &lt;em&gt;Response time&lt;/em&gt; graphs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fappsignal-performance-tracking-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fappsignal-performance-tracking-dashboard.png" alt="Rails performance tracking with Appsignal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Throughput&lt;/strong&gt; - This basically measures how many requests per second your app is currently processing (not to be confused with how many requests per second your app can handle overall). The basic rule of thumb is: the more requests per second, the better. However, if your app server handles too many requests per second, it might be time to scale your server resources to handle this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response time&lt;/strong&gt; - The average time a browser response takes in milliseconds. The more time a response takes, the worse your app is running. A good rule of thumb here is that if your Rails web app has sub-100ms response times, you can consider it fast, while 300ms+ response times can be considered slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's say we want to track our app's response times and throughput over a seven-day window. That's very easy to do — just go to the &lt;em&gt;Graphs&lt;/em&gt; sub-menu under &lt;em&gt;Performance&lt;/em&gt; located on the left-side menu, then use the time filter buttons on top of the graphs, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fappsignal-performance-graphs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fappsignal-performance-graphs.png" alt="Performance graphs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, you can easily see if your app runs slow on some days compared to others.&lt;/p&gt;

&lt;p&gt;While on this view, it's also important to check out the &lt;em&gt;Event groups&lt;/em&gt; graph, located below the &lt;em&gt;Response time&lt;/em&gt; and &lt;em&gt;Throughput&lt;/em&gt; graphs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fevent-groups-breakdown.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fevent-groups-breakdown.png" alt="Event groups breakdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This graph gives you details on how fast or slow your app is running in the controller layer and the view layer. From here, you can find out what's causing slow response times and fix the issues accordingly.&lt;/p&gt;

&lt;p&gt;Let's now switch gears to tracking database queries using AppSignal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking Database Queries
&lt;/h3&gt;

&lt;p&gt;It is generally true that you can squeeze a lot more speed from your app by optimizing response times and throughput. However, more often than not, slow, unoptimized database queries are the worst offenders when it comes to sluggish app behavior.&lt;/p&gt;

&lt;p&gt;Let's take an example of the infamous N+1 query. In the &lt;a href="https://github.com/iamaestimo/expense-tracker" rel="noopener noreferrer"&gt;expense tracker app&lt;/a&gt; introduced in part 1, the index method in the expenses controller looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controller/expenses_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExpensesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@expenses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Expense&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might look innocent, but if you take a look at the &lt;em&gt;Issues&lt;/em&gt; list in your AppSignal dashboard, you'll see something interesting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fn1-query-issue-list.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fn1-query-issue-list.png" alt="N+1 Query issue"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An N+1 query has been highlighted in the expenses controller's index method.&lt;/p&gt;

&lt;p&gt;If we dig further by clicking on the issue link, we get to an &lt;em&gt;Issue details&lt;/em&gt; screen like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fslow-query-explanation.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fslow-query-explanation.png" alt="Slow query explanation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how AppSignal offers an explanation of the issue, including a link to a more detailed blog post showing you how to deal with it.&lt;/p&gt;

&lt;p&gt;Let's move on and learn how AppSignal helps you out with background jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping Track of Rails Background Jobs with AppSignal
&lt;/h3&gt;

&lt;p&gt;Background jobs are a common feature in many production Ruby on Rails applications, with a variety of job queue processing gems and libraries for you to choose from.&lt;/p&gt;

&lt;p&gt;AppSignal supports tracking and monitoring various background job processing libraries, including Sidekiq, Que, Delayed Job, Resque, and others.&lt;/p&gt;

&lt;p&gt;Let's say we want a feature where users get daily currency exchange rates in their dashboard.&lt;/p&gt;

&lt;p&gt;For this to work, we need an API call to a service providing such rates (we'll use one that doesn't require too much upfront configuration or payment, like &lt;a href="https://www.exchangerate-api.com/docs/free" rel="noopener noreferrer"&gt;this one&lt;/a&gt;, to fetch currency rates). We also need a background job to queue the call to this service at regular intervals.&lt;/p&gt;

&lt;p&gt;The background processing gem we'll use in the expense tracker app is &lt;a href="https://github.com/bensheldon/good_job" rel="noopener noreferrer"&gt;GoodJob&lt;/a&gt;, but you're free to use whatever suits you.&lt;/p&gt;

&lt;p&gt;Let's create a background job to fetch rates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/fetch_rates.rb&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchRates&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:good_job&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;
    &lt;span class="c1"&gt;# fetch rates&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://open.er-api.com/v6/latest/USD'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'result'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"success"&lt;/span&gt;
      &lt;span class="no"&gt;Rate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;base_rate_name: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'base_code'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;eur: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rates'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'EUR'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;cad: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rates'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'CAD'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;aud: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rates'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'AUD'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;gbp: &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'rates'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'GBP'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then set GoodJob to run a cron for this job every few minutes (or however you see fit). AppSignal automatically detects the presence of the background service and includes a nifty filter so you can separate it from the default web dashboards:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fbackground-jobs-dashboards.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fbackground-jobs-dashboards.png" alt="Background jobs dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without the need for much tooling upfront, AppSignal will also detect API calls and keep track of any issues:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fbackground-jobs-slow-events.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fbackground-jobs-slow-events.png" alt="Slow events"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to dive deeper into monitoring Rails background jobs with AppSignal, I recommend you &lt;a href="https://docs.appsignal.com/ruby/integrations/active-job.html" rel="noopener noreferrer"&gt;check out the documentation&lt;/a&gt;. For now, let's turn our attention to uptime monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uptime Monitoring
&lt;/h2&gt;

&lt;p&gt;Another matter that concerns many Rails developers is the continuous availability of their application for end users. Any downtime might mean loss of revenue and have other negative consequences.&lt;/p&gt;

&lt;p&gt;Even so, you wouldn't want to poll the uptime status of your app manually. You can set up uptime monitoring using AppSignal.&lt;/p&gt;

&lt;p&gt;Setting it up is a breeze. Begin by clicking on the &lt;em&gt;Uptime monitoring&lt;/em&gt; link in the left-side menu.&lt;/p&gt;

&lt;p&gt;Then, when you click on the &lt;em&gt;create uptime monitor&lt;/em&gt; button, you should get a dialog similar to the one shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fuptime-monitor-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fuptime-monitor-1.png" alt="Create uptime monitor - one"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fuptime-monitor-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fuptime-monitor-2.png" alt="Create uptime monitor - two"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Complete your uptime monitor by adding the most important settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt; - Give your uptime monitor an appropriate name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt; - Provide the full URL to the uptime route. Beginning with Rails 7.1, the default uptime route is &lt;em&gt;&lt;a href="https://your-app-domain/up" rel="noopener noreferrer"&gt;https://your-app-domain/up&lt;/a&gt;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region&lt;/strong&gt; - Select the regions where you want to check for uptime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notification&lt;/strong&gt; - Choose the notification channel/s to receive alerts on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that done, go ahead and create the monitor!&lt;/p&gt;

&lt;p&gt;One thing to note with the AppSignal uptime monitor is that since polling is done almost every minute or so, it's very easy to hit your plan's limits. But there's a very good workaround to this which you can read more about &lt;a href="https://docs.appsignal.com/uptime-monitoring/setup.html" rel="noopener noreferrer"&gt;in their docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before moving on to how AppSignal can help with logging, there's another important feature to complete the uptime monitoring layer: free &lt;a href="https://docs.appsignal.com/uptime-monitoring/public-status-page.html#set-up" rel="noopener noreferrer"&gt;public status pages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Click on the &lt;em&gt;creating a public status&lt;/em&gt; page link as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fpublic-status-page-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fpublic-status-page-1.png" alt="Creating a public status page - step 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on the &lt;em&gt;New status page&lt;/em&gt; button, bringing you to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fpublic-status-page-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fpublic-status-page-3.png" alt="Creating a public status page - step 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fpublic-status-page-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Fpublic-status-page-4.png" alt="Creating a public status page - step 4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and fill in all the required information. Note that if you use a custom domain, you'll need to add a CNAME directive pointing to &lt;code&gt;cname.appsignal-status.com&lt;/code&gt; in your custom domain's DNS settings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Logging with AppSignal
&lt;/h2&gt;

&lt;p&gt;Logging is a relatively new feature in AppSignal, but one that is timely and welcome. Using AppSignal, you don't have to run application monitoring and logging as separate services.&lt;/p&gt;

&lt;p&gt;To get started with logging, make sure you are running the latest version of the AppSignal gem. If you have an older version of the gem, update it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle update appsignal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a new initializer and edit it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/appsignal_logging.rb&lt;/span&gt;

&lt;span class="n"&gt;appsignal_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Appsignal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appsignal_logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can access your app's logs like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Flogging-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Flogging-dashboard.png" alt="Logging dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that you also get a nice filter to filter log records by severity, as well as access to live logs.&lt;/p&gt;

&lt;p&gt;You can go through &lt;a href="https://docs.appsignal.com/logging/platforms/integrations/ruby.html" rel="noopener noreferrer"&gt;AppSignal's logging for Ruby documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Notable Features
&lt;/h2&gt;

&lt;p&gt;There are a couple of other notable features you can use in AppSignal: anomaly detection and custom metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anomaly Detection
&lt;/h3&gt;

&lt;p&gt;Let's say you are concerned with your app running out of memory. You can easily set up a trigger that sends an email notification if your app has a notable drop in available memory.&lt;/p&gt;

&lt;p&gt;Go to the &lt;em&gt;Anomaly detection&lt;/em&gt; link and create a new trigger, then configure it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Ftrigger-for-low-memory.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-06%2Ftrigger-for-low-memory.png" alt="Trigger for low memory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metric&lt;/strong&gt; - In my case, I am interested in memory usage, but you could choose any other metric that is specific to your use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger details&lt;/strong&gt; - Here, you'll configure the details relevant to your trigger. In my case, the trigger will fire if the host memory goes below 200MB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Custom Metrics
&lt;/h3&gt;

&lt;p&gt;AppSignal also gives you the tools to capture and visualize custom metrics. This feature alone warrants an entire article. You can read all about it in &lt;a href="https://blog.appsignal.com/2023/04/26/how-to-monitor-custom-metrics-with-appsignal.html" rel="noopener noreferrer"&gt;How to Monitor Custom Metrics with AppSignal&lt;/a&gt; and &lt;a href="https://docs.appsignal.com/metrics/custom.html" rel="noopener noreferrer"&gt;check out AppSignal's docs&lt;/a&gt; too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In part one of this article series, we set up a Rails app hosted on DigitalOcean and monitored it for errors using AppSignal.&lt;/p&gt;

&lt;p&gt;In this second and final part, we looked at some of AppSignal's great features for your Rails app, including performance monitoring, uptime monitoring, logging, and more.&lt;/p&gt;

&lt;p&gt;There's so much more to AppSignal than could effectively be covered by this article series. I highly encourage you to check it out for your Rails app.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>apm</category>
      <category>appsignal</category>
    </item>
    <item>
      <title>Getting Started: Your Ruby On Rails App Hosted On DigitalOcean With AppSignal</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Wed, 12 Jun 2024 08:45:16 +0000</pubDate>
      <link>https://dev.to/appsignal/getting-started-your-ruby-on-rails-app-hosted-on-digitalocean-with-appsignal-342c</link>
      <guid>https://dev.to/appsignal/getting-started-your-ruby-on-rails-app-hosted-on-digitalocean-with-appsignal-342c</guid>
      <description>&lt;p&gt;Imagine this: you’ve just finished working on your brand new Rails app and have deployed it to a cloud provider like DigitalOcean. Like any developer, you’re very proud of your work but you still have lots of questions, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How well your new app will handle traffic&lt;/li&gt;
&lt;li&gt;Whether the optimizations you put in place will actually work, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your goal is to provide the best user experience. You want to be notified whenever errors or other important events occur so you can take care of them fast.&lt;/p&gt;

&lt;p&gt;It would be great to have a setup that automatically monitors your application. Enter AppSignal! In this article, the first part of a two-part series, we'll set AppSignal up so that you can effectively monitor your Rails app hosted on DigitalOcean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A local installation of Ruby (this tutorial uses version 3.3.0).&lt;/li&gt;
&lt;li&gt;A local PostgreSQL installation (you can use a Docker version or a locally installed version).&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://cloud.digitalocean.com/registrations/new" rel="noopener noreferrer"&gt;DigitalOcean account&lt;/a&gt; to deploy the application.&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://appsignal.com/users/sign_up" rel="noopener noreferrer"&gt;AppSignal account&lt;/a&gt; (a free 30-day trial is available).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  An Introduction To Our Ruby On Rails App
&lt;/h2&gt;

&lt;p&gt;For this tutorial, we'll be using a simple Rails 7 expense tracker app. Users will be able to sign up and create entries for their personal expenses, to track their expenses over time.&lt;/p&gt;

&lt;p&gt;We'll deploy this app to DigitalOcean, then configure AppSignal's monitoring solution to keep track of what is going on under the hood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/iamaestimo/expense-tracker" rel="noopener noreferrer"&gt;You can grab the source code or fork the app from here&lt;/a&gt; to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Your Rails App To DigitalOcean
&lt;/h2&gt;

&lt;p&gt;We'll use DigitalOcean's app platform to deploy our application. The steps below assume that you've already forked the expense tracker app described above and that you have a DigitalOcean account ready to go.&lt;/p&gt;

&lt;p&gt;After logging in, we'll create an app and get it up and running. Since we want to control some parts of this process, though, the first step is to create a database for the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the App Database
&lt;/h3&gt;

&lt;p&gt;Click on the &lt;em&gt;Databases&lt;/em&gt; link on the left-hand menu, then click on the &lt;em&gt;Create Database&lt;/em&gt; link to create a new PostgreSQL database:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fcreate-new-app-database.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fcreate-new-app-database.png" alt="Create new app database"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing the database settings in the next screen, take note of the database connection string. You'll use it as an environment variable when creating the app in the next step:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Ffinalize-database-creation.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Ffinalize-database-creation.png" alt="Finalize database creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At this point, you'll likely get a warning that your database is open to all incoming connections. Follow the accompanying link to take care of this security warning.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating and Deploying the App
&lt;/h3&gt;

&lt;p&gt;Finally, create the app by first clicking on the &lt;em&gt;Apps&lt;/em&gt; link on the left side menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Flogin-do-app-platform.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Flogin-do-app-platform.png" alt="DigitalOcean app platform dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then connect the app's code repository resource as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fconnect-app-repo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fconnect-app-repo.png" alt="Connect app code repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Continue to the environment variables screen and insert the copied database URL string. Also, add the &lt;code&gt;RAILS_MASTER_KEY&lt;/code&gt; as an environment variable since it will be used in the deployment process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fapp-environment-variables.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fapp-environment-variables.png" alt="Setting up the environment variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, with these environment variables in place, click on the &lt;em&gt;Next&lt;/em&gt; button to deploy the app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fapp-deployment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fapp-deployment.png" alt="App deployed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the app successfully deployed, we'll now set up monitoring using AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up AppSignal to Monitor Your Rails App
&lt;/h2&gt;

&lt;p&gt;First, log in to your &lt;a href="https://appsignal.com/users/sign_in" rel="noopener noreferrer"&gt;AppSignal account&lt;/a&gt; and choose 'Ruby &amp;amp; Rails'.&lt;/p&gt;

&lt;p&gt;Now add the &lt;a href="https://github.com/appsignal/appsignal-ruby" rel="noopener noreferrer"&gt;AppSignal gem&lt;/a&gt; to the Rails app. This nifty gem will collect errors, exceptions, and relevant performance data, and port it over to AppSignal for analysis.&lt;/p&gt;

&lt;p&gt;Open up the app's Gemfile and add the gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"appsignal"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;bundle install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, you'll need to run the installation script that comes with your account-specific API key attached. When you run this install script, you should get something similar to the below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; bundle &lt;span class="nb"&gt;exec &lt;/span&gt;appsignal &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;redacted&amp;gt;

&lt;span class="c"&gt;#######################################&lt;/span&gt;
&lt;span class="c"&gt;## Starting AppSignal Installer      ##&lt;/span&gt;
&lt;span class="c"&gt;## --------------------------------- ##&lt;/span&gt;
&lt;span class="c"&gt;## Need help?  support@appsignal.com ##&lt;/span&gt;
&lt;span class="c"&gt;## Docs?       docs.appsignal.com    ##&lt;/span&gt;
&lt;span class="c"&gt;#######################################&lt;/span&gt;

Validating API key...
  API key valid!

Installing &lt;span class="k"&gt;for &lt;/span&gt;Ruby on Rails

  Your app&lt;span class="s1"&gt;'s name is: '&lt;/span&gt;ExpenseTracker&lt;span class="s1"&gt;'
  Do you want to change how this is displayed in AppSignal? (y/n): n
How do you want to configure AppSignal?
  (1) a config file
  (2) environment variables
  Choose (1/2): 1

Writing config file...
  Config file written to config/appsignal.yml

#####################################
## AppSignal installation complete ##
#####################################

  Sending example data to AppSignal...
  Example data sent!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of things to note when you run the script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You'll get the option to change your app's name as it will appear on AppSignal's dashboard or leave the default option.&lt;/li&gt;
&lt;li&gt;You'll also get the option to choose how you want AppSignal configured for your app. In my case, I went with the config file option, creating a config file in &lt;code&gt;config/appsignal.yml&lt;/code&gt;, with the below content:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/appsignal.yml&lt;/span&gt;

&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;defaults&lt;/span&gt;
  &lt;span class="na"&gt;push_api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;%=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ENV['APPSIGNAL_PUSH_API_KEY']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%&amp;gt;"&lt;/span&gt;

  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ExpenseTracker"&lt;/span&gt;

&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*defaults&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*defaults&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;push_api_key&lt;/code&gt; which connects the app to AppSignal.&lt;/li&gt;
&lt;li&gt;The app's name as it will appear on AppSignal.&lt;/li&gt;
&lt;li&gt;The environments in which AppSignal will monitor the app (in this case, both development and production environments).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, if you'd like to turn off AppSignal monitoring on the development environment, just set the &lt;code&gt;active&lt;/code&gt; flag to &lt;code&gt;false&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;development&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*defaults&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Both the config file and environment variable options do the same thing. They define how AppSignal will connect to your app, the app name, and which environment will be monitored.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Assuming everything goes to plan, you should see a screen showing that AppSignal is receiving data from your app!&lt;/p&gt;

&lt;h2&gt;
  
  
  An Introduction to AppSignal's Dashboard for Rails
&lt;/h2&gt;

&lt;p&gt;Now that everything is set up correctly and AppSignal is receiving data from your app, you can access the default app monitoring dashboard view as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fdefault-appsignal-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Fdefault-appsignal-dashboard.png" alt="Default Appsignal dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Tip&lt;/strong&gt;: If you set up monitoring for both development and production environments, you can easily switch between them from the selection shown by the arrow.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From the default view, you have access to the following default charts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error rate&lt;/strong&gt; - This will show the rate of errors occurring in your app per unit of time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput&lt;/strong&gt; - Here, you'll get a snapshot of the throughput your app is able to handle in terms of requests per minute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response time&lt;/strong&gt; - This will show your app's response times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latest open errors&lt;/strong&gt; - This section will list the latest errors that will have happened within your app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latest open performance measurements&lt;/strong&gt; - This section lists the latest performance measurements, including method calls, API requests, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we'll see how to set up proper error tracking for a Rails application using AppSignal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Errors with AppSignal
&lt;/h2&gt;

&lt;p&gt;There are more than ten error types that could affect a running Ruby on Rails app. Obviously, some are more common than others. In this section, we'll simulate a few of these errors and see how AppSignal handles them. Let's start with a simple example first.&lt;/p&gt;

&lt;p&gt;Since the expense tracker app is using Devise for authentication, let's add a check for whether a user is signed in. Instead of using the recommended &lt;code&gt;user_signed_in?&lt;/code&gt; method, let's use the erroneous &lt;code&gt;user_logged_in?&lt;/code&gt; method, which should trigger an &lt;code&gt;ActionView::Template::Error&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/views/shared/_navbar.html.erb --&amp;gt;&lt;/span&gt;
...
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_logged_in?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Logout'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destroy_user_session_path&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;new_user_session_path&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    Login
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy this change, reload the production app, then go into the production environment dashboard in AppSignal and watch how this error shows up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Faction-template-error.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Faction-template-error.png" alt="ActionView template error on AppSignal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AppSignal makes it a breeze to get more details on any errors that happen in an application. For example, we can get details about the &lt;em&gt;ActionView&lt;/em&gt; template error by clicking on it from the 'Latest Open Errors' dashboard panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Faction-template-error-details.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Faction-template-error-details.png" alt="ActionView template error details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can clearly see that the error is caused by an undefined method which points us in the right direction to fix it.&lt;/p&gt;

&lt;p&gt;AppSignal's Errors dashboard also gives you extra information through the &lt;em&gt;Logbook&lt;/em&gt; and &lt;em&gt;Settings&lt;/em&gt; panels:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Faction-template-error-details-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-05%2Faction-template-error-details-2.png" alt="AppSignal error dashboard details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logbook&lt;/strong&gt; - Here, you or your team members can add comments to an error being tracked by AppSignal. The Logbook panel also shows a chronological breakdown of any actions taken regarding the error in question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings&lt;/strong&gt; - From this panel, you can assign an error to another team member, change alert settings (we'll check these out in detail in the second part of this tutorial), and set error severity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's it for this part of the series!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we deployed a simple Rails application to DigitalOcean's app platform and hooked it up to AppSignal's application monitoring platform. We also went through how errors are monitored and displayed in AppSignal's Errors dashboard.&lt;/p&gt;

&lt;p&gt;Obviously, this is just scratching the surface of what's possible with AppSignal. In the second part of this series, we'll dive deeper into performance measurements, anomaly detection, uptime monitoring, and logging.&lt;/p&gt;

&lt;p&gt;In the meantime, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Should You Use Ruby on Rails or Hanami?</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Wed, 08 May 2024 10:23:08 +0000</pubDate>
      <link>https://dev.to/appsignal/should-you-use-ruby-on-rails-or-hanami-2e35</link>
      <guid>https://dev.to/appsignal/should-you-use-ruby-on-rails-or-hanami-2e35</guid>
      <description>&lt;p&gt;Ruby on Rails is the most popular web framework in the Ruby ecosystem and has a large user base, ranging from freelancers to large established companies. With an active user community and wide-ranging documentation, it can be used to build everything from simple applications to complex web platforms.&lt;/p&gt;

&lt;p&gt;That said, a new contestant is taking on Rails’ dominance for the full-stack Ruby framework title: Hanami. It is a fast, modular Ruby framework with improved performance and maintainability compared to Rails.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore the strengths and weaknesses of each framework in terms of performance, features, testing, and more. So whether you are looking to build a customer-facing web app, an internal tool, or a massively scalable API, you should leave being better informed about what to use for your next project.&lt;/p&gt;

&lt;p&gt;Let's get started with a brief introduction to each framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Ruby on Rails
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/getting_started.html" rel="noopener noreferrer"&gt;Ruby on Rails&lt;/a&gt; is the most well-known Ruby web application development framework with a mission to enhance developer productivity (by making a bunch of assumptions about how apps should be built, often referred to as the "Rails way").&lt;/p&gt;

&lt;p&gt;Some of these assumptions include making sure developers do not spend time configuring stuff, or what is termed as "convention over configuration", and an emphasis on using DRY ("don't repeat yourself") principles. DRY principles encourage a developer to avoid repeating code over and over again, but instead use single and focused representations of app functionality to ensure maintainability and organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Hanami
&lt;/h2&gt;

&lt;p&gt;While Rails is very well-known in the Ruby community, &lt;a href="https://hanamirb.org/" rel="noopener noreferrer"&gt;Hanami&lt;/a&gt; is less so. It's a fairly new modern Ruby framework trying to take on Rails' dominance of the full-stack web framework space.&lt;/p&gt;

&lt;p&gt;Hanami is built from the ground up to have a small memory footprint and a focus on modularity, which, in turn, makes for a very fast and nimble framework.&lt;/p&gt;

&lt;p&gt;Obviously, these brief introductions won't really give you all the information you need to decide on the framework that suits you best. For that, we'll need to dive deeper into each, starting with how they are structured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structure and Architecture of Rails and Hanami
&lt;/h2&gt;

&lt;p&gt;Rails and Hanami are somewhat similar in that both are Ruby frameworks. However, how each is built and their application architecture is where you'll find most of the differences.&lt;/p&gt;

&lt;p&gt;For starters, with Rails, you have fewer files or &lt;em&gt;abstractions&lt;/em&gt; (the building blocks you use to build your app), which tend to grow in size as you develop your app. On the other hand, Hanami takes abstractions to a whole new level, with lots of files that tend to be smaller in size.&lt;/p&gt;

&lt;p&gt;The diagrams below illustrate the point more clearly. Let's start with Rails.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Frails-app-structure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Frails-app-structure.png" alt="Rails app structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now compare the Rails abstraction diagram with the Hanami one below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Fhanami-app-structure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Fhanami-app-structure.png" alt="Hanami app structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, each framework generally follows the &lt;em&gt;model-view-controller&lt;/em&gt; (MVC) structure. However, Hanami takes abstraction to the next level.&lt;/p&gt;

&lt;p&gt;Here's a simplified outline of how each framework is organized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routes&lt;/strong&gt; - Each framework has a routes definition with your app's endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controllers vs. actions&lt;/strong&gt; - In Rails, you get controllers with one or a couple of actions in them. Controllers receive and respond to route requests by directing them to the relevant actions. In Hanami, there are no controllers. Instead, you go straight to self-contained actions (each action has its own self-contained class).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Models and persistence&lt;/strong&gt; - The Rails model layer takes care of data validation and database communication, including any queries your app may have.
In Hanami, each of these functionalities is contained in a different part of the persistence layer: data validations are handled by &lt;em&gt;contracts&lt;/em&gt;, database communication is done by the &lt;em&gt;repository&lt;/em&gt; (repo), while relations and scoping are the responsibility of &lt;em&gt;relations&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View rendering&lt;/strong&gt; - As with the persistence layer, Ruby on Rails views tend to contain everything you need for rendering data to the outside world in one place. This includes all the HTML structure, view helpers, and view logic. When it comes to view rendering in Hanami, things are more abstract. For starters, you have &lt;em&gt;views&lt;/em&gt; that utilize any view helpers the app has and also render the &lt;em&gt;template&lt;/em&gt;. The &lt;em&gt;template&lt;/em&gt; handles the actual HTML structure, while the &lt;em&gt;parts&lt;/em&gt; take care of any presentation logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, what does all this mean when it comes to building an app? With fewer abstractions, Rails is a good choice for getting your app off the ground and is more beginner-friendly (as we shall see later on in this article). With Rails, you can build very robust monolith apps, but your code will get more and more complex as you scale. On the other hand, Hanami's more complex structure can be daunting to learn, but it will allow you to build massively scalable applications and could make for much better code organization.&lt;/p&gt;

&lt;p&gt;Next, let's take a look at each framework's ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem and Community
&lt;/h2&gt;

&lt;p&gt;As we mentioned earlier, Rails has a more established framework than Hanami. Rails has been around for longer and, as such, has a bigger and more mature community.&lt;/p&gt;

&lt;p&gt;The differences in ecosystem and community are summarized below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; - It doesn't matter what you're trying to build, if you use Rails, you have access to very well-done documentation. You are guided through all parts of an app build, from authentication, to data persistence, view presentation, and everything in between. Additionally, a wide range of third-party tutorials cover almost everything imaginable in terms of building an application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, things are not so established on the Hanami side of things. Being a relatively newer framework, Hanami has a comparatively limited documentation base. The Hanami team has done an impressive job with the official guides, but compared to the Rails documentation base, it doesn't come close.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt; - Here, as with the documentation, Rails is the clear winner. Rails has been around much longer than Hanami, so it's been adopted the most. It doesn't matter whether you are a beginner or advanced developer, you'll find a Rails community on relevant subreddits, Slack groups, Discords, and so forth. On the other hand, Hanami is still growing, meaning it has a much smaller community.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gems and libraries&lt;/strong&gt; - Since both Hanami and Rails are Ruby frameworks, it could be argued that gems that work in Rails will work in Hanami. Although this is technically true, you'll often find that Rails has a more established base of gems and libraries for all sorts of specialized functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, because of Hanami's focus on abstractions and specialization, it has some very advanced gems that could take your Rails apps to the next level. For example, the default &lt;a href="https://dry-rb.org/" rel="noopener noreferrer"&gt;dry-rb gems&lt;/a&gt; in Hanami could bring better code organization and abstraction when used in Rails apps.&lt;/p&gt;

&lt;p&gt;Moving on, let's compare each framework's learning curve and adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ease of Use, Adoption, Governance, and Learning Curve
&lt;/h2&gt;

&lt;p&gt;For the very reasons outlined in the previous section, Ruby on Rails easily beats Hanami in terms of ease of use, industry adoption, and learning curve, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Learning&lt;/strong&gt; - Imagine you're a complete beginner who's looking to learn a new programming language. Your very first thought will be to check out online learning resources. If a framework has widely available learning materials covering the app-building process from start to finish, you'll likely choose that language over others. And because Rails has a more established documentation base, it trumps Hanami as the beginner's choice of framework. In addition, Rails is much more friendly for beginners to pick up since it makes so many assumptions under the hood (compared to Hanami, which offers a more abstract way of building apps). Hanami's abstractions can be daunting even for established Rails developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry adoption&lt;/strong&gt; - Without including the adoption of other popular and more established frameworks like Python, React, C#, and others, if we consider the adoption of Ruby frameworks, Rails easily eclipses Hanami. The &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;Rails homepage&lt;/a&gt; lists some big-name organizations using the framework. On the other hand, as the new kid on the block, Hanami is not so widely adopted. We'll have to wait and see whether that will change in the future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job market prospects&lt;/strong&gt; - In terms of job prospects, you'll find more job openings for Rails developers than Hanami developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governance&lt;/strong&gt; - Another important aspect that should not be overlooked is governance. A changing landscape guides how open-source frameworks evolve and advance. However, more than that, the core team members of these frameworks often wield immense powers and can dictate what goes into a framework, how it develops, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good example is &lt;a href="https://twitter.com/dhh/status/1743664413964374505" rel="noopener noreferrer"&gt;January's announcement&lt;/a&gt; by David Heinemeier Hansson (DHH), the creator of Rails. He said that, in the future, he would push for Rails to offer first-class support for building full-stack progressive web applications and native notifications. These features would make Rails very attractive to mobile developers.&lt;/p&gt;

&lt;p&gt;In reaction, many developers, some outside the Ruby ecosystem and even others who had dropped Rails over the years, overwhelmingly responded in the positive, saying they would gladly pick up the framework or learn it if DHH kept his word. This example just goes to show you that governance is a very important consideration when deciding what framework to pick.&lt;/p&gt;

&lt;p&gt;Let's now switch gears and look into something more technical, application performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance: Ruby on Rails vs. Hanami
&lt;/h2&gt;

&lt;p&gt;As a developer, you will be concerned about your app's reliability, responsiveness, and how it will utilize server resources once deployed in production. These are fundamental concerns that can greatly affect your choice of framework.&lt;/p&gt;

&lt;p&gt;You could use many methods to run performance tests on your app, one of the most popular being &lt;a href="https://jmeter.apache.org/" rel="noopener noreferrer"&gt;Apache JMeter&lt;/a&gt;. Let's use the &lt;a href="https://web-frameworks-benchmark.netlify.app/result?asc=0&amp;amp;f=hanami,rails&amp;amp;order_by=level64" rel="noopener noreferrer"&gt;benchmark numbers&lt;/a&gt; to compare Hanami and Rails.&lt;/p&gt;

&lt;p&gt;Here's a quick benchmark test showing the number of requests each framework can handle per second:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Freq-per-second.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Freq-per-second.png" alt="Request per second Rails vs Hanami"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hanami beats Rails hands down, with the ability to handle 3 times more requests than Rails can handle.&lt;/p&gt;

&lt;p&gt;This next screenshot shows the average latency values for each framework:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Faverage-latency.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.appsignal.com%2Fimages%2Fblog%2F2024-04%2Faverage-latency.png" alt="Average latency comparison Rails vs Hanami"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, Hanami beats Rails with an average latency time that's about 3 times shorter.&lt;/p&gt;

&lt;p&gt;If you're looking for a Ruby framework that is blazingly fast (let's say, you need to work on a fast and hugely scalable API), then you'll be hard-pressed to find anything better than Hanami in the Ruby landscape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;When it comes to testing code, both frameworks are very much comparable since you can test either using the versatile &lt;a href="https://rspec.info/" rel="noopener noreferrer"&gt;RSpec&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;RSpec is included via the &lt;code&gt;hanami-rspec&lt;/code&gt; gem for Hanami apps, whereas you need to install the &lt;code&gt;rspec-rails&lt;/code&gt; gem to use it in your Rails app. The example below shows a very basic test spec that is included with your new Hanami app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/requests/root_spec.rb&lt;/span&gt;

&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :request&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"is successful"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;

    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_successful&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello from Hanami"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it with &lt;code&gt;$ bundle exec rspec spec/requests/root_spec.rb&lt;/code&gt; should result in a passing test (assuming you've not edited the default view template):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.01812 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 0.47734 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
1 example, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Rails side, you need to handle some things by yourself. Firstly, you need to add the RSpec gem to the development/test block in the &lt;code&gt;Gemfile&lt;/code&gt; as shown below, then run &lt;code&gt;bundle&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'rspec-rails'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to run the RSpec install with the command &lt;code&gt;bundle exec rails generate rspec:install&lt;/code&gt; to get it ready for your Rails project.&lt;/p&gt;

&lt;p&gt;Finally, before writing and running any tests, you'll likely need to install another gem to define test data for your Rails app: &lt;a href="https://github.com/thoughtbot/factory_bot_rails" rel="noopener noreferrer"&gt;FactoryBot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And assuming you've set up your test suite properly and defined a few tests, you can run a test just as you would in Hanami, with &lt;code&gt;bundle exec rspec spec/models/user_spec.rb&lt;/code&gt;. You can get your test results just as you would with a Hanami app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Finished &lt;span class="k"&gt;in &lt;/span&gt;0.04421 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 0.88106 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
1 example, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's see how the frameworks compare when it comes to deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Nowadays, there are lots of options for deploying Ruby apps to production.&lt;/p&gt;

&lt;p&gt;To begin with, you could go with a Platform-as-a-Service (PaaS) provider like &lt;a href="https://www.heroku.com/ruby" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;, or &lt;a href="https://fly.io/" rel="noopener noreferrer"&gt;Fly&lt;/a&gt; for a more seamless experience. You can also do a bit of DevOps: set up a Docker installation on a VPS and deploy your app there.&lt;/p&gt;

&lt;p&gt;Whichever option you choose, you can expect deployment to be relatively similar in both cases. The only caveat is the lack of extensive documentation and tutorials for deploying Hanami apps.&lt;/p&gt;

&lt;p&gt;As we pointed out before, being a newer framework, Hanami still doesn't have extensive documentation covering the deployment process and the challenges that might crop up. If you don't mind hacking around this, then you should be okay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this article, we've looked at two Ruby frameworks — Ruby on Rails and Hanami — comparing them in terms of features, architecture, performance, and more. Ultimately, we have to answer the question: "Which framework should I use, and why?". We can summarize as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're a beginner Ruby developer, start with Rails: it's easier to pick up and learn. If you're a more advanced Ruby developer, develop a Hanami skillset, as it will help you develop even more robust Ruby applications.&lt;/li&gt;
&lt;li&gt;If you need to develop an app that should be fast, say an API serving several clients, Hanami's small memory footprint, blazing fast responses, and low latency will serve you well. However, if you just need to develop a monolithic full-stack app to validate a SaaS idea, Rails will definitely get you there faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately, what you end up choosing is really up to you. It isn't a bad idea to acquire skills in both frameworks. That way, you can decide on the best framework to go for, depending on the needs of your project.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic" rel="noopener noreferrer"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. Did you know AppSignal has an integration for &lt;a href="https://www.appsignal.com/ruby/rails-monitoring" rel="noopener noreferrer"&gt;Rails&lt;/a&gt; and &lt;a href="https://www.appsignal.com/ruby/hanami-monitoring" rel="noopener noreferrer"&gt;Hanami&lt;/a&gt;?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>hanami</category>
    </item>
    <item>
      <title>Active Record or Sequel: Which Best Fits The Needs of Your Ruby App?</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Wed, 20 Mar 2024 14:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/active-record-or-sequel-which-best-fits-the-needs-of-your-ruby-app-222i</link>
      <guid>https://dev.to/appsignal/active-record-or-sequel-which-best-fits-the-needs-of-your-ruby-app-222i</guid>
      <description>&lt;p&gt;When it comes to choosing an object-relational mapping (ORM) library for your Ruby application, Active Record is usually the favorite choice. It's an easy-to-use ORM library that allows for lots of data wrangling without resorting to SQL. All the same, you might wonder: "Is Active Record the only Ruby ORM library I can use?"&lt;/p&gt;

&lt;p&gt;In this article, we'll compare some Active Record features to its lesser-known but powerful cousin, Sequel. There are too many points of comparison to cover everything (such as how each library handles &lt;em&gt;CRUD&lt;/em&gt; operations, table joins, associations, database replication and sharding, etc). Instead, we'll scratch the surface of a few database operations — namely, filtering, database locking, and transactions — and show how each library handles them.&lt;/p&gt;

&lt;p&gt;By the end, you'll have a better idea about how to use each library's strengths to the fullest.&lt;/p&gt;

&lt;p&gt;But first, let's learn what object-relational mapping is all about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Object-Relational Mapping (ORM)?
&lt;/h2&gt;

&lt;p&gt;Object-relational mapping is a way to pass data between an application and its data store, usually a relational database like SQLite, PostgreSQL, or MySQL. Without ORM techniques, you would be forced to write raw SQL queries for all operations to get data in or out of your Ruby app's database. But with an ORM, you get access to class objects and methods, making it much easier to read/write data to your database without using raw SQL.&lt;/p&gt;

&lt;p&gt;Let's introduce the two ORM libraries we're looking at, starting with Active Record, and then moving on to Sequel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Active Record and Sequel for Ruby
&lt;/h2&gt;

&lt;p&gt;Let's quickly examine what Active Record and Sequel can do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Active Record
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.rubydoc.info/gems/activerecord"&gt;Active Record&lt;/a&gt; is the most well-known Ruby object-relational mapping library. It defines class objects that are directly mapped to tables in a database and provides convenient methods for manipulating data using these classes.&lt;/p&gt;

&lt;p&gt;The library was first described by the pioneering computer scientist Martin Fowler in his book &lt;em&gt;Patterns of Enterprise Architecture&lt;/em&gt;. It comes bundled with Rails, which could explain why it's more popular than Sequel. That said, Active Record is suitable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object-oriented database operations&lt;/li&gt;
&lt;li&gt;Performing validations on models before persisting them in the database&lt;/li&gt;
&lt;li&gt;Representing models, their data, and relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sequel
&lt;/h3&gt;

&lt;p&gt;On the other hand, &lt;a href="https://github.com/jeremyevans/sequel"&gt;Sequel&lt;/a&gt; is the lesser-known of the two. It includes a powerful DSL for manipulating data within a variety of databases.&lt;/p&gt;

&lt;p&gt;Sequel can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Represent database records as Ruby objects&lt;/li&gt;
&lt;li&gt;Handle simple and even complex associations in a concise way&lt;/li&gt;
&lt;li&gt;Do database sharding, database replications, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One standout feature that makes Sequel so powerful is its &lt;a href="https://sequel.jeremyevans.net/rdoc/files/doc/dataset_basics_rdoc.html"&gt;&lt;em&gt;dataset&lt;/em&gt;&lt;/a&gt;. A dataset is Sequel's way of directly representing an SQL query and making it readily available for a developer to use.&lt;/p&gt;

&lt;p&gt;Of course, a lot more could be said of each of these ORM libraries, but we'll leave it at that. Let's dive straight into our first example to see how Active Record and Sequel deal with record filtering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filtering Records
&lt;/h2&gt;

&lt;p&gt;Filtering data is the process of refining data results by applying specific filters. By using filters, you can fetch exactly what you want from a dataset. Let's see how each ORM library achieves this, starting with Active Record.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Active Record
&lt;/h3&gt;

&lt;p&gt;Let's say you want to filter data to return all orders of products priced under $20. How would you achieve this using Active Record?&lt;/p&gt;

&lt;p&gt;You can apply &lt;em&gt;conditions&lt;/em&gt; to your Active Record query. Active Record has many methods that act as conditions for filtering data, including &lt;code&gt;where&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, and &lt;code&gt;where.not&lt;/code&gt;. While it's not possible to get into all of the conditions in this article, we'll sample a few and see how they stack up against Sequel's filter methods.&lt;/p&gt;

&lt;p&gt;For Active Record, you'll first need to set up the proper model associations like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models/order.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:product&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the Product model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models/product.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for the Active Record query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;product_orders_under_20 &lt;span class="o"&gt;=&lt;/span&gt; Order.joins&lt;span class="o"&gt;(&lt;/span&gt;:product&lt;span class="o"&gt;)&lt;/span&gt;.where&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.price &amp;lt; ?'&lt;/span&gt;, 20&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we've used a &lt;em&gt;joins&lt;/em&gt; to include results from the products table and then applied a &lt;em&gt;where&lt;/em&gt; condition to filter for the products that fit our desired criteria.&lt;/p&gt;



&lt;h3&gt;
  
  
  Using Sequel
&lt;/h3&gt;

&lt;p&gt;Sequel's use of SQL representations in the form of datasets makes filtering a breeze. Let's consider how Sequel would handle the example we used for Active Record.&lt;/p&gt;

&lt;p&gt;Just like we did with Active Record, you need to define some models first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models/product.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="n"&gt;one_to_many&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Order&lt;/code&gt; model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models/order.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="n"&gt;many_to_one&lt;/span&gt; &lt;span class="ss"&gt;:product&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is how you'd run the query with Sequel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;product_orders_under_20 &lt;span class="o"&gt;=&lt;/span&gt; DB[:orders].join&lt;span class="o"&gt;(&lt;/span&gt;:products, &lt;span class="nb"&gt;id&lt;/span&gt;: :product_id&lt;span class="o"&gt;)&lt;/span&gt;.where &lt;span class="o"&gt;{&lt;/span&gt; price &amp;lt; 20 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next up, let's look at database locks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Locking
&lt;/h2&gt;

&lt;p&gt;Imagine you have a content management system where a user with the editor role can edit other users' posts. Let's say there are several editors in the organization and it just so happens that two editors end up working on the same post at the same time. Such a scenario raises several questions, such as which editor's work is saved, assuming both commit changes at the same time.&lt;/p&gt;

&lt;p&gt;This challenge is solved through the use of database locks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Active Record
&lt;/h3&gt;

&lt;p&gt;Active Record allows for two types of locks: optimistic locks and pessimistic locks. In optimistic locking, you need to include a version column in the respective database table. This is so that a record is checked against a matching version to ensure that another user or process doesn't modify it. If it is, an error is raised.&lt;/p&gt;

&lt;p&gt;The example below shows how optimistic locking happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;invoice1 &lt;span class="o"&gt;=&lt;/span&gt; Invoice.find&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
invoice2 &lt;span class="o"&gt;=&lt;/span&gt; Invoice.find&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;

invoice1.total_amount &lt;span class="o"&gt;=&lt;/span&gt; 100
invoice1.save &lt;span class="c"&gt;# 100&lt;/span&gt;

invoice2.total_amount &lt;span class="o"&gt;=&lt;/span&gt; 500
invoice2.save &lt;span class="c"&gt;# raises ActiveRecord::StaleObjectError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it comes to pessimistic locks, Active Record can implement them at the row level if your database allows for it. There are several ways of achieving pessimistic locking with Active Record, but we won't go into the details now. Instead, the example below should give you an idea of how this is done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Invoice.lock.find&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# applies a pessimistic lock on the record so that an UPDATE is done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Sequel
&lt;/h3&gt;

&lt;p&gt;On the other hand, Sequel implements optimistic locking through a &lt;a href="https://sequel.jeremyevans.net/plugins.html"&gt;plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You first add the plugin to the model like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# invoice.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:optimistic_locking&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test this with two concurrent queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;invoice1 &lt;span class="o"&gt;=&lt;/span&gt; Invoice.find&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
invoice2 &lt;span class="o"&gt;=&lt;/span&gt; Invoice.find&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;

invoice1.update&lt;span class="o"&gt;(&lt;/span&gt;status: &lt;span class="s1"&gt;'Sent'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# record will be updated&lt;/span&gt;

invoice2.update&lt;span class="o"&gt;(&lt;/span&gt;status: &lt;span class="s1"&gt;'Void'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# raises Sequel::NoExistingObject error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For pessimistic locking, Sequel implements the lock when the records (or record) are fetched using the following methods: &lt;code&gt;lock_style&lt;/code&gt;, &lt;code&gt;lock&lt;/code&gt;, and &lt;code&gt;for_update&lt;/code&gt;. Again, we won't provide details on this. You can read &lt;a href="https://sequel.jeremyevans.net/documentation.html"&gt;the documentation&lt;/a&gt; or &lt;a href="https://sequel.jeremyevans.net/2010/03/09/pessimistic-locking.html"&gt;Jeremy Evans' Pessimistic Locking blog post&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;Here's an example using the &lt;code&gt;for_update&lt;/code&gt; method to implement a pessimistic lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dataset &lt;span class="o"&gt;=&lt;/span&gt; DB[:invoices]
invoice1 &lt;span class="o"&gt;=&lt;/span&gt; dataset.where&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;: 1&lt;span class="o"&gt;)&lt;/span&gt;.for_update &lt;span class="c"&gt;# will apply a pessimistic lock on invoice1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's take a look at database transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Transactions
&lt;/h2&gt;

&lt;p&gt;To understand the concept of database transactions, consider a peer-to-peer lending app where Person A sends some money to Person B who can then withdraw the money and use it. Here, the app needs to first withdraw money from Person A's account and then send that amount over to Person B.&lt;/p&gt;

&lt;p&gt;In such a scenario, you wouldn't want the app to send money to Person B if there's an error with the withdrawal from Person A's account. The ideal solution would be to wrap the two processes in a "database transaction", so if one of the processes fails (usually the first one), then the database can be rolled back to its original state before any of the transactions happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Active Record
&lt;/h3&gt;

&lt;p&gt;With Active Record, implementing these queries without the use of database transactions might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;person_a &lt;span class="o"&gt;=&lt;/span&gt; Person.find_by&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
person_b &lt;span class="o"&gt;=&lt;/span&gt; Person.find_by&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

withdrawal &lt;span class="o"&gt;=&lt;/span&gt; person_a.accounts.first.withdraw&lt;span class="o"&gt;(&lt;/span&gt;250&lt;span class="o"&gt;)&lt;/span&gt;
person_b.accounts.first.account_total &lt;span class="c"&gt;# 100&lt;/span&gt;

person_b.find&lt;span class="o"&gt;(&lt;/span&gt;B&lt;span class="o"&gt;)&lt;/span&gt;.accounts.first.deposit&lt;span class="o"&gt;(&lt;/span&gt;withdrawal&lt;span class="o"&gt;)&lt;/span&gt;
person_b.accounts.first.account_total &lt;span class="c"&gt;# 450&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this could work, you also need to account for something going wrong with the withdrawal and take steps to take care of it. Let's now use a database transaction to improve this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;a_account &lt;span class="o"&gt;=&lt;/span&gt; Person.find_by&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.accounts.first
b_account &lt;span class="o"&gt;=&lt;/span&gt; Person.find_by&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.accounts.first

Account.transaction &lt;span class="k"&gt;do
  &lt;/span&gt;a_account.withdraw&lt;span class="o"&gt;(&lt;/span&gt;250&lt;span class="o"&gt;)&lt;/span&gt;
  b_account.deposit&lt;span class="o"&gt;(&lt;/span&gt;250&lt;span class="o"&gt;)&lt;/span&gt;
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use &lt;code&gt;ActiveRecord::Transactions&lt;/code&gt; to wrap the two queries in a transaction so that the database can be rolled back if something goes wrong.&lt;/p&gt;

&lt;p&gt;What about Sequel?&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Sequel
&lt;/h3&gt;

&lt;p&gt;Just like in Active Record, if you want to implement transactions in Sequel, you'll need to be explicit (although there are situations where this is enabled by default).&lt;/p&gt;

&lt;p&gt;With the same example we used for Active Record, this is how to wrap our queries using a transaction in Sequel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;DB.transaction &lt;span class="k"&gt;do
  &lt;/span&gt;Person.first&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.accounts.first.withdraw&lt;span class="o"&gt;(&lt;/span&gt;250&lt;span class="o"&gt;)&lt;/span&gt;
  Person.first&lt;span class="o"&gt;(&lt;/span&gt;name: &lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.accounts.first.deposit&lt;span class="o"&gt;(&lt;/span&gt;250&lt;span class="o"&gt;)&lt;/span&gt;
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this example, we see that Sequel's dataset feature allows for complex transactional queries to be built using a few commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  When To Choose Active Record Vs. Sequel for Ruby
&lt;/h2&gt;

&lt;p&gt;When should you use Active Record, and when might Sequel be a better choice? Here's a quick summary of each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Record is very beginner-friendly, in that, it conveniently hides complex SQL queries under the hood. On the other hand, if you are an advanced user who's comfortable using SQL, then this may be a bit limiting. Additionally, if you're looking for more powerful features like proxying queries, you'll likely need to add complementary gems on top of Active Record.&lt;/li&gt;
&lt;li&gt;Sequel is very powerful, especially for those who master its dataset feature. That said, you can expect a steeper learning curve compared to Active Record. If you get through that, though, you will be rewarded with a flexible ORM that is perfect for almost anything you could throw at it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this article, we've compared two Ruby object-relational mapping libraries, Active Record and Sequel. We explored how each handles filtering, database locking, and transactions. Finally, we touched on when you might want to use one ORM over the other.&lt;/p&gt;

&lt;p&gt;Until next time, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. Did you know that AppSignal offers an Active Record integration? &lt;a href="https://www.appsignal.com/ruby/active-record-monitoring"&gt;Find out more&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Advanced Multi-tenancy for Elixir Applications Using Ecto</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Tue, 19 Dec 2023 11:16:25 +0000</pubDate>
      <link>https://dev.to/appsignal/advanced-multi-tenancy-for-elixir-applications-using-ecto-41i</link>
      <guid>https://dev.to/appsignal/advanced-multi-tenancy-for-elixir-applications-using-ecto-41i</guid>
      <description>&lt;p&gt;Welcome to part two of this series. In the previous tutorial, we learned about multi-tenancy, including different multi-tenancy implementation strategies. We also started building a multi-tenant Phoenix link shortening app and added basic user authentication.&lt;/p&gt;

&lt;p&gt;In this final part of the series, we'll build the link resource, associate users to links, and set up the redirect functionality in our app. As we finalize the app build, we'll learn about features (like Ecto custom types) that make Phoenix such a powerful framework for Elixir.&lt;/p&gt;

&lt;p&gt;Let's now turn our attention to link shortening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding &lt;code&gt;Link&lt;/code&gt; Resources
&lt;/h2&gt;

&lt;p&gt;As a reminder, here's how link shortening will happen in our app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logged-in user enters a long URL&lt;/li&gt;
&lt;li&gt;A random string is generated and associated with the long URL&lt;/li&gt;
&lt;li&gt;Whenever this short URL is visited, the app will keep a tally of the number of visits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll get started by generating a controller, schema, migration, and views for a &lt;code&gt;Link&lt;/code&gt; resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.gen.html Links Link links url:string visits:integer account_id:references:accounts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should generate most of the boilerplate code we need to work with the &lt;code&gt;Link&lt;/code&gt; resource.&lt;/p&gt;

&lt;p&gt;You may notice that we haven't included a field for the short random string referencing the long URL. We'll cover this next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Short URLs Using the Ecto Custom Type in Phoenix
&lt;/h2&gt;

&lt;p&gt;By default, Ecto models have an &lt;code&gt;id&lt;/code&gt; field used as a model's primary key. Usually, it's in the form of an integer or, in some cases, a &lt;a href="https://hexdocs.pm/ecto/0.13.0/Ecto.Schema.html"&gt;binary ID&lt;/a&gt;. In either case, when present in a model, this field is automatically incremented for every additional model created in an app.&lt;/p&gt;

&lt;p&gt;A core function of our app is generating short, unique strings to represent long URLs. We could write a custom function to generate such strings, but since we are on a quest to learn Elixir, let's use a more creative approach.&lt;/p&gt;

&lt;p&gt;Let's substitute the &lt;code&gt;id&lt;/code&gt; primary key in the &lt;code&gt;Link&lt;/code&gt; model with a custom &lt;a href="https://elixir-lang.org/getting-started/typespecs-and-behaviours.html"&gt;Ecto type&lt;/a&gt; called &lt;code&gt;HashId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using this approach, we'll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learn how to create and use Elixir custom types.&lt;/li&gt;
&lt;li&gt;Automatically generate unique string hashes to form the short URL field for links. Ecto will manage the process whenever a new link model is created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, create a new file to represent this new type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/ecto/hash_id.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HashId&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@behaviour&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Type&lt;/span&gt;
  &lt;span class="nv"&gt;@hash_id_length&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;

  &lt;span class="c1"&gt;# Called when creating an Ecto.Changeset&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hash_id_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Accepts a value that has been directly placed into the ecto struct after a changeset&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hash_id_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Changes a value from the default type into the HashId type&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hash_id_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# A callback invoked by autogenerate fields&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;autogenerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;autogenerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

 &lt;span class="c1"&gt;# The Ecto type that is being converted&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;hash_id_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;hash_id_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;validate_hash_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' is not a string"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Validation of given value to be of type "String"&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;validate_hash_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;when&lt;/span&gt; &lt;span class="n"&gt;is_binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;validate_hash_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_other&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

  &lt;span class="c1"&gt;# The function that generates a HashId&lt;/span&gt;
  &lt;span class="nv"&gt;@spec&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nv"&gt;@hash_id_length&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:crypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strong_rand_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_encode64&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;binary_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@hash_id_length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://elixir-lang.org/getting-started/typespecs-and-behaviours.html"&gt;Ecto custom types documentation&lt;/a&gt;, which covers the subject in a much more exhaustive way than we could in this tutorial.&lt;/p&gt;

&lt;p&gt;For now, what we've just done allows us to use the custom data type &lt;code&gt;HashId&lt;/code&gt; when defining a field's data type.&lt;/p&gt;

&lt;p&gt;Let's use this new data type to define the short URL field for the &lt;code&gt;Link&lt;/code&gt; resource next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Custom Ecto Type in Phoenix
&lt;/h2&gt;

&lt;p&gt;Open up the &lt;code&gt;Link&lt;/code&gt; schema and edit it to reference the new Ecto type we've just defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/links/link.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HashId&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;

  &lt;span class="nv"&gt;@primary_key&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HashId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;autogenerate:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
  &lt;span class="nv"&gt;@derive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key:&lt;/span&gt; &lt;span class="ss"&gt;:hash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before moving on, let's briefly explain the use of &lt;code&gt;@derive&lt;/code&gt; in the code above.&lt;/p&gt;

&lt;p&gt;In Elixir, a &lt;a href="https://hexdocs.pm/elixir/1.14/Protocol.html"&gt;protocol&lt;/a&gt; defines an API and its specific implementations. &lt;a href="https://hexdocs.pm/phoenix/Phoenix.Param.html"&gt;&lt;code&gt;Phoenix.Param&lt;/code&gt;&lt;/a&gt; is a protocol used to convert data structures into URL parameters. By default, this protocol is used for integers, binaries, atoms, and structs. The default key &lt;code&gt;:id&lt;/code&gt; is usually used for structs, but it's possible to define other parameters. In our case, we use the parameter &lt;code&gt;hash&lt;/code&gt; and make its implementation derivable to actually use it.&lt;/p&gt;

&lt;p&gt;Let's modify the links migration to use this new parameter type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# priv/repo/migrations/XXXXXX_create_links.exs&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CreateLinks&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;primary_key:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete:&lt;/span&gt; &lt;span class="ss"&gt;:delete_all&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the migration to create the links table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix ecto.migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to associate &lt;code&gt;Link&lt;/code&gt; and &lt;code&gt;Account&lt;/code&gt;— but before we do, let's ensure that only an authenticated user can make CRUD operations for &lt;code&gt;Link&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating &lt;code&gt;Link&lt;/code&gt; Routes in Phoenix for Elixir
&lt;/h2&gt;

&lt;p&gt;In the router, we need to create a new pipeline to process routes that only authenticated users can access.&lt;/p&gt;

&lt;p&gt;Go ahead and add this pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/router.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:router&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="ss"&gt;:protected&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RequireAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error_handler:&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PlugErrorHandler&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the scope to handle &lt;code&gt;Link&lt;/code&gt; routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/router.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:router&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="ss"&gt;:protected&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RequireAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error_handler:&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PlugErrorHandler&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:protected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="s2"&gt;"/links"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;LinkController&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you try to access any of the protected routes, Pow should redirect you to a login page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XZtLruOt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-12/link-routes-protected.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XZtLruOt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-12/link-routes-protected.png" alt="Trying to access a protected route" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our app build is progressing well. Next, let's associate &lt;code&gt;Link&lt;/code&gt; and &lt;code&gt;Account&lt;/code&gt; since this forms the basis of tenant separation in our app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assigning &lt;code&gt;Link&lt;/code&gt; to &lt;code&gt;Account&lt;/code&gt; in Elixir
&lt;/h2&gt;

&lt;p&gt;Take a look at the &lt;code&gt;Link&lt;/code&gt; schema below. We want to make sure &lt;code&gt;account_id&lt;/code&gt; is included, since this will form the link to &lt;code&gt;Account&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/links/link.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HashId&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"links"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:integer&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;

    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, edit the changeset block, adding &lt;code&gt;account_id&lt;/code&gt; to the list of allowed attributes and those that will go through validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/links/link.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;HashId&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;link&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_required&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:visits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, edit the &lt;code&gt;Account&lt;/code&gt; schema and add a &lt;code&gt;has_many&lt;/code&gt; relation as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/accounts/account.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"accounts"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Link&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;

    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to edit the &lt;code&gt;Link&lt;/code&gt; context to work with &lt;code&gt;Account&lt;/code&gt;. Remember, scoping a resource to a user or account is the foundation of any multi-tenant structure.&lt;/p&gt;

&lt;p&gt;Edit your file as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/links.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;list_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order_by:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;asc:&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_link!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_by!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;account_id:&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;create_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="p"&gt;%{})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build_assoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding a &lt;code&gt;Current_Account&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Our edits to the &lt;code&gt;Link&lt;/code&gt; context show that most related controller methods use the &lt;code&gt;account&lt;/code&gt; variable. This variable needs to be extracted from the currently logged-in user's &lt;code&gt;account_id&lt;/code&gt; and passed on to the respective controller action.&lt;/p&gt;

&lt;p&gt;So we need to make a custom plug to add to the &lt;code&gt;conn&lt;/code&gt;. Create a new file — &lt;code&gt;lib/plugs/set_current_account.ex&lt;/code&gt; — and edit its content as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/plugs/set_current_account.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Plugs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SetCurrentAccount&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Conn&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:current_user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;account:&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's use this new plug by adding it to the router (specifically to the &lt;code&gt;protected&lt;/code&gt; pipeline since user sessions are handled there):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/router.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:router&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="ss"&gt;:protected&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RequireAuthenticated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;error_handler:&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;PlugErrorHandler&lt;/span&gt;
    &lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Plugs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SetCurrentAccount&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:protected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:browser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="s2"&gt;"/links"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;LinkController&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, edit the &lt;code&gt;Link&lt;/code&gt; controller to pass the &lt;code&gt;current_account&lt;/code&gt; in every action where it's required, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/controllers/link_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LinkController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;  &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;links:&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"link"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;link_params&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Link created successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="sx"&gt;~p"/links"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;changeset:&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;  &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_link!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;changeset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;change_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;link:&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;changeset:&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"link"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;link_params&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;  &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_link!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Link updated successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="sx"&gt;~p"/links/#{link}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;link:&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;changeset:&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;  &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_link!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;put_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Link deleted successfully."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to:&lt;/span&gt; &lt;span class="sx"&gt;~p"/links"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now, any created &lt;code&gt;Link&lt;/code&gt; will be associated with a currently logged-in user's account.&lt;/p&gt;

&lt;p&gt;At this point, we have everything we need for users to create links that are properly scoped to their respective accounts. You can see this in action when you consider the list index action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;
&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LinkController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;current_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;
    &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;links:&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, a user should only see links that are associated with them.&lt;/p&gt;

&lt;p&gt;For example, this is a list index view for one user:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lh5BJDIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-12/user1s-links-index.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lh5BJDIC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-12/user1s-links-index.png" alt="First user's link index view" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is a view for a different user (notice the logged-in user's email and the different link URLs):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h0SQ0hNv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-12/user2s-links-index.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h0SQ0hNv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-12/user2s-links-index.png" alt="Second user's link index view" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's build the link redirection feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redirecting Links and Incrementing Link Views in Phoenix
&lt;/h2&gt;

&lt;p&gt;Let's begin by adding a new controller to handle the redirect. This controller will have one &lt;code&gt;show&lt;/code&gt; action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/controllers/redirect_controller.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;RedirectController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:controller&lt;/span&gt;

  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;short_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_short_url_link!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Links&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increment_visits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;short_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;external:&lt;/span&gt; &lt;span class="n"&gt;short_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to modify the &lt;code&gt;Link&lt;/code&gt; context with a custom &lt;code&gt;get&lt;/code&gt; function that does not reference the current user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/links.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_short_url_link!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, modify the router accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/router.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:router&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Phoenix&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Router&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UrlbotWeb&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;pipe_through&lt;/span&gt; &lt;span class="ss"&gt;:browser&lt;/span&gt;

    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PageController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:home&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/links/:id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;RedirectController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, to handle the link view visits, we'll add a custom function to the &lt;code&gt;Link&lt;/code&gt; context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/links.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Links&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;increment_visits&lt;/span&gt;&lt;span class="p"&gt;(%&lt;/span&gt;&lt;span class="no"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="no"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;update:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;inc:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;visits:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now, when we visit a link like &lt;code&gt;http://localhost:4000/links/rMbzTz3o&lt;/code&gt;, this should redirect to the original long URL and increment the link's view counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This two-part series has taken you on a journey to build a multi-tenant Elixir app using the Phoenix web framework. You've learned how to implement a simple tenant separation system using foreign keys and related models within a shared database and shared schema.&lt;/p&gt;

&lt;p&gt;Although we've tried to be as exhaustive as possible, we couldn't possibly capture the whole app-building process, including testing, deployment options, or even the use of the amazing LiveView.&lt;/p&gt;

&lt;p&gt;All the same, we hope this tutorial provides a foundation for you to build your very own Elixir app that helps solve serious problems for your users.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Setting Up a Multi-tenant Phoenix App for Elixir</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Tue, 05 Dec 2023 11:18:16 +0000</pubDate>
      <link>https://dev.to/appsignal/setting-up-a-multi-tenant-phoenix-app-for-elixir-bl</link>
      <guid>https://dev.to/appsignal/setting-up-a-multi-tenant-phoenix-app-for-elixir-bl</guid>
      <description>&lt;p&gt;Apps built with Elixir can support massive scalability, real-time interactivity, great fault tolerance, and the language's syntax is actually a joy to use. Elixir is a natural fit for applications such as chat apps, data dashboard apps, and anything needed to support a large userbase.&lt;/p&gt;

&lt;p&gt;In this article, we'll use Elixir — specifically, the web framework Phoenix — to build a multi-tenant link shortening app.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Needed for Our Phoenix Project Built with Elixir
&lt;/h2&gt;

&lt;p&gt;With the growing popularity of social media, there's been an increase in the use of URL shortening services. As a budding entrepreneur, you believe there's a market for yet another URL shortening service. And since such an app lives in between a generated short URL and the long version, it's necessary that the app be fast and scalable.&lt;/p&gt;

&lt;p&gt;These features and more are already built into Phoenix, which makes it a great choice for this project, enabling you to stand out in the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;After this tutorial, you will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand the internal workings of an Elixir/Phoenix app (for example, how to create and use a custom Ecto type, how to handle authentication, and more).&lt;/li&gt;
&lt;li&gt;Understand the concept of multi-tenancy, including the different strategies available.&lt;/li&gt;
&lt;li&gt;Build a multi-tenant link shortening Elixir/Phoenix app that can be extended into something more advanced with real-world use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To follow along with the tutorial, let's set up a few things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Elixir installed on your development machine. If you don't have it installed, follow &lt;a href="https://elixir-lang.org/install.html"&gt;this guide&lt;/a&gt;. For this tutorial, we're using Elixir version 1.14.0.&lt;/li&gt;
&lt;li&gt;Since we'll be using Phoenix for the web app build, you'll also need to &lt;a href="https://hexdocs.pm/phoenix/index.html"&gt;have it installed&lt;/a&gt; on your local development machine. We're using Phoenix version 1.7.2 in this tutorial.&lt;/li&gt;
&lt;li&gt;PostgreSQL - Ensure you have PostgreSQL installed, as it's the default database used by Elixir/Phoenix apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in place, let's see what kind of app we'll be building next.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Overview of the Phoenix App We'll Build
&lt;/h2&gt;

&lt;p&gt;The app that we'll build is a simple multi-tenant URL shortening app with the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User authentication&lt;/strong&gt; - users can register and log in to the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant separation&lt;/strong&gt; - users can create shortened links within their own separate accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL shortening&lt;/strong&gt; - users can input normal links and generate shortened versions that redirect back to the original link when clicked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;View metrics&lt;/strong&gt; - users can see how many times a link was clicked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing these features should add to your experience in working with Elixir and provide a good foundation to build even more complex Elixir apps.&lt;/p&gt;

&lt;p&gt;Next, let's understand what multi-tenancy is and how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Overview of Multi-tenancy
&lt;/h2&gt;

&lt;p&gt;At the most basic level, a multi-tenant app is one where the same application and database serve several tenants, with each client's data kept separate from that of other clients. There are thousands of real-world multi-tenant app examples, including AppSignal, Gmail, and others.&lt;/p&gt;

&lt;p&gt;In our app, a user will be able to register and create shortened links that are associated with their account. Other users will also be able to sign up and create shortened links associated to them, but users won't be able to see other users' links. This is a simplified but very good example of multi-tenancy in action.&lt;/p&gt;

&lt;p&gt;In the next section, we'll outline the different strategies a developer can use when building a multi-tenant app. It's important to point out that these strategies aren't exclusive to Elixir. Rather, think of them as universal building blocks for developing multi-tenant apps regardless of the programming language used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenancy Strategies
&lt;/h2&gt;

&lt;p&gt;Several multi-tenancy strategies are available, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate databases&lt;/strong&gt; - Here, each tenant gets their own database for complete isolation of each client's data. The beauty of this approach is that it's very scalable and great for applications where data security and isolation is key: for example, patient medical records, financial records, and other similar use cases. As you can imagine, one of the biggest disadvantages with this approach is how complex and costly it is to build and maintain apps using this strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shared database with separate schema&lt;/strong&gt; - In this strategy, each client gets a separate schema within a shared database. This approach allows for a scalable system that is not too complex to build and maintain. That said, having separate schemas for each client is not something you can easily use to handle massive scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shared database with shared schema&lt;/strong&gt; - Here, all tenants share a common database and schema. It is a great choice for low-to-medium traffic apps and offers a convenient way to get started with multi-tenancy. Many SaaS startups are often built using this strategy. The biggest disadvantage of this strategy is that it's not built for scale or speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid strategy&lt;/strong&gt; - A not-so-common approach where you have both the shared database and shared schema for some groups of users (say, the ones that pay the least in a SaaS app), and a common database with a separate schema for premium customers. This approach offers some level of scaling but is very complex to build and maintain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containerization&lt;/strong&gt; - Similar to the separate databases approach, here, each tenant is provided with a completely separate and isolated app container. The obvious advantages are speed and scalability, but this is complex to build and maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now have a good overview of what strategy to use when you build your own Elixir app next time.&lt;/p&gt;

&lt;p&gt;For this app project, we'll be using the &lt;em&gt;shared database with shared schema&lt;/em&gt; approach and an app stack described in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Elixir Application Stack
&lt;/h2&gt;

&lt;p&gt;To build our app project, we will use the up-and-coming Elixir stack called "PETAL", short for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;P - Phoenix&lt;/li&gt;
&lt;li&gt;E - Elixir&lt;/li&gt;
&lt;li&gt;T - Tailwind CSS&lt;/li&gt;
&lt;li&gt;A - Alpine JS&lt;/li&gt;
&lt;li&gt;L - &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html"&gt;Liveview&lt;/a&gt; - LiveView is an Elixir library that you include as a dependency in a Phoenix app to handle interactivity and other real-time flows characteristic of single-page applications (SPAs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since our goals are simple, employing the full PETAL framework in this case is overkill. Instead, we'll use the simple stack you get when you generate a new Phoenix web application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phoenix&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;HTML and Tailwind CSS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are now ready to jump into the build, but before we do, it's necessary that we understand how link shortening actually works. This will form the basis for the steps we'll take during the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Link Shortening Works
&lt;/h2&gt;

&lt;p&gt;Link shortening is actually very simple. The link shortening app lies between a long URL and a generated short URL. When a visitor hits a short URL, their request is received by the app and a quick database search is done to find the matching long URL. Once that long URL is found, the visitor is redirected to it, and the link visits counter updates.&lt;/p&gt;

&lt;p&gt;Obviously, this is a very simplified outline, but it will suffice for now. Next, let's start off our build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a New Phoenix Application
&lt;/h2&gt;

&lt;p&gt;Begin by generating a new Phoenix application using the following &lt;code&gt;mix&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.new urlbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open your project directory in a text editor and edit the &lt;code&gt;dev.exs&lt;/code&gt; file with your development environment's database settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/dev.exs&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:shorten_my_links_app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ShortenMyLinksApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;username:&lt;/span&gt; &lt;span class="s2"&gt;"DATABASE USERNAME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Edit this line&lt;/span&gt;
  &lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="s2"&gt;"DATABASE PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Edit this line&lt;/span&gt;
  &lt;span class="ss"&gt;hostname:&lt;/span&gt; &lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;database:&lt;/span&gt; &lt;span class="s2"&gt;"DATABASE NAME"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Edit this line&lt;/span&gt;
  &lt;span class="ss"&gt;stacktrace:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;show_sensitive_data_on_connection_error:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;pool_size:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the database for the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix ecto.create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is done, run &lt;code&gt;mix phx.server&lt;/code&gt; in the terminal to compile the application and spin up an instance of the compiled app on &lt;code&gt;localhost:4000&lt;/code&gt;, where you can see the default Phoenix home page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--roSBwaGu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-11/default-phoenix-app-homepage.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--roSBwaGu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-11/default-phoenix-app-homepage.png" alt="Default Phoenix app homepage" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up, let's set up the multi-tenant structure and user authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Multi-tenancy for Elixir
&lt;/h2&gt;

&lt;p&gt;The tenant structure needs to be implemented as early as possible into a project build, since doing it later can result in all sorts of challenges. To put the &lt;em&gt;shared database with shared schema&lt;/em&gt; multi-tenant strategy that we chose into actuality, we'll make use of foreign keys and related models.&lt;/p&gt;

&lt;p&gt;Specifically, we'll have two main models: &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Account&lt;/code&gt; (this can also be called &lt;code&gt;Organization&lt;/code&gt;, &lt;code&gt;Team&lt;/code&gt;, or even &lt;code&gt;Company&lt;/code&gt;); a &lt;code&gt;User&lt;/code&gt; belongs to the &lt;code&gt;Account&lt;/code&gt; model.&lt;/p&gt;

&lt;p&gt;We'll also have a third &lt;code&gt;Link&lt;/code&gt; model which will belong to the &lt;code&gt;Account&lt;/code&gt; model. This way, the &lt;code&gt;User&lt;/code&gt; model can be used exclusively for authentication purposes, while resource ownership will be handled by &lt;code&gt;Account&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This structure is represented in the diagram below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H_HIAO3T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-11/multi-tenant-account-structure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H_HIAO3T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2023-11/multi-tenant-account-structure.png" alt="Multi-tenant account structure" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this structure in place, all resources created, updated, or deleted in the app can be scoped to a specific &lt;code&gt;Account&lt;/code&gt; and &lt;code&gt;User&lt;/code&gt;. And just like that, we can achieve our goal of having separate tenant resources in a &lt;em&gt;shared database with shared schema&lt;/em&gt; setup.&lt;/p&gt;

&lt;p&gt;An additional benefit to using this structure is that you can easily expand it to invite other users to an account as "teammates" and assign them different roles. However, we won't cover this feature in this tutorial.&lt;/p&gt;

&lt;p&gt;Let's begin by generating the &lt;a href="https://hexdocs.pm/phoenix/contexts.html"&gt;&lt;code&gt;Account&lt;/code&gt; context&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating an &lt;code&gt;Account&lt;/code&gt; Context
&lt;/h2&gt;

&lt;p&gt;Generate the &lt;code&gt;Account&lt;/code&gt; context, schema, and migration file with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix phx.gen.context Accounts Account accounts name:string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the migration with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix ecto.migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's move on to the &lt;code&gt;User&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a &lt;code&gt;User&lt;/code&gt; Context and Authentication with Elixir
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;User&lt;/code&gt; context will be used for authentication purposes. Instead of generating the &lt;code&gt;User&lt;/code&gt; context and trying to integrate it into an authentication flow from scratch, Elixir gives us several libraries we can use, including &lt;a href="https://hexdocs.pm/phx_gen_auth/overview.html"&gt;&lt;code&gt;phx.gen.auth&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/smpallen99/coherence"&gt;Coherence&lt;/a&gt;, and &lt;a href="https://github.com/pow-auth/pow"&gt;Pow&lt;/a&gt;, a modular and extendable authentication library for Phoenix apps.&lt;/p&gt;

&lt;p&gt;We first add Pow to the project's dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# mix.exs&lt;/span&gt;

&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:pow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.0.30"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And fetch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix deps.get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then finish by installing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix pow.install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, we get a &lt;code&gt;User&lt;/code&gt; context, schema, and migration file. User authentication routes are also appended to the router.&lt;/p&gt;

&lt;p&gt;At this point, we could run the migration, but there are a couple of changes we should make to our generated user files first to ensure they are properly related.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Account&lt;/code&gt; Relationship
&lt;/h2&gt;

&lt;p&gt;Begin by adding a &lt;code&gt;belongs_to&lt;/code&gt; association to &lt;code&gt;User&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/shorten_my_links_app/users/user.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;pow_user_fields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;

    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's also modify &lt;code&gt;Account&lt;/code&gt; to include the &lt;code&gt;has_many&lt;/code&gt; relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/accounts/account.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"accounts"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;

    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's modify the &lt;code&gt;users&lt;/code&gt; migration to add the &lt;code&gt;account_id&lt;/code&gt; field as a foreign key. We also need to indicate that a user will be deleted whenever their related account is deleted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# priv/repo/migrations/XXXXXXXXXX_create_users.exs&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;CreateUsers&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:password_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

      &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
      &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;on_delete:&lt;/span&gt; &lt;span class="ss"&gt;:delete_all&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;null:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:account_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;unique_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, it's nice to have an &lt;code&gt;Account&lt;/code&gt; automatically created the first time a user registers on the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an &lt;code&gt;Account&lt;/code&gt; on User Registration
&lt;/h2&gt;

&lt;p&gt;We need a way to capture the one attribute of the &lt;code&gt;Acccount&lt;/code&gt; model (the &lt;code&gt;account_name&lt;/code&gt; in the user registration form) and pass this attribute to a modified user creation process which will create a related &lt;code&gt;Account&lt;/code&gt; for us.&lt;/p&gt;

&lt;p&gt;That sounds like a lot, but let's go step-by-step.&lt;/p&gt;

&lt;p&gt;First, add the &lt;code&gt;account_name&lt;/code&gt; attribute to the user registration form. Since we are working with Pow which comes with pre-built view templates, we need to generate the templates by running the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mix pow.phoenix.gen.templates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate Pow's view templates, but we are only interested in the user registration view for now. Edit it by adding the &lt;code&gt;account_name&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot_web/controllers/pow/registration_html/new.html.heex&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mx-auto max-w-sm"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;simple_form&lt;/span&gt; &lt;span class="ss"&gt;:let=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;@changeset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;@action&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;phx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ignore"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;!&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="no"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;.&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:account_name&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Account name"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/.&lt;/span&gt;&lt;span class="n"&gt;simple_form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way, instead of adding the &lt;code&gt;account_name&lt;/code&gt; like this, we can use a nested form for working with &lt;code&gt;Account&lt;/code&gt; from &lt;code&gt;User&lt;/code&gt; (but this method should work just as well).&lt;/p&gt;

&lt;p&gt;Next, we want to add &lt;code&gt;account_name&lt;/code&gt; as a virtual attribute in the &lt;code&gt;User&lt;/code&gt; schema. The reason it's a virtual attribute is simply because it's not an attribute that is built into the &lt;code&gt;User&lt;/code&gt; schema, but we still need to use it in there. You can &lt;a href="https://hexdocs.pm/ecto/Ecto.Schema.html"&gt;read more on virtual attributes&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/urlbot/users/user.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;

  &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;pow_user_fields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ss"&gt;:account_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;virtual:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

    &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;

    &lt;span class="n"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you can run &lt;code&gt;mix ecto.migrate&lt;/code&gt; to create the user table.&lt;/p&gt;

&lt;p&gt;Next, we want to make sure that the virtual attribute we've just added is passed on to the &lt;code&gt;User&lt;/code&gt; &lt;a href="https://hexdocs.pm/ecto/Ecto.Changeset.html"&gt;changeset&lt;/a&gt;, which we'll add since it's not included by default when we run the Pow generator.&lt;/p&gt;

&lt;p&gt;We'll also add a custom private function to create an account at the same time a user is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/shorten_my_links_app/users/user.ex&lt;/span&gt;

&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;User&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Pow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Schema&lt;/span&gt;

  &lt;span class="c1"&gt;# Add this line since we'll be using Ecto's changeset in this schema&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Changeset&lt;/span&gt;

  &lt;span class="c1"&gt;# Add this line to reference Account since it's used in the private function&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Urlbot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Accounts&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="c1"&gt;# Also add this block of code to add a changeset&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pow_changeset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:account_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validate_required&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:account_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;create_user_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;assoc_constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Add the custom private function&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;create_user_account&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;valid?:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;changes:&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;account_name:&lt;/span&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;account_id:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;Accounts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_account&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;account_name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;put_assoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;create_user_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changeset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;changeset&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what's going on. First, we define a changeset block passing in a &lt;code&gt;user&lt;/code&gt; and the user attributes. Then we cast the virtual attribute we added, followed by a validation rule to make sure it's present. We then call the private function &lt;code&gt;create_user_account&lt;/code&gt; (defined just below the changeset block) and finalize with an &lt;code&gt;assoc_constraint&lt;/code&gt; which checks that the associated field, &lt;code&gt;account_id&lt;/code&gt;, exists.&lt;/p&gt;

&lt;p&gt;With all that done, whenever a new user registers, a connected account will be automatically created. Any link resources created by the logged-in user will always be scoped to them. For example, an index view under &lt;em&gt;/links&lt;/em&gt; will only display links created by the user, and not list other users' links, even if they are available. This resource separation at the account or user level is the foundation of a multi-tenant structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Up Next: Adding &lt;code&gt;Link&lt;/code&gt; Resources and Custom Ecto Types
&lt;/h2&gt;

&lt;p&gt;We've explored how multi-tenancy works and discussed a few multi-tenancy strategies. We created a Phoenix app and set up multi-tenancy. Finally, we added accounts, users and authentication, and associated users and accounts.&lt;/p&gt;

&lt;p&gt;In the next and final part of this series, we'll look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating the link resource&lt;/li&gt;
&lt;li&gt;Using Ecto custom types&lt;/li&gt;
&lt;li&gt;Generating a &lt;code&gt;current_account&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assigning a &lt;code&gt;current_account&lt;/code&gt; to links&lt;/li&gt;
&lt;li&gt;Redirecting links and updating the &lt;code&gt;views&lt;/code&gt; counter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until then, happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, &lt;a href="https://dev.to/elixir-alchemy"&gt;subscribe to our Elixir Alchemy newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Advanced Usages of Action Policy for Ruby on Rails</title>
      <dc:creator>Aestimo K.</dc:creator>
      <pubDate>Wed, 25 Oct 2023 09:36:18 +0000</pubDate>
      <link>https://dev.to/appsignal/advanced-usages-of-action-policy-for-ruby-on-rails-2dbe</link>
      <guid>https://dev.to/appsignal/advanced-usages-of-action-policy-for-ruby-on-rails-2dbe</guid>
      <description>&lt;p&gt;In part one of this series, we looked at some basic usages of Action Policy. Now we'll leverage Action Policy for more advanced authorization use cases.&lt;/p&gt;

&lt;p&gt;First up, let's explore applying pre-checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-checks
&lt;/h2&gt;

&lt;p&gt;Let's say we want users with "editor" status to have access to a post's &lt;code&gt;show&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, and &lt;code&gt;destroy&lt;/code&gt; actions, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/policies/posts_policy.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show?&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update?&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy?&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can refactor this policy code using an Action Policy pre-check that extracts common rules into &lt;code&gt;pre-checks&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/policies/posts_policy.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;
  &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="ss"&gt;:allow_editors&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allow_editors&lt;/span&gt;
    &lt;span class="n"&gt;allow!&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editor&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scopes
&lt;/h2&gt;

&lt;p&gt;Another Action Policy feature that deserves our attention is &lt;code&gt;scoping&lt;/code&gt;. Scopes filter data depending on any authorization rules you've set.&lt;/p&gt;

&lt;p&gt;Using our blog app as an example, let's say we want to apply the following rules to the posts index action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List all posts for all users with the "editor" role&lt;/li&gt;
&lt;li&gt;List only posts that a user has created if they have the "author" role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without using any Action Policy authorization, the posts controller index action looks like any generic index action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/posts_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll need to utilize Action Policy scoping to refactor this action and apply the outlined access rules. Scoping rules are defined within a policy class and applied in the respective controller using the &lt;code&gt;authorized_scope&lt;/code&gt; or &lt;code&gt;authorized&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;First modify the Post policy, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/policies/posts_policy.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="n"&gt;relation_scope&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editor?&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;relation_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:own&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the posts controller's index action, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/posts_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorized_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :relation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :own&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caching in Action Policy for Ruby and Rails
&lt;/h2&gt;

&lt;p&gt;Action Policy is a performant authorization library, partly thanks to its efficient use of caching.&lt;/p&gt;

&lt;p&gt;It has several cache layers for you to leverage, including memoization and external caching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memoization
&lt;/h3&gt;

&lt;p&gt;Consider a situation where the same rule is called several times on an object instance. For example, let's say we need to load all comments associated with a post within the &lt;code&gt;index&lt;/code&gt; action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/posts_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorized_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, imagine there's an "edit" link for every comment a post has within the index view. As you can see, this is a resource-intensive undertaking since we need to check if the current user is allowed to first access the post index, and then edit a post's comments. If a post has multiple comments, this would mean loading the authorization policy multiple times.&lt;/p&gt;

&lt;p&gt;In a situation like this, Action Policy will re-use the required policy instance — specifically, the &lt;code&gt;record.policy_cache_key&lt;/code&gt; — as one of the identifiers in the local store.&lt;/p&gt;

&lt;p&gt;This kind of caching works well for short-lived requests, but if you need to cache resource-intensive rules that will be persisted across requests, using an external cache store is a better option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using an External Cache Store
&lt;/h3&gt;

&lt;p&gt;If you need to run access control rules that utilize complex database queries, for example, you can use an external cache store such as Redis. Your rules cache will be made available across requests. You just need to remember to explicitly define which rules to cache within the policy class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/policies/post_policy.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;
  &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="ss"&gt;:index?&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show?&lt;/span&gt;
    &lt;span class="c1"&gt;# some complex database heavy rules...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nf"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And configure the cache store for your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_cache_store&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Quick tip:&lt;/strong&gt; There's a lot more to this. Dig into the &lt;a href="https://actionpolicy.evilmartians.io/#/caching?id=rule-cache"&gt;Action Policy caching documentation&lt;/a&gt; for more information.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Aliases in Action Policy for Rails
&lt;/h2&gt;

&lt;p&gt;An alias is an alternative way to name policies so that they make more sense.&lt;/p&gt;

&lt;p&gt;Let's check out an example using our blog app. Consider this Post policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/policies/post_policy.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostPolicy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationPolicy&lt;/span&gt;
  &lt;span class="n"&gt;alias_rule&lt;/span&gt; &lt;span class="ss"&gt;:destroy?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:update?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: :manage_post?&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;manage_article?&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we combine the &lt;code&gt;update?&lt;/code&gt; and &lt;code&gt;destroy?&lt;/code&gt; rules into one alias called &lt;code&gt;manage_post?&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then we use it in the controller, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/posts_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="n"&gt;authorize!&lt;/span&gt; &lt;span class="ss"&gt;:manage_post?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@post&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# DELETE /posts/1 or /posts/1.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="n"&gt;authorize!&lt;/span&gt; &lt;span class="ss"&gt;:manage_post?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@post&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's quickly look at how to handle unauthorized access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Unauthorized Access in a Ruby and Rails App
&lt;/h2&gt;

&lt;p&gt;If a user tries to access a resource they shouldn't, an &lt;code&gt;ActionPolicy::Unauthorized&lt;/code&gt; error is raised.&lt;/p&gt;

&lt;p&gt;You need to explicitly handle this error in your app's &lt;code&gt;ApplicationController&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/application_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;rescue_from&lt;/span&gt; &lt;span class="no"&gt;ActionPolicy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Unauthorized&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;alert: &lt;/span&gt;&lt;span class="s1"&gt;'Access denied.'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this post, we explored advanced Action Policy features, including pre-checks, scopes, caching, aliases, and finally, handling unauthorized access.&lt;/p&gt;

&lt;p&gt;From basic rules to complex conditional scenarios, Action Policy has everything you need to handle almost any authorization scenario. Use this library in your next project and see how powerful it is.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
