<?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: Matias Carpintini</title>
    <description>The latest articles on DEV Community by Matias Carpintini (@matiascarpintini).</description>
    <link>https://dev.to/matiascarpintini</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%2F369241%2Fa0111bcb-a046-4398-98cd-f4cf0e2f8d0d.png</url>
      <title>DEV Community: Matias Carpintini</title>
      <link>https://dev.to/matiascarpintini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matiascarpintini"/>
    <language>en</language>
    <item>
      <title>Creating a Project Brief: Key Elements and Best Practices for Effective Planning</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Sun, 11 Dec 2022 22:17:53 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/creating-a-project-brief-key-elements-and-best-practices-for-effective-planning-9oi</link>
      <guid>https://dev.to/matiascarpintini/creating-a-project-brief-key-elements-and-best-practices-for-effective-planning-9oi</guid>
      <description>&lt;p&gt;A project brief is a document that outlines the key details of a project. It's typically used to communicate the project's goals, objectives, and deliverables to stakeholders and team members. A well-crafted project brief can help ensure that everyone involved in the project is on the same page and working towards the same goals.&lt;/p&gt;

&lt;p&gt;Here are some steps to help you build a project brief for your next project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the project's purpose and goals. What is the project trying to achieve, and why is it important? Be as specific as possible when defining the project's purpose and goals.&lt;/li&gt;
&lt;li&gt;Outline the project's objectives. What are the specific steps or tasks that need to be completed in order to achieve the project's goals? Make sure that each objective is clear, measurable, and achievable.&lt;/li&gt;
&lt;li&gt;Define the project's deliverables. What will be produced as a result of the project? This could include things like reports, presentations, prototypes, or finished products. Be sure to specify who will be responsible for each deliverable and when it is due.&lt;/li&gt;
&lt;li&gt;Identify the project's stakeholders. Who will be impacted by the project, and who will need to be involved in its planning and execution? This could include clients, customers, team members, or other individuals or groups who have a vested interest in the project's success.&lt;/li&gt;
&lt;li&gt;Establish the project's timeline and budget. When will the project start and end, and how much money will be needed to complete it? Be sure to include any major milestones or deadlines in the timeline, and to allocate sufficient resources to ensure that the project can be completed on time and within budget.&lt;/li&gt;
&lt;li&gt;Outline the project's risks and challenges. What are the potential obstacles or challenges that could arise during the course of the project? Identify these upfront and develop strategies for mitigating or addressing them.&lt;/li&gt;
&lt;li&gt;Include information about the communication channels, ticket boards, mockups, and other resources that will be used during the project. This could include things like Slack for team communication, Asana for tracking project tasks, and mockups or inspiration to guide the design process. Be sure to specify the technology stack that will be used to build the project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following these steps, you can create a comprehensive project brief that will help ensure that your project is well-planned and executed. By clearly communicating the project's goals, objectives, and deliverables, a project brief can help ensure that everyone involved in the project is working towards the same goals and that the project stays on track and within budget.&lt;/p&gt;

&lt;p&gt;If you want to create a project brief for your next project, you can &lt;a href="https://docs.google.com/document/d/1gRVoMjLzrZfZb3W0IJomS4f-azzzRIqyciDW7lQdZzA/edit?usp=sharing" rel="noopener noreferrer"&gt;download my template&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I also created a new app called &lt;a href="https://glivo.app" rel="noopener noreferrer"&gt;Glivo&lt;/a&gt;, to help you with this kind-of things, maybe you find it useful, it's free! :)&lt;/p&gt;

</description>
      <category>deno</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How to generate OG links preview like Dev.to (for free) with Rails 7</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Thu, 28 Apr 2022 23:41:55 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/how-to-generate-og-links-preview-like-devto-for-free-with-rails-7-57b2</link>
      <guid>https://dev.to/matiascarpintini/how-to-generate-og-links-preview-like-devto-for-free-with-rails-7-57b2</guid>
      <description>&lt;p&gt;Hey hey heyyyyyy&lt;br&gt;
Quick article on how to have beauty link previews, just like Dev.to does&lt;/p&gt;

&lt;p&gt;You're gonna get something 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%2F1mxuv3c26w4duy8l634y.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%2F1mxuv3c26w4duy8l634y.png" alt="Final result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yup, was being literal with the "&lt;em&gt;like dev.to&lt;/em&gt;" 😛&lt;br&gt;
I've build something similar for my new project &lt;a href="https://workby.io" rel="noopener noreferrer"&gt;workby.io&lt;/a&gt;, here's how i've build it...&lt;/p&gt;



&lt;p&gt;Almost every guide that i found was using &lt;a href="https://htmlcsstoimage.com" rel="noopener noreferrer"&gt;htmlcsstoimage.com&lt;/a&gt;, in fact, dev.to uses it. It's pretty popular, and efficient, BUT, i don't want to pay $14 bucks for each 1k images 😂🐁&lt;/p&gt;

&lt;p&gt;So, i'm going to use &lt;a href="https://github.com/Studiosity/grover" rel="noopener noreferrer"&gt;Grover&lt;/a&gt;: A Ruby gem to transform HTML into PDFs, PNGs or JPEGs using Google Puppeteer and Chromium.&lt;/p&gt;

&lt;p&gt;If you want to build this with, let's say, Node.js, you can also follow me. Puppeteer is popular and easy to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long story short:&lt;/strong&gt; We're going to build a HTML view and take an screenshot of it.&lt;/p&gt;

&lt;p&gt;You can create a blank Rails project, or just add additional functionality to your current app.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;Install Grover gem with&lt;br&gt;
&lt;code&gt;bundle add grover&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And the dependency of Grover, &lt;em&gt;Puppeteer&lt;/em&gt;, with:&lt;br&gt;
&lt;code&gt;yarn add puppeteer&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then create an empty controller where you can respond with different link preview templates:&lt;br&gt;
&lt;code&gt;rails g controller og-imager dev_to&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;dev_to&lt;/code&gt; action, let's call Grover and let him do the magic&lt;br&gt;
&lt;code&gt;app/controllers/og_imager_controller.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OgImagerController&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="c1"&gt;# GET /og_imager/dev_to&lt;/span&gt;
  &lt;span class="c1"&gt;# @param {string} title&lt;/span&gt;
  &lt;span class="c1"&gt;# @param {string} avatar&lt;/span&gt;
  &lt;span class="c1"&gt;# @param {string} username&lt;/span&gt;
  &lt;span class="c1"&gt;# @param {string} timestamp&lt;/span&gt;
  &lt;span class="c1"&gt;# @param {array} logos&lt;/span&gt;
  &lt;span class="c1"&gt;# @returns image/png&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dev_to&lt;/span&gt;
    &lt;span class="c1"&gt;# Get params and set to variable&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: Retrieve your object instead :p&lt;/span&gt;
    &lt;span class="vi"&gt;@title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@avatar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:avatar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:username&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:timestamp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="vi"&gt;@logos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:logos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Grover.new accepts a URL or inline HTML and optional parameters for Puppeteer&lt;/span&gt;
    &lt;span class="n"&gt;grover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Grover&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="n"&gt;render_to_string&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get a screenshot&lt;/span&gt;
    &lt;span class="n"&gt;png&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grover&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_png&lt;/span&gt;

    &lt;span class="c1"&gt;# Render image&lt;/span&gt;
    &lt;span class="n"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'image/png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;disposition: &lt;/span&gt;&lt;span class="s1"&gt;'inline'&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's our view, just simple HTML/CSS&lt;br&gt;
&lt;code&gt;app/views/og_imager/dev_to.html.erb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"details"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"user"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;%= @avatar %&amp;gt;"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"avatar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;username&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; - &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;timestamp&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"logos"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;logos.each&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="na"&gt;logo&lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;%= logo %&amp;gt;"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"logo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="sx"&gt;url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&amp;amp;display=swap')&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="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Roboto'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#cd685f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.container&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1128px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.card&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.8rem&lt;/span&gt; &lt;span class="m"&gt;4rem&lt;/span&gt; &lt;span class="m"&gt;4rem&lt;/span&gt; &lt;span class="m"&gt;2.8rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#c56b61&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;#c56b61&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.8rem&lt;/span&gt; &lt;span class="m"&gt;1.8rem&lt;/span&gt; &lt;span class="m"&gt;1.8rem&lt;/span&gt; &lt;span class="m"&gt;1.8rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.details&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.user&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.avatar&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#c56b61&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.username&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.logos&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.logo&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, create an initializer to set our Browser viewport (final image size).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;initializers/grover.rb&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="no"&gt;Grover&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;viewport: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;width: &lt;/span&gt;&lt;span class="mi"&gt;1128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;height: &lt;/span&gt;&lt;span class="mi"&gt;600&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;If you have created a blank project, and want to use this as a micro-service, here's a guide on how to deploy this thing to Heroku.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the app on your Heroku account (T'm assuming you already have the cli installed): &lt;code&gt;heroku create your_app_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Warn Heroku of our JS library (Puppeteer): &lt;code&gt;heroku buildpacks:add heroku/nodejs --index=1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;And also help him to setup Puppeteer dependencies 😛: &lt;code&gt;heroku buildpacks:add jontewks/puppeteer --index=2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tell Grover to run Puppeteer in the "no-sandbox": &lt;code&gt;heroku config:set GROVER_NO_SANDBOX=true&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To make use of this image on the link preview, you will need &lt;a href="https://ogp.me/" rel="noopener noreferrer"&gt;The Open Graph protocol&lt;/a&gt;. Just simple meta tags that browsers will look for.&lt;/p&gt;

&lt;p&gt;Meta tags are always on &lt;/p&gt; so,&lt;br&gt;
&lt;code&gt;app/views/layouts/application.html.erb&lt;/code&gt;&lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;yield&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;:head&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And then ask ERB to fill previous block with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;content_for&lt;/span&gt; &lt;span class="na"&gt;:head&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;%= @article.title %&amp;gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;%= article_link_preview(@article) %&amp;gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"image/png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1128"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;at your details view. Like: &lt;code&gt;/articles/:id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;twitter:card&lt;/code&gt; thing it's because they are more fancy on link previews, you can read more about that &lt;a href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The previous &lt;code&gt;article_link_preview&lt;/code&gt; method it's a simple helper that generates the URL that we are looking for.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/helpers/articles_helper.rb&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;article_link_preview&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://your_app_name.herokuapp.com/ogimage'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode_www_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'images[]'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:image&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="ss"&gt;timestamp: &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_formatted_s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:short&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="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&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 will get something like this:&lt;br&gt;
&lt;code&gt;https://your_app_name.herokuapp.com/og_imager/dev_to?title=Real%20Time%20Notification%20System%20with%20Hotwire,%20in%20Rails%207&amp;amp;avatar=https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/369241/a0111bcb-a046-4398-98cd-f4cf0e2f8d0d.png&amp;amp;username=Matias%20Carpintini&amp;amp;timestamp=13%20April&amp;amp;logos[]=https://img.icons8.com/office/80/000000/ruby-gemstone.png&amp;amp;logos[]=https://practicaldev-herokuapp-com.freetls.fastly.net/assets/devlogo-pwa-512.png&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://github.com/matias-carpintini/og-imager" rel="noopener noreferrer"&gt;repo&lt;/a&gt; of the whole thing.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Interesting libraries, fonts and more</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Thu, 14 Apr 2022 23:11:52 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/interesting-libraries-fonts-and-more-32hf</link>
      <guid>https://dev.to/matiascarpintini/interesting-libraries-fonts-and-more-32hf</guid>
      <description>&lt;p&gt;Share yours below! 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Tools, libraries and more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://type-scale.com/"&gt;Typescale&lt;/a&gt;: Great tool for all devs that are obsessed with getting the typography right&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://uicolors.app/create?ref=producthunt"&gt;UI Colors&lt;/a&gt;: Create Tailwind CSS color family&lt;/li&gt;
&lt;li&gt;
&lt;a href="//daisyui.com"&gt;DaisyUI&lt;/a&gt;: The most popular, free and open-source Tailwind CSS component library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.skypack.dev/view/canvas-confetti"&gt;Confetti&lt;/a&gt;: Performant confetti animation in the browser&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mantine.dev/"&gt;Mantime&lt;/a&gt;: React UI Library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nocodb/nocodb"&gt;Nocodb&lt;/a&gt;: Open Source Airtable Alternative - turns any MySQL, Postgres database into a collaborative spreadsheet with REST APIs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/meilisearch/meilisearch"&gt;Meilisearch&lt;/a&gt;: Powerful, fast, and an easy to use search engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/prabhatsharma/zinc"&gt;Zinc&lt;/a&gt;: Zinc Search engine. A lightweight alternative to elasticsearch that requires minimal resources, written in Go.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/BuilderIO/partytown"&gt;Partytown&lt;/a&gt;: Relocate resource intensive third-party scripts off of the main thread and into a web worker.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://asciinema.org/"&gt;Asciinema&lt;/a&gt;: Record and share your terminal sessions, the simple way.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kleampa/not-paid"&gt;Not paid&lt;/a&gt;: Client did not pay? Add opacity to the body tag and decrease it every day until their site completely fades away&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fonts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://manropefont.com/"&gt;Manrope&lt;/a&gt; [&lt;a href="https://fonts.google.com/specimen/Manrope"&gt;Google Fonts&lt;/a&gt;]&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fonts.google.com/specimen/Space+Grotesk"&gt;Space Grotesk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.behance.net/gallery/66716945/Recoleta"&gt;Recoleta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.behance.net/gallery/118133975/Newake-Free-Font"&gt;Newake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pixelsurplus.com/collections/free-fonts/products/sonder-regular-free-font"&gt;Sonder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.behance.net/gallery/32918197/Modernist-Typeface"&gt;Modernist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.atipofoundry.com/fonts/archia"&gt;Archia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.behance.net/gallery/79907715/Technique-Free-Font?tracking_source=search%7Cfont"&gt;Technique&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.behance.net/gallery/14680165/Campton-Typefamily"&gt;Campton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fontfabric.com/fonts/track/"&gt;Track&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://tokotype.github.io/plusjakarta-sans/"&gt;Plus Jakarta Sans&lt;/a&gt; (open source)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Jobs search
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://relocate.me/"&gt;Relocate.me&lt;/a&gt;: Niche job board
for techies looking to relocate&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://workby.io"&gt;Workby.io&lt;/a&gt;: Hiring remote devs around the globe (self-promotion 😛)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>css</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Real Time Notification System with Hotwire, in Rails 7</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Wed, 13 Apr 2022 15:18:20 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/real-time-notification-system-with-hotwire-in-rails-7-1j9f</link>
      <guid>https://dev.to/matiascarpintini/real-time-notification-system-with-hotwire-in-rails-7-1j9f</guid>
      <description>&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt;&lt;br&gt;
Time ago i wrote an article here, on how to build a notifications system with Rails and Redis, from scratch. This new one it's a quick update of that one.&lt;/p&gt;

&lt;p&gt;If you don't know what i'm talking about, and are curious on how to accomplish same thing without Hotwire, here you go:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/matiascarpintini" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F369241%2Fa0111bcb-a046-4398-98cd-f4cf0e2f8d0d.png" alt="matiascarpintini"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/matiascarpintini/real-time-notification-system-with-sidekiq-redis-and-devise-in-rails-6-33l9" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Real Time Notification System with Sidekiq, Redis and Devise in Rails 6&lt;/h2&gt;
      &lt;h3&gt;Matias Carpintini ・ Apr 25 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#rails&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ruby&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#redis&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;





&lt;p&gt;Rails 7 was a huuuge update. I'm not going to talk about that in this post but Hotwire. &lt;a href="https://hotwire.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt; became -officially- part of Rails 7. Now you can have real-time interactions, SPA looking Rails projects, without even write a single line of JS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;TR&lt;/strong&gt;&lt;br&gt;
Hotwire can be understood as an umbrella/approach. Inside this thing you will find 2 little boys, Turbo, that replaces TurboLinks, and Stimulus, which is in their own words: &lt;em&gt;A modest JavaScript framework for the HTML you already have&lt;/em&gt; or, the thing that we're going to use when we need to listen some button clicks :p&lt;/p&gt;

&lt;h3&gt;
  
  
  Hands on 👊
&lt;/h3&gt;

&lt;p&gt;I'm going to use a clean project, you can skip this if you want. Therefore:&lt;br&gt;
&lt;code&gt;$ rails new notifications_with_hotwire -d=postgresql -T&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;???&lt;br&gt;
&lt;code&gt;-d=postgresql&lt;/code&gt; specify the DB that we're going to use. By default, Rails uses sqlite3.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-T&lt;/code&gt; skips test files.&lt;/p&gt;

&lt;p&gt;To make it more real, let's include &lt;a href="https://github.com/heartcombo/devise" rel="noopener noreferrer"&gt;Devise&lt;/a&gt;, and &lt;a href="https://github.com/rails/tailwindcss-rails" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; with their icons library, &lt;a href="https://github.com/bharget/heroicon" rel="noopener noreferrer"&gt;Heroicons&lt;/a&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 bundle add devise tailwindcss-rails heroicon

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Now we need to setup that gems...&lt;/em&gt;&lt;br&gt;
For tailwind and heroicons just run &lt;code&gt;$ rails tailwindcss:install &amp;amp;&amp;amp; rails g heroicon:install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For Devise, run &lt;code&gt;$ rails g devise:install &amp;amp;&amp;amp; rails g devise User&lt;/code&gt; and follow their notes.&lt;/p&gt;

&lt;p&gt;I'm also going to create a resource that will dispatch notifications later on: &lt;code&gt;$ rails g scaffold Post title body:rich_text user:references&lt;/code&gt;. Don't forget to assign user on create,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;except: &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;:show&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;posts&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="n"&gt;post_params&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 of course, specify the relationship on the User model with: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, let's move to what you're looking for 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  How to notifications
&lt;/h2&gt;

&lt;p&gt;I want to create notifications for different resources, such as posts, comments or likes. For this, i'm using a polymorphic reference. If you're not familiar with, read about that &lt;a href="https://guides.rubyonrails.org/association_basics.html#polymorphic-associations" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 rails g model Notification item:references{polymorphic} user:references viewed:boolean

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Just add a default value for viewed field on the migration:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="ss"&gt;:viewed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Build a basic nav and place the notifications count badge on it with something like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= link_to notifications_path, class: "bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" do %&amp;gt;
  &amp;lt;span class=&lt;/span&gt;&lt;span class="s2"&gt;"sr-only"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;View&lt;/span&gt; &lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt;
  &amp;lt;%= heroicon "bell", variant: :outline, options: { class: "h-6 w-6" } %&amp;gt;
  &amp;lt;%= tag.div id: :notifications_count do %&amp;gt;
    &amp;lt;%= render "notifications/&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="s2"&gt;", count: current_user.unviewed_notifications_count %&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You may noticed that unviewed_notifications_count doesn't exists on our User model so, let's define that as a class method&lt;br&gt;
 on &lt;code&gt;user.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:notifications&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unviewed_notifications_count&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;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unviewed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&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 the &lt;code&gt;app/views/notifications/_count&lt;/code&gt; partial is needed for Turbo. Going to explain this below.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= tag.div id: :notifications_count do %&amp;gt;
  &amp;lt;% if count &amp;gt; 0 %&amp;gt;
    &amp;lt;span class=&lt;/span&gt;&lt;span class="s2"&gt;"h-6 w-6 flex items-center justify-center bg-red-500 rounded-md text-sm"&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="sx"&gt;%= count &amp;gt; 9 ? "+9" : count  %&amp;gt;
    &amp;lt;/span&amp;gt;
  &amp;lt;% end %&amp;gt;
&amp;lt;% end %&amp;gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here's the thing of the whole article (&lt;code&gt;notification.rb&lt;/code&gt;). Turbo actions. Let's take the first callback to explain what's going on.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;broadcast&lt;/em&gt;: now turbo handle the sttuff we built on the &lt;a href="https://dev.to/matiascarpintini/real-time-notification-system-with-sidekiq-redis-and-devise-in-rails-6-33l9"&gt;previous article&lt;/a&gt;. TD;TR: ApplicationCable to stablish connection between the user and the server, create and send jobs to the background to be performed async with Redis.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;prepend/replace/remove&lt;/em&gt;: simple JS actions&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;to&lt;/em&gt;: the target (AKA &lt;a href="https://turbo.hotwired.dev/handbook/streams" rel="noopener noreferrer"&gt;stream&lt;/a&gt;). In this case, i'm establishing connection with the specific signed user.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;target&lt;/em&gt;: simple HTML (or &lt;a href="https://turbo.hotwired.dev/handbook/frames" rel="noopener noreferrer"&gt;turbo_frame&lt;/a&gt;) element used to perform the JS action.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:unviewed&lt;/span&gt;&lt;span class="p"&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="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;viewed: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;default_scope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;latest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;after_create_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="n"&gt;broadcast_prepend_to&lt;/span&gt; &lt;span class="s2"&gt;"broadcast_to_user_&lt;/span&gt;&lt;span class="si"&gt;#{&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;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="ss"&gt;target: :notifications&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;after_update_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="n"&gt;broadcast_replace_to&lt;/span&gt; &lt;span class="s2"&gt;"broadcast_to_user_&lt;/span&gt;&lt;span class="si"&gt;#{&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;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;after_destroy_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="n"&gt;broadcast_remove_to&lt;/span&gt; &lt;span class="s2"&gt;"broadcast_to_user_&lt;/span&gt;&lt;span class="si"&gt;#{&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;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="ss"&gt;target: :notifications&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;broadcast_replace_to&lt;/span&gt; &lt;span class="s2"&gt;"broadcast_to_user_&lt;/span&gt;&lt;span class="si"&gt;#{&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;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="ss"&gt;target: &lt;/span&gt;&lt;span class="s2"&gt;"notifications_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"notifications/count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;count: &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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unviewed_notifications_count&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;Yup, the &lt;em&gt;latest&lt;/em&gt; scope didn't exists either, i like to define that on &lt;code&gt;application_record.rb&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:latest&lt;/span&gt;&lt;span class="p"&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="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created_at DESC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;application.html.erb&lt;/code&gt; create the stream that allows the previous snippet "talk" with the signed user (without this everyone's getting everyone's notifications 😛)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% if &lt;/span&gt;&lt;span class="n"&gt;user_signed_in?&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
  &amp;lt;%= turbo_stream_from dom_id(current_user, :broadcast_to) %&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &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;Now let's put notifications-related stuff on &lt;code&gt;concerns/notificable.rb&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;included&lt;/em&gt; will append where we import this concern. It sets a relationship and have a simple callback that runs once we &lt;em&gt;create&lt;/em&gt; a new resource (post in this case) object.&lt;/li&gt;
&lt;li&gt;If that resource model have the user_ids method, it will create the notifications for that users.
```ruby
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;module Notificable&lt;br&gt;
  extend ActiveSupport::Concern&lt;/p&gt;

&lt;p&gt;included do&lt;br&gt;
    has_many :notifications, as: :item, dependent: :destroy&lt;br&gt;
    after_create_commit :send_notifications_to_users&lt;br&gt;
  end&lt;/p&gt;

&lt;p&gt;def send_notifications_to_users&lt;br&gt;
    if self.respond_to? :user_ids&lt;br&gt;
      self.user_ids&amp;amp;.each do |user_id|&lt;br&gt;
        Notification.create user_id: user_id, item: self&lt;br&gt;
      end&lt;br&gt;
    end&lt;br&gt;
  end&lt;br&gt;
end&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
So then, when we want to create notifications for a new resource, we just need to include that concern on our resource model, in this case `post.rb`
```ruby


include Notificable

def user_ids
  User.where.not(id: self.user_id).ids
end


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, to show the notifications on our application UI, let's respond to /notifications path on &lt;code&gt;notifications_controller.rb&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationsController&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="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&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;@notifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notifications&lt;/span&gt;
    &lt;span class="vi"&gt;@notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;viewed: &lt;/span&gt;&lt;span class="kp"&gt;true&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;Don't forget to notify the controller about this new path on &lt;code&gt;config/routes.rb&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:notifications&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;:index&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And there you go. The notifications index :)&lt;br&gt;
views/notifications/index.html.erb&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"space-y-4"&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="nb"&gt;p&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"text-lg leading-6 font-medium text-gray-900 flex justify-between"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="no"&gt;Notifications&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&amp;gt;
  &amp;lt;ul class="border-t border-b border-gray-200 divide-y divide-gray-200" id="notifications"&amp;gt;
    &amp;lt;%= render @notifications %&amp;gt;
  &amp;lt;/u&lt;/span&gt;&lt;span class="n"&gt;l&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="sr"&gt;/div&amp;gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The previous &lt;code&gt;render @notifications&lt;/code&gt; will look for the &lt;code&gt;views/notifications/_notification.html.erb&lt;/code&gt; partial.&lt;br&gt;
And i like to render a new partial here, since we have a polymorphic relation.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"py-4"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="s2"&gt;"notifications/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notification: &lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That previous &lt;code&gt;render&lt;/code&gt; will look for &lt;code&gt;views/notifications/_post.html.erb&lt;/code&gt; (in this case). If you create notifications for, let's say, likes then you need to create a new partial, called &lt;code&gt;_like&lt;/code&gt;. And customize the notification for that new resource.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-900"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt; just posted:
  &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;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;class: &lt;/span&gt;&lt;span class="s2"&gt;"underline font-medium"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here's the &lt;a href="https://github.com/matias-carpintini/notifications-with-hotwire" rel="noopener noreferrer"&gt;repo&lt;/a&gt; with everything :)&lt;/p&gt;

&lt;p&gt;Oh, and i'm building a job board for devs that want to work remotely. There's already couple job offers for Rails devs, if you're looking for job, &lt;a href="https://workby.io" rel="noopener noreferrer"&gt;check this out&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bye.&lt;/p&gt;

</description>
      <category>hotwire</category>
      <category>rails</category>
      <category>redis</category>
    </item>
    <item>
      <title>Spam filtering system - Bayes classifier</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Mon, 11 Apr 2022 01:26:35 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/spam-filtering-system-bayes-classifier-1d8l</link>
      <guid>https://dev.to/matiascarpintini/spam-filtering-system-bayes-classifier-1d8l</guid>
      <description>&lt;h4&gt;
  
  
  What the f*ck is a Bayes Cassifier?
&lt;/h4&gt;

&lt;h2&gt;
  
  
  The Bayes Rule, "The theory that never died"
&lt;/h2&gt;

&lt;p&gt;A simple 17th century theory for the evaluation of knowledge, criticized for most of the 20th century.&lt;/p&gt;

&lt;p&gt;It helps people evaluate their initial ideas, update and modify them with new information, in order to make better decisions. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Initial beliefs + recent objective data = A new improved belief.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The theory is very robust. In practice, Bayes rule requires multiple calculations, and powerful computers that reintegrate millions of times the probability of an initial belief each time new information arrives. Bayes rule does not generate an absolutely true (exact answer), instead, it uses probability to move step by step toward the most likely conclusion.&lt;/p&gt;

&lt;p&gt;It was discovered and published by two clergymen and amateur mathematicians, the Englishman Thomas Bayes and his Welsh friend Richard Price, during the 18th century. &lt;/p&gt;

&lt;p&gt;The French mathematician Pierre-Simon Laplace developed it in the form in which it is used today. Now we could call it the Bayes-Price-Laplace theory, or GLP for short.&lt;/p&gt;

&lt;p&gt;Part of the initial controversy is due to the fact that during the 40's of the 18th century a harsh controversy had opened about the improbability of Christian miracles. The question was whether there was evidence in the natural world that would help us reach rational conclusions about God the creator, which in the 18th century was known as "the cause" or "the first cause". &lt;/p&gt;

&lt;p&gt;We do not know if Bayes was trying to prove the existence of God as a cause. But we do know that he tried to deal mathematically with the problem of cause and effect;&lt;/p&gt;

&lt;p&gt;During the Cold War, the United States Air Force lost a hydrogen bomb off the coast of Palomares, and the United States Navy began to secretly develop the Bayesian theory for finding underwater objects. &lt;/p&gt;

&lt;p&gt;In 2009, Air France Flight 447 disappeared in the South Atlantic Ocean with 228 people on board. The United States Navy had developed Bayesian search theory enough to end two years of unsuccessful search for AF447 in a week's underwater search.&lt;/p&gt;

&lt;p&gt;Today we use it for an immense amount of things, such as filtering spam and training autopilot systems.&lt;/p&gt;

&lt;p&gt;If you want to know more about the history behind it, take a look to &lt;a href="https://www.youtube.com/watch?v=BcvLAw-JRss"&gt;this video&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Ok, nice. But you haven't answered the previous question!
&lt;/h4&gt;

&lt;h2&gt;
  
  
  Naive Bayes Classification
&lt;/h2&gt;

&lt;p&gt;The Naive Bayes classifier is a machine learning technique that can be used to classify objects such as text documents into two or more classes. A new object it's classified by the similarity between others.&lt;/p&gt;

&lt;p&gt;Despite its "naivete", the naive Bayes method tends to work very well in practice. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“all models are wrong, but some are useful” - George Box&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  Cool, go for it!
&lt;/h4&gt;

&lt;p&gt;So, for our purpose (spam filtering), we need data (a lot of emails), classify each email as spam or ham (legitimate) and then analyze the words independently, in order to get the most common words in each of those classes.&lt;/p&gt;

&lt;p&gt;P.S: You can have many classes as you want. Like Gmail does with Promotions, Updates, Forums or Social.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Initial beliefs:&lt;/em&gt; For simplicity, let says that the half of emails we get are spam. 1:1.&lt;/p&gt;

&lt;p&gt;Now we need to obtain the probability that each of those words appears in spam or ham. The simplest way is to count how many times each word appears in the data and divide the number by the total word count.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;word&lt;/th&gt;
&lt;th&gt;spam&lt;/th&gt;
&lt;th&gt;ham&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;184&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total&lt;/td&gt;
&lt;td&gt;104342&lt;/td&gt;
&lt;td&gt;294554&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this example, the probability that the word "Free" in a spam message appears is 1 out of 567 words. Same exercise for ham: 1 in 24&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recent objective data:&lt;/strong&gt; If we found the word "Free" in our message, it will increase the probability of being spam in 23.6 (567/24)&lt;/p&gt;

&lt;p&gt;1/1 (initial beliefs: half messages we receive are spam) * 23.6 (we just found the world "Free" in our email) = 23.6. &lt;/p&gt;

&lt;p&gt;There are on the average about 23.6 spam messages for each ham message, or to use whole numbers, 236 (23.6*10) spam messages for every 10 ham messages. So, the probability would be: 236 / (236 + 10) * 100 = 95.9%&lt;/p&gt;

&lt;p&gt;To handle the rest of the words in a email, we can use exactly the same procedure. The posterior odds after one word (what we just calculated), will become the prior odds (or the &lt;em&gt;initial belief&lt;/em&gt;) for the next word, and so on.&lt;/p&gt;

&lt;p&gt;You may have noticed that how whole thing will be biased. Since once we are going to analyze the second word, it already have a strong belief that the email is spam, due it has the free world on it, and same with next words. &lt;/p&gt;

&lt;p&gt;That's known as the base rate fallacy, and you can read more about &lt;a href="https://en.wikipedia.org/wiki/Base_rate_fallacy"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;But let's keep it simple, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Land-and-Expand"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  How can i play with this?
&lt;/h3&gt;

&lt;p&gt;I just found &lt;a href="https://github.com/jekyll/classifier-reborn/"&gt;&lt;code&gt;classifier-reborn&lt;/code&gt;&lt;/a&gt;, a gem that keeps it pretty simple. And &lt;a href="https://www.kaggle.com/datasets/venky73/spam-mails-dataset"&gt;this&lt;/a&gt; dataset.&lt;/p&gt;

&lt;p&gt;Here you go:&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'classifier-reborn'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'csv'&lt;/span&gt;

&lt;span class="c1"&gt;# Load dataset&lt;/span&gt;
&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"spam_ham_dataset.csv"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create our Bayes / LSI classifier&lt;/span&gt;
&lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ClassifierReborn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Bayes&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="s1"&gt;'Spam'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ham'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Train the classifier&lt;/span&gt;
&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each_with_index&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&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;dataset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"spam"&lt;/span&gt;
    &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt; &lt;span class="s2"&gt;"Spam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"text"&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;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt; &lt;span class="s2"&gt;"Ham"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"text"&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;# Play with it&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Insert your email here (txt)"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also check &lt;a href="https://github.com/sausheong/naive-bayes"&gt;this repo&lt;/a&gt;, to see how it looks without the gem :p&lt;/p&gt;




&lt;p&gt;Speaking of spam... I'm going to launch a job board for devs that want to work remotely, and i'm looking for feedback, &lt;a href="https://workby.io"&gt;take a look&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Bye.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Building a Rails Recommendation Engine</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Sun, 24 May 2020 23:33:51 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/building-a-rails-recommendation-engine-216o</link>
      <guid>https://dev.to/matiascarpintini/building-a-rails-recommendation-engine-216o</guid>
      <description>&lt;p&gt;Just to give you some context, we were working on the new Diabecarp App a few days ago. I can't talk much about the new features, but I am going to extrapolate 2 interesting solutions during the process, give you why, explain a bit and, as always, put together a demo with Rails ♥️.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First Requirement -&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation engine
&lt;/h2&gt;

&lt;p&gt;Without giving many details about the case itself, we have a very large amount of content. Various resources (such as Articles or Recipes). Therefore, we must find a way to organize it efficiently according to the user's interest. Come on, nothing new, right?&lt;/p&gt;

&lt;p&gt;So, the first thing we did was ask ourselves, what do we have at our disposal to better understand the user and their interests? At this point, due to early stage, we can only use: likes and views.&lt;/p&gt;

&lt;p&gt;The next thing we did was go over the different ways of understanding our user. And we opted for the one that seems simpler, and not for that less elegant.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌎 Collaborative Filtering
&lt;/h3&gt;

&lt;p&gt;It is based on the premise that we all have certain similarities, and therefore, likes and interests. And precisely this simplicity is what makes this algorithm extremely elegant.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Collaborative_filtering"&gt;Wikipedia&lt;/a&gt;:&lt;/strong&gt; Collaborative filtering is a method of making automatic predictions (filtering) about the interests of a user by collecting preferences or taste information from many users (collaborating).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most websites like Amazon, YouTube, and Netflix use collaborative filtering as a part of their sophisticated recommendation systems. You can use this technique to build recommenders that give suggestions to a user on the basis of the likes and dislikes of similar users.&lt;/p&gt;

&lt;p&gt;Let's see with an illustration, surely they understand me instantly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jLuWGF-E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ibb.co/4dbd75K/cf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jLuWGF-E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ibb.co/4dbd75K/cf.jpg" alt="cf" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also very common to see this with &lt;em&gt;&lt;strong&gt;Content Based Filtering&lt;/strong&gt;&lt;/em&gt;, the same as with users, only here we compare the relationship of the content. Something like that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TBkAQ2Tb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ibb.co/0m41BX6/cbf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TBkAQ2Tb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ibb.co/0m41BX6/cbf.jpg" alt="cbf" width="800" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; For the rest of the article I will touch on both options, I just want to make it clear that the concept is the same, the similarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 Let's see an example&lt;/strong&gt; of how to recommend similar content (in this case, songs).&lt;/p&gt;

&lt;p&gt;First, we take each pair of songs in our database and calculate a similarity between the two songs. To find this similarity, we compared the lists of users who liked the two songs. If many people liked both songs, it is likely that they are quite similar. And if not many people liked both songs, they are probably less similar. A table of similarities could look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Another Brick in the Wall&lt;/th&gt;
&lt;th&gt;Hey You&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Another Brick in the Wall&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hey You&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once we have this list of similarity scores between songs, it's pretty easy to provide recommendations. If a user is listening to a song, we can provide a "Similar Songs" list by simply finding the most similar songs in our previous metric.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenges with Collaborative Filtering
&lt;/h3&gt;

&lt;p&gt;Basically, there are 2 big problems.&lt;/p&gt;

&lt;p&gt;1.- &lt;strong&gt;Cold Start:&lt;/strong&gt;&lt;br&gt;
Starting without enough data, such as users and reactions of those users that allow us to multiply matrices and reach a good result (later).&lt;/p&gt;

&lt;p&gt;This clear problem has a solution, only that we find a much simpler return. Basically, in the index of each resource (eg. Articles) we have a top section with 10 recommended articles and below the rest ordered by 'most recent'.&lt;/p&gt;

&lt;p&gt;This is how we get those recent ones to get the traction they deserve. Here it can be automatically filtered to the top section or disappear completely. Finally, we filter those that the user already read and end.&lt;/p&gt;

&lt;p&gt;2.- &lt;strong&gt;Scalability:&lt;/strong&gt;&lt;br&gt;
The more users and more data, the more expensive it becomes to compare these matrices.&lt;/p&gt;

&lt;p&gt;As we do not have much time or resources, we decided to take this technical debt, considering that very possibly in the future we will have to migrate.&lt;/p&gt;



&lt;p&gt;Now let's see a real implementation so that you can understand me.&lt;/p&gt;

&lt;p&gt;There are already several gems developed for this need, yet several are old. I'm not 100% sure they still work, at the end of the day at Diabecarp we have another stack. Maybe we can write a new one in the future 😉&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Pathgather/predictor"&gt;Predictor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/davidcelis/recommendable"&gt;Recommendable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/geoffreylitt/simple_recommender"&gt;Simple Recommender&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I choose the last one because the rest works with Redis and I no longer want to extend the post. Let's go! 👊&lt;/p&gt;

&lt;p&gt;To find the similarity of two songs, we need to take the user IDs that liked each of the two songs, and compute: (size of intersection of sets) / (&lt;a href="https://en.wikipedia.org/wiki/Jaccard_index"&gt;Jaccard similarity coefficient&lt;/a&gt;). Because we have to do this computation for every pair of songs, performance becomes important.&lt;/p&gt;

&lt;p&gt;One strategy used by the Redis-based gems is to push the similarity computation into our datastore– we want to avoid the overhead of sending each pair of ID sets back and forth to our application server, especially if those are large sets. We also get extra performance points if our datastore has primitives that help make the similarity computation faster.&lt;/p&gt;

&lt;p&gt;Fortunately, these are both problems that can be solved with a relational database. SQL is totally flexible enough to express a single query that computes many item similarities at once. Also, postgres happens to have a convenient extension called intarray which provides efficient intersection and union operations for arrays of integers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Setup the gem...&lt;/em&gt;&lt;br&gt;
Specify an ActiveRecord association to use for recommendation (&lt;code&gt;./app/models/song.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Song&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;:likes&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="ss"&gt;through: :likes&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;SimpleRecommender&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Recommendable&lt;/span&gt;
  &lt;span class="n"&gt;similar_by&lt;/span&gt; &lt;span class="ss"&gt;:users&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 you can call similar_items to find similar items based on who liked them:&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="n"&gt;song&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s2"&gt;"Another Brick in the Wall"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;similar_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;n_results: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; [#&amp;lt;Song id: 2, name: "Time"&amp;gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;#&amp;lt;Song id: 3, name: "Hey You"&amp;gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;#&amp;lt;Song id: 4, name: "Wish You Where Here"&amp;gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scope is composing a query that operates on its &lt;em&gt;join table&lt;/em&gt;, like a Like table with user_id and song_id. It uses &lt;a href="https://www.postgresql.org/docs/9.2/static/queries-with.html"&gt;common table expressions&lt;/a&gt; to create a temporary table with one row per pair of songs, and computes the similarity for each row of that table. That temporary table looks something like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Song 1 Name&lt;/th&gt;
&lt;th&gt;Song 1 User IDs&lt;/th&gt;
&lt;th&gt;Song 2 Name&lt;/th&gt;
&lt;th&gt;Song 2 User IDs&lt;/th&gt;
&lt;th&gt;Similarity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Another Brick in the Wall&lt;/td&gt;
&lt;td&gt;{1, 2, 3, 4, 5}&lt;/td&gt;
&lt;td&gt;Hey You&lt;/td&gt;
&lt;td&gt;{1, 3, 4, 5, 6}&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Another Brick in the Wall&lt;/td&gt;
&lt;td&gt;{1, 2, 3, 4, 5}&lt;/td&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;td&gt;{3, 8, 10, 12, 13}&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then it just looks for the highest singularity and returns it.&lt;/p&gt;

&lt;p&gt;Ok, that's it for today. In the following article I would like to talk about how we are using Bayesian Networks or even how we prepare for onboarding.&lt;/p&gt;

&lt;p&gt;BTW, if you are interested in the project, we are still looking for devs!&lt;/p&gt;

&lt;p&gt;👋 Bye&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Real Time Notification System with Sidekiq, Redis and Devise in Rails 6</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Sat, 25 Apr 2020 03:06:11 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/real-time-notification-system-with-sidekiq-redis-and-devise-in-rails-6-33l9</link>
      <guid>https://dev.to/matiascarpintini/real-time-notification-system-with-sidekiq-redis-and-devise-in-rails-6-33l9</guid>
      <description>&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt;&lt;br&gt;
Just published a new article on how to do this in Rails 7, with Hotwire.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/matiascarpintini" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KChRhTyM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/practicaldev/image/fetch/s--xHLMeWmj--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/369241/a0111bcb-a046-4398-98cd-f4cf0e2f8d0d.png" alt="matiascarpintini"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/matiascarpintini/real-time-notification-system-with-hotwire-in-rails-7-1j9f" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Real Time Notification System with Hotwire, in Rails 7&lt;/h2&gt;
      &lt;h3&gt;Matias Carpintini ・ Apr 13 '22 ・ 5 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#hotwire&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#rails&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#redis&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;You can read this one anyways, here i'm going to explain things that in the new one we didn't see ;)&lt;/p&gt;




&lt;p&gt;In this post, we are going to talk a lot about asynchronous functions, something very fashionable nowadays, but not so much just a few years ago.&lt;/p&gt;

&lt;p&gt;With this I do not mean that it is something necessarily new, but I believe that thanks to the JS ecosystem today the world happens in real time.&lt;/p&gt;

&lt;p&gt;In this post, I want to teach the key concepts of this. But as always, we do not stay in theory and we are going to see a real implementation, such as real-time notifications from our application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I will try to be as brief and simple as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Definitions and concepts -
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Asynchronous programming:&lt;/em&gt; refers to the occurrence of events that happen in our program. They are executed independently of our main program, and never interfere with their execution. Prior to this, we were forced to wait for a response to continue execution, seriously impairing the user experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://medium.com/swift-india/concurrency-parallelism-threads-processes-async-and-sync-related-39fd951bc61d"&gt;Concurrency, Parallelism, Threads, Processes, Async and Sync — Related? 🤔&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;WebSockets:&lt;/em&gt; WebSockets represent a long awaited evolution in client/server web technology. They allow a long-held single TCP socket connection to be established between the client and server which allows for bi-directional, full duplex, messages to be instantly distributed with little overhead resulting in a very low latency connection. &lt;a href="https://pusher.com/websockets"&gt;Keep reading&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other words, it allows us to establish a peer-to-per connection between the client and the server. Before this, the client was only the one who knew where the server was, but not vice versa.&lt;/p&gt;

&lt;p&gt;Thanks to this, we can send a request to the server, and continue executing our program, without waiting for your response. Then the server knows where the client is, and can send you the response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zS1VHPad--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ibb.co/syY39yK/Web-Sockets-Diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zS1VHPad--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.ibb.co/syY39yK/Web-Sockets-Diagram.png" alt="WebSockets Diagram" width="640" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's do it 👊
&lt;/h3&gt;

&lt;p&gt;All of the above already makes sense for our notification system, right?&lt;br&gt;
Before continuing, make sure you have Redis installed. Sidekiq uses Redis to store all of its job and operational data.&lt;/p&gt;

&lt;p&gt;👋 &lt;em&gt;If you do not know what Redis is, you can discover it on its &lt;a href="https://redis.io/"&gt;official site&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mperham/sidekiq"&gt;Sidekiq&lt;/a&gt; helps us to work in the background in a super simple and efficient way. (Also, one of my favorite gems ♥️)&lt;/p&gt;

&lt;p&gt;I created &lt;a href=""&gt;this project&lt;/a&gt; for this article in order to focus directly on what interests us. The project is a simple blog with user authentication and the necessary front to display our notifications. You can download it and follow the article with me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; you can see the full implementation in the "notifications" branch&lt;/p&gt;
&lt;h4&gt;
  
  
  Init configuration...
&lt;/h4&gt;

&lt;p&gt;In &lt;code&gt;config/routes.rb&lt;/code&gt; we will mount the routes of &lt;a href="https://edgeguides.rubyonrails.org/action_cable_overview.html"&gt;ActionCable&lt;/a&gt; (framework for real-time communication over websockets)&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="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;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# everything else...&lt;/span&gt;
  &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/cable'&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, do you remember how WebSockets works? A peer-to-peer connection, well in other words, is also a &lt;em&gt;channel&lt;/em&gt; (as we call it in Rails), and within that channel, we have to always identify each user. This so that the server can know who to reply to, and know who made a request. In this case, we will identify it with the user.id (I am using devise)&lt;/p&gt;

&lt;p&gt;So, in &lt;code&gt;app/channels/application_cable/connection.rb&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ApplicationCable&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Connection&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;identified_by&lt;/span&gt; &lt;span class="ss"&gt;:current_user&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;connect&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;current_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_user&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;find_user&lt;/span&gt;
      &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;current_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&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;user_id&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;current_user&lt;/span&gt;
        &lt;span class="n"&gt;current_user&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;reject_unauthorized_connection&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to save the user who logged in a cookie (it will help us to obtain it from other places, we will see), an interesting solution for this (at least with Devise) is using &lt;a href="https://github.com/warden-stack/Warden/wiki/Hooks"&gt;Warden Hooks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For that, we can create an initializer on our application, &lt;code&gt;config/initializers/warden_hooks.rb&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="no"&gt;Warden&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;after_set_user&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.id"&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="nf"&gt;id&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.expires_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_now&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;Warden&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before_logout&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"user.expires_at"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&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, let's create a table in our database to save every notification we create, for this, &lt;code&gt;$ rails g model Notification user:references item:references viewed:boolean&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; &lt;em&gt;:item&lt;/em&gt; is a &lt;a href="https://guides.rubyonrails.org/association_basics.html#polymorphic-associations"&gt;polymorphic association&lt;/a&gt;, I do it this way so they can add various types of notifications)&lt;/p&gt;

&lt;p&gt;Let's specify this and other details in our migration (&lt;code&gt;db/migrate/TIMESTAMP_create_notifications.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateNotifications&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;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:notifications&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;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;foreign_key: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt; &lt;span class="ss"&gt;:item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="ss"&gt;:viewed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&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, &lt;code&gt;$ rails db:migrate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;app/models/notification.rb&lt;/code&gt; we are going to do a couple of things that we will see on the go&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Indicates a polymorphic reference&lt;/span&gt;

  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;NotificationBroadcastJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# We make this later&lt;/span&gt;

  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:leatest&lt;/span&gt;&lt;span class="p"&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="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created_at DESC"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:unviewed&lt;/span&gt;&lt;span class="p"&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="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;viewed: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="c1"&gt;# This is like a shortcut&lt;/span&gt;

  &lt;span class="c1"&gt;# This returns the number of unviewed notifications&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Notification&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_id: &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unviewed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&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 create a &lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Concern.html"&gt;&lt;em&gt;concern&lt;/em&gt;&lt;/a&gt;, remember that one of the most respected Rails philosophies is DRY (Don't Repeat Yourself), currently, each notification requires the same to work (in models)(again, in this project we only have publications, but We could have many other things that we want to integrate with our notification system, so with this form it is super simple).&lt;/p&gt;

&lt;p&gt;For that, &lt;code&gt;app/models/concerns/notificable.rb&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Notificable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt; &lt;span class="c1"&gt;# module '::'&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="c1"&gt;# this appends in each place where we call this module&lt;/span&gt;
    &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:notifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :item&lt;/span&gt;
    &lt;span class="n"&gt;after_commit&lt;/span&gt; &lt;span class="ss"&gt;:send_notifications_to_users&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;send_notifications_to_users&lt;/span&gt;
    &lt;span class="k"&gt;if&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;respond_to?&lt;/span&gt; &lt;span class="ss"&gt;:user_ids&lt;/span&gt; &lt;span class="c1"&gt;# returns true if the model you are working with has a user_ids method&lt;/span&gt;
      &lt;span class="no"&gt;NotificationSenderJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&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;Now we can include it in our &lt;code&gt;app/models/post.rb&lt;/code&gt;. Remember that our &lt;code&gt;send_notifications_to_users&lt;/code&gt; expects the method &lt;code&gt;user_ids&lt;/code&gt; to reply to you with the respective fix. Let's do that (app/models/post.rb):&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Notificable&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_ids&lt;/span&gt;
    &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ids&lt;/span&gt; &lt;span class="c1"&gt;# send the notification to that users&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 are going to create the &lt;a href="https://edgeguides.rubyonrails.org/active_job_basics.html"&gt;job&lt;/a&gt; in charge of sending the notifications, this is what we will send to the background and we will handle with Sidekiq. For that, &lt;code&gt;$ rails g job NotificationSender&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside the job (&lt;code&gt;app/jobs/notification_sender_job.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationSenderJob&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="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# this method dispatch when job is called&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&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;user_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="no"&gt;Notification&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;item: &lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&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;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;Finnally, we need to install Sidekiq (and Sinatra for make few things a little more easier), so, in out &lt;code&gt;Gemfile&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;# everything else...&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sinatra'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 2.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 2.0.8.1'&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'sidekiq'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 6.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 6.0.7'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget, &lt;code&gt;$ bundle install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We are going to tell Rails that we will use Sidekiq for jobs on the queue adapter (&lt;code&gt;config/application.rb&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;# everything else...&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Blog&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
    &lt;span class="c1"&gt;# everything else...&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;active_job&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;:sidekiq&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 are also going to set up the routes that Sidekiq provides us, among them, a kind of backoffice for our background (later you can have acces from localhost:3000/sidekiq), very interesting. In &lt;code&gt;config/routes.rb&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="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sidekiq/web'&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;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# everything else...&lt;/span&gt;
  &lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Web&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'/sidekiq'&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 we are going to create the channel through which we will transmit our notifications. &lt;code&gt;$ rails g channel Notification&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the backend of this channel (&lt;code&gt;app/channels/notification_channel.rb&lt;/code&gt;), we will subscribe users:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
    &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="s2"&gt;"notifications.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# in this way we identify to the user inside the channel later&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;unsubscribed&lt;/span&gt;
    &lt;span class="c1"&gt;# Any cleanup needed when channel is unsubscribed&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 in the frontend of the channel (&lt;code&gt;app/javascript/channels/notification_channel.js&lt;/code&gt;) it would be interesting to send a push notification to the browser, there are many JS libraries that make that very easy (like &lt;a href="https://pushjs.org/"&gt;this&lt;/a&gt;), but in order not to make the post much heavier, we are going to print a simple message on the console. So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// everything else...&lt;/span&gt;
&lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NotificationChannel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// everything else...&lt;/span&gt;
  &lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new_notification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="nx"&gt;cosole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`New notification! Now you have &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; unread notifications`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// we will define action &amp;amp; message in the next step&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we already have a lot running, let's send that notification to the user! For this we are going to create another job that just does this, remember, the previous job is in charge of creating the notifications, this one does the broadcast. So, &lt;code&gt;$ rails g job NotificationBroadcast&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;app/jobs/notification_broadcast_job.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationBroadcastJob&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="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:default&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notification_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notification&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="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt; &lt;span class="s2"&gt;"notifications.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt; &lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;"new_notification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="n"&gt;notification_count&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;Fantastic, we already have everything working! 🎉&lt;br&gt;
I'm going to add a few things to the backend to end the example.&lt;/p&gt;

&lt;p&gt;First of all, I'm going to add a method to my user model so I can count the notifications I haven't seen yet. And the model is a good place to do this query. In &lt;code&gt;app/models/user.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="c1"&gt;# everything else...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unviewed_notifications_count&lt;/span&gt;
    &lt;span class="no"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_user&lt;/span&gt;&lt;span class="p"&gt;(&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;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;I'm also going to create a controller, &lt;code&gt;$ rails g controller Notifications index&lt;/code&gt;. Inside the controller (&lt;code&gt;app/controllers/notifications_controller.rb&lt;/code&gt;) i'm going to add some methods:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationsController&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@notifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Notification&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;current_user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;unviewed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leatest&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;js&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="nf"&gt;update&lt;/span&gt;
    &lt;span class="vi"&gt;@notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notification_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"Viewed notification"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"There was an error"&lt;/span&gt;

    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="ss"&gt;:back&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="n"&gt;message&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;notification_params&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:notification&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:viewed&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;I will create a js view to be able to respond remote and display the latest notifications in my dropdown in the nav. In &lt;code&gt;app/helpers/notifications_helper.rb&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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;NotificationsHelper&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_notifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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;notification&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"notifications/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item_type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="ss"&gt;notification: &lt;/span&gt;&lt;span class="n"&gt;notification&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;html_safe&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;Add the link in your nav, in my case (&lt;code&gt;app/views/partials/notifications.html.erb&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="na"&gt;notifications_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;remote:&lt;/span&gt; &lt;span class="na"&gt;true&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data:&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type:&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's not forget to add the paths (&lt;code&gt;app/config/routes.rb&lt;/code&gt;) for this new controller.&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;# everything else...&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;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# everything else...&lt;/span&gt;
  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:notifications&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;:index&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="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just create a partial for this item (like &lt;code&gt;app/views/notifications/_post.rb&lt;/code&gt;). They can include a link to 'mark as seen', in this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="na"&gt;notification_path&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id:&lt;/span&gt; &lt;span class="na"&gt;notification&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;notification:&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;viewed:&lt;/span&gt; &lt;span class="na"&gt;true&lt;/span&gt;&lt;span class="err"&gt;}),&lt;/span&gt; &lt;span class="na"&gt;method:&lt;/span&gt; &lt;span class="na"&gt;:put&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run this locally you will have to run Redis (&lt;code&gt;$ redis-server&lt;/code&gt;) and Sidekiq (&lt;code&gt;$ bundle exec sidekiq&lt;/code&gt;) + &lt;code&gt;$ rails s&lt;/code&gt;, have 3 terminal windows open with these 3 commands running in parallel.&lt;/p&gt;

&lt;p&gt;That is all, I hope it is useful to you 👋&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>redis</category>
    </item>
    <item>
      <title>Magic Links with Ruby On Rails and Devise</title>
      <dc:creator>Matias Carpintini</dc:creator>
      <pubDate>Sat, 18 Apr 2020 21:47:35 +0000</pubDate>
      <link>https://dev.to/matiascarpintini/magic-links-with-ruby-on-rails-and-devise-4e3o</link>
      <guid>https://dev.to/matiascarpintini/magic-links-with-ruby-on-rails-and-devise-4e3o</guid>
      <description>&lt;p&gt;Passwords are a problem for users. A good practice to skip this is by sending an access link to your account by email, that link has a token that we will use to validate and finally, sign in to the user to their account. Next, I'm going to show you an implementation in Ruby On Rails 6 with Devise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I am going to create an application from 0 for avoid mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up
&lt;/h3&gt;

&lt;p&gt;Create the project: &lt;code&gt;$ rails new magicLinks -T --skip-turbokinks --database=postgresql&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Installing dependencies...
&lt;/h4&gt;

&lt;p&gt;Add this to your &lt;code&gt;Gemfile.rb&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="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'devise'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 4.7'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 4.7.1'&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;'letter_opener'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'~&amp;gt; 1.7'&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="sr"&gt;/ For open emails in the browser
end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, &lt;code&gt;$ bundle install&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Let's install Devise:&lt;/strong&gt; with &lt;code&gt;$ rails g devise:install &amp;amp;&amp;amp; rails g devise User &amp;amp;&amp;amp; rails db:create &amp;amp;&amp;amp; rails db:migrate&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="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_mailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_url_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="p"&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_mailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:letter_opener&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Devise asks us for a root path, let's do that: &lt;code&gt;$ rails g controller welcome index&lt;/code&gt;.&lt;br&gt;
Add this in your &lt;code&gt;app/config/routes.rb&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="n"&gt;root&lt;/span&gt; &lt;span class="s1"&gt;'welcome#index'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Let's do it 👊
&lt;/h3&gt;

&lt;p&gt;First, we need to create a table for save the generated tokens on our db.&lt;br&gt;
So, &lt;code&gt;$ rails g model EmailLink token expires_at:datetime user:references &amp;amp;&amp;amp; rails db:migrate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now we are going to generate the token and send the email when this happens, for that, in &lt;code&gt;app/models/email_link.rb&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailLink&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:send_mail&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &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;return&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;create&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="ss"&gt;expires_at: &lt;/span&gt;&lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;token: &lt;/span&gt;&lt;span class="n"&gt;generate_token&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="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_token&lt;/span&gt;
    &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;friendly_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&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;send_mail&lt;/span&gt;
    &lt;span class="no"&gt;EmailLinkMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign_in_mail&lt;/span&gt;&lt;span class="p"&gt;(&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;deliver_now&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 generate the controller to trigger the previous callback when the user gives us their email and sends the form, &lt;code&gt;rails g controller EmailLinks new create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's add the necessary routes in our &lt;code&gt;app/config/routes.rb&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="n"&gt;root&lt;/span&gt; &lt;span class="s1"&gt;'welcome#index'&lt;/span&gt;
&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'email_links/new'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :new_magic_link&lt;/span&gt;
&lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s1"&gt;'email_links/create'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :magic_link&lt;/span&gt;
&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'email_links/validate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;as: :email_link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's give functionality to our controller:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailLinksController&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&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;create&lt;/span&gt;
    &lt;span class="vi"&gt;@email_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EmailLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@email_link&lt;/span&gt;
      &lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:notice&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Email sent! Please, check your inbox."&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="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:alert&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"There was an error, please try again!"&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;new_magic_link_path&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="nf"&gt;validate&lt;/span&gt;
    &lt;span class="n"&gt;email_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;EmailLink&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;token: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:token&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="s2"&gt;"expires_at &amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;

    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;email_link&lt;/span&gt;
      &lt;span class="n"&gt;flash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:alert&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid or expired token!"&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;new_magic_link_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;sign_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: :user&lt;/span&gt;&lt;span class="p"&gt;)&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="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, our program is capable of generating the token, validating it and logging in. What we have left is to send the email and the view where the user gives us their email.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: If you want to see these alerts, you must add them in &lt;code&gt;app/views/layouts/application.html.erb&lt;/code&gt;, inside the body tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"notice"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;notice&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sending the email
&lt;/h4&gt;

&lt;p&gt;Very easy, first we are going to generate our mailer, &lt;code&gt;$ rails g mailer EmailLinkMailer&lt;/code&gt; and then we give it functionality:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailLinkMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sign_in_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email_link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email_link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;

    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"Here is your magic link! 🚀"&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 edit our email (&lt;code&gt;app/views/email_link_mailer/sign_in_mail.html.erb&lt;/code&gt;) a little:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hello, &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;user.email&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Recently someone requested a link to enter your account, if it was you, just press the button below to log in&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt; &lt;span class="na"&gt;my&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;email_link_url&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;token:&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Brilliant! At this point our program is already capable of sending the email, we just need the main view, where the user will give us their email and we can fire all this backend.&lt;/p&gt;

&lt;p&gt;Just add this simple form in &lt;code&gt;app/views/email_links/new.html.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;form_with&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;url:&lt;/span&gt; &lt;span class="na"&gt;magic_link_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method:&lt;/span&gt; &lt;span class="na"&gt;:post&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;label_tag&lt;/span&gt; &lt;span class="na"&gt;:email&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;E-mail&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;email_field_tag&lt;/span&gt; &lt;span class="na"&gt;:email&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nil&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;placeholder:&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;carpintinimatias&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;gmail.com&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;autofocus:&lt;/span&gt; &lt;span class="na"&gt;true&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required:&lt;/span&gt; &lt;span class="na"&gt;true&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;submit_tag&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Send&lt;/span&gt;&lt;span class="err"&gt;!"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To validate on the front that this works, we can add this dummy example in &lt;code&gt;app/views/welcome/index.html.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="na"&gt;user_signed_in&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hello, &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;current_user.email&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Sign&lt;/span&gt; &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;destroy_user_session_path&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method:&lt;/span&gt; &lt;span class="na"&gt;:delete&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;else&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Hey, Sign In with Magic Links!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;link_to&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;Click&lt;/span&gt; &lt;span class="na"&gt;here&lt;/span&gt;&lt;span class="err"&gt;",&lt;/span&gt; &lt;span class="na"&gt;new_magic_link_path&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All right, that's it. Thanks for reading. 👋&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
