<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://caioramos.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://caioramos.dev/" rel="alternate" type="text/html" /><updated>2025-01-17T22:33:47+00:00</updated><id>https://caioramos.dev/feed.xml</id><title type="html">Caio Ramos</title><subtitle>Personal page and blog
</subtitle><author><name>Caio Ramos</name></author><entry><title type="html">A family request</title><link href="https://caioramos.dev/a-family-request.html" rel="alternate" type="text/html" title="A family request" /><published>2025-01-17T15:00:00+00:00</published><updated>2025-01-17T15:00:00+00:00</updated><id>https://caioramos.dev/a-family-request</id><content type="html" xml:base="https://caioramos.dev/a-family-request.html"><![CDATA[<h2 id="why-i-build-an-inventory-management-system">Why I build an inventory management system</h2>

<p>My parents recently started producing dairy products in their small farm and wanted a way to better manage the inventory and sales. Initially the workflow was:</p>

<ul>
  <li>Write down orders from friends and family;</li>
  <li>Produce the products;</li>
  <li>Deliver the orders and receive payments.</li>
</ul>

<p>Their struggle was mainly with order management, as they had to keep track of the orders, the products needed to produce them, and payment and deliver status. They asked me to help them with a solution, and I thought it would be a good opportunity to put in practice some of the things I recently learned about Ruby on Rails.</p>

<h2 id="the-stack">The stack</h2>

<p>The project started with the following stack:</p>

<ul>
  <li>Rails 8 with Ruby 3.3.5;</li>
  <li>SQLite3 for development, testing, and production;</li>
  <li>Turbo and Stimulus for the frontend;</li>
  <li><a href="https://github.com/lazaronixon/css-zero">css-zero</a> for styling;</li>
  <li>solid-queue/solid-cache/solid-cable for background jobs, caching, and websockets;</li>
  <li>authentication-zero to bootstrap some authentication features.</li>
</ul>

<h2 id="development-process">Development process</h2>

<p>The initial features of the application were a way to manage customers, products, and orders. The schema of the application was as follows:</p>

<p><img src="/assets/primavera-erd.png" alt="ERD diagram of the application" /></p>

<p>The application was intended to be used only by my parents, so I didn’t create any tenant relationship between users and the other models.</p>

<p>When I got the initial version working, I needed a way to deploy and make the application available to them. Since I’ve been hearing a lot about Kamal, I decided to take a stab. Created a VPS at Hetzner, configured my DNS, added the configuration to the YML file, and ran the deploy command. After some struggles with Cloudflare SSL, the application was up and running.</p>

<p>They got very excited about what I had built, and started to request features. One of cool things I could experience was the feedback loop. They came to me with a problem, and I as the PM/Dev/CEO/CTO had to come up with a solution. The end user is very good at describing the problems they have, but usually not great at proposing solutions. So I had to come up with ideas that would solve their problems and be feasible with the technology I was using and time available that I had.</p>

<h2 id="current-state">Current state</h2>

<p>They have been using the application for a couple of months now, and I was able to add some new features, such as:</p>

<ul>
  <li>Multi-tenancy: introduced the concept of organizations/memberships, so in the future the application can support other sub-companies outside the dairy domain;</li>
  <li>Backups: integrated Litestream to the application to make sure no data would be lost in the event of a VPS/storage failure;</li>
  <li>Active storage: added the ability to upload images to products;</li>
</ul>

<p>Some other small quality of life features were also added like basic payment integration with Pix payment links, a simple dashboard with some metrics, a public order and menu page, and a way to add product variants to each of the products.</p>

<p>The latest change to the application was the addition of support to the PWA standard, so it can be installed on their phones and used offline. This was done using the workbox library by Google, that made managing the service worker and caching strategies very easy. This combined with View Transitions (from Turbo) made the application feel very snappy and responsive.</p>

<h2 id="demo">Demo</h2>

<p>Here’s a small demo of the application:</p>

<p><img src="/assets/primavera-demo.gif" alt="Demo of the application" /></p>

<h2 id="conclusion">Conclusion</h2>

<ul>
  <li>Hetzner + Kamal was pretty easy to setup and deploy. One thing I missed was to have metrics to monitor the current state of the VPS and the application;</li>
  <li>css-zero is straightforwad to use and the examples are very good. Not having to think much about styling was a good thing for this project;</li>
  <li>Cloudflare worked great for me, specifically to manage DNS and caching. Rails’ defaults work in harmony with Cloudflare’s caching strategies and nothing needs to be configured to get CDN benefits;</li>
  <li>Litestream is an incredible tool. After I discovered it I went into the SQLite rabbit hole, leaning more about Ben Johnson, and all the cool use cases that are currently available for modern SQLite;</li>
</ul>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="ruby" /><category term="rails" /><summary type="html"><![CDATA[Why I build an inventory management system]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://caioramos.dev/logo.png" /><media:content medium="image" url="https://caioramos.dev/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Rails World 2024, Kamal, #NoPaaS</title><link href="https://caioramos.dev/rails-world-kamal-nopass.html" rel="alternate" type="text/html" title="Rails World 2024, Kamal, #NoPaaS" /><published>2024-10-18T01:00:00+00:00</published><updated>2024-10-18T01:00:00+00:00</updated><id>https://caioramos.dev/rails-world-kamal-nopass</id><content type="html" xml:base="https://caioramos.dev/rails-world-kamal-nopass.html"><![CDATA[<h2 id="rails-world-2024">Rails World 2024</h2>

<p>I had the amazing opportunity to be at Rails World 2024 in Toronto, CA. It was my first time in Canada and attending an international conference. I had the opportunity to meet some very nice people, and chat about one of the things I most enjoy doing: programming.
Being there was a confirmation of the incredible community behind the Ruby on Rails ecosystem. People that not only love technology, but also the journey of building web applications with tools that bring joy to programmers.</p>

<p>All the talks are now available at Rails World’s <a href="https://www.youtube.com/playlist?list=PLHFP2OPUpCeb182aDN5cKZTuyjn3Tdbqx">Youtube channel</a>.</p>

<h3 id="kamal-and-nopaas">Kamal and #NoPaaS</h3>

<p>One of the most mentioned topics in the conference was the “NoPaaS” concept; it means to deploy your application without the need of platforms like <a href="https://www.heroku.com/">Heroku</a>, <a href="https://render.com/">Render</a>, and <a href="https://railway.app/">Railway</a> (not to mention solutions that require a separate DevOps team to manage and understand it all).
This concept connects well with the new tools that will be defaults for new Rails 8 apps:</p>
<ul>
  <li><a href="https://kamal-deploy.org/">Kamal</a>: deploy Dockerized apps anywhere with SSH access;</li>
  <li>“Solid Stack”: database backed libraries that take advantage of fast hard drives and new DBMS features:
    <ul>
      <li><a href="https://github.com/rails/solid_queue">Solid Queue</a>;</li>
      <li><a href="https://github.com/rails/solid_cache">Solid Cache</a>;</li>
      <li><a href="https://github.com/rails/solid_cable">Solid Cable</a>;</li>
    </ul>
  </li>
  <li>SQLite by default and production ready.</li>
</ul>

<p>Kamal also allows you to run and manage you own “accessories” such as databases, cache storages, and other services that complement your application.
I’m not sure if I would trust myself to run my own Postgres instance in production from scratch, specially when I consider that managed solutions like <a href="https://www.digitalocean.com/pricing/managed-databases#postgresql">Managed Databases fro DigitalOcean</a> start as low as $15 with a lot of out-of-the-box features; but it’s definitely a powerful tool.</p>

<h3 id="more-on-production-sqlite-for-web-servers">More on production SQLite for web servers</h3>

<p>We all know that, by default, SQLite is not suitable to web applications. SQLite defaults aim to be as backwards compatible with the 2000’s version as possible and it really shines with web frameworks like Rails when <a href="https://github.com/rails/rails/pull/49349">correctly configured</a> to handle the concurrent access. Rails 8 + SQLite + Kamal is a killer stack for new companies when we consider that:</p>
<ol>
  <li>All you need to get the first version of your app to the world (w/ caching, async jobs, and real-time updates) is a $5 VPS and Kamal;</li>
  <li>Simple infrastructure means more time for developers to focus on building new features;</li>
  <li>Clear path to overcome bottlenecks (and migrate off SQLite if needed).</li>
</ol>

<h2 id="strive-for-conceptual-and-operational-compression">Strive for conceptual and operational compression</h2>

<p>The Rails community really cares about about simplicity, and sees complexity as the bridge to progress. Not because we want to build simple applications, but because we want to build complex application using the simplest (and most enjoyable) solution possible.
That’s one of the reasons why people choose monoliths over microservices: not because <a href="https://www.youtube.com/watch?v=LcJKxPXYudE">microservices are bad</a>, but because monoliths are the simplest way to get things done.</p>

<p>Engineering teams should optimize development for their current team size and commit to never rewrite their apps. With this mindset you can archive:</p>
<ul>
  <li>Applications that live longer and healthier;</li>
  <li>Developers that can focus on a single codebase to build features and share knowledge;</li>
  <li>Use resources as efficient as possible.</li>
</ul>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="ruby" /><category term="rails" /><summary type="html"><![CDATA[Rails World 2024]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://caioramos.dev/logo.png" /><media:content medium="image" url="https://caioramos.dev/logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">RSS and social media</title><link href="https://caioramos.dev/rss-and-social-media.html" rel="alternate" type="text/html" title="RSS and social media" /><published>2023-10-04T01:00:00+00:00</published><updated>2023-10-04T01:00:00+00:00</updated><id>https://caioramos.dev/rss-and-social-media</id><content type="html" xml:base="https://caioramos.dev/rss-and-social-media.html"><![CDATA[<h2 id="avoiding-social-media">Avoiding social media</h2>
<p>If you feel uncomfortable with the amount of time you spend in social media apps like Instagram browsing through ads and algorithmic content, you’re not alone. A lot of times I find myself unconsciously scrolling my timeline (or clicking across Stories) and loosing track of time. Here is an alternative to be up-to-date with the content you find relevant, without the distractions that these apps are famous for.</p>

<h2 id="rss-to-the-rescue">RSS to the rescue</h2>
<p><a href="https://en.wikipedia.org/wiki/RSS">RSS</a> allows users and applications to access updates to websites in a standardized, computer-readable format. This means that you can have updates from websites without actually accessing them. Here’s an example of a RSS <em>feed</em>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;rss version="2.0"&gt;
&lt;channel&gt;
 &lt;title&gt;RSS Title&lt;/title&gt;
 &lt;description&gt;This is an example of an RSS feed&lt;/description&gt;
 &lt;link&gt;http://www.example.com/main.html&lt;/link&gt;
 &lt;copyright&gt;2020 Example.com All rights reserved&lt;/copyright&gt;
 &lt;lastBuildDate&gt;Mon, 6 Sep 2010 00:01:00 +0000&lt;/lastBuildDate&gt;
 &lt;pubDate&gt;Sun, 6 Sep 2009 16:20:00 +0000&lt;/pubDate&gt;
 &lt;ttl&gt;1800&lt;/ttl&gt;

 &lt;item&gt;
  &lt;title&gt;Example entry&lt;/title&gt;
  &lt;description&gt;Here is some text containing an interesting description.&lt;/description&gt;
  &lt;link&gt;http://www.example.com/blog/post/1&lt;/link&gt;
  &lt;guid isPermaLink="false"&gt;7bd204c6-1655-4c27-aeee-53f933c5395f&lt;/guid&gt;
  &lt;pubDate&gt;Sun, 6 Sep 2009 16:20:00 +0000&lt;/pubDate&gt;
 &lt;/item&gt;

&lt;/channel&gt;
&lt;/rss&gt;
</code></pre></div></div>
<p>To convert this file to human-readable format a news reader or aggregator is used. Aggregation technology consolidates many websites into one page that can show only the new or updated information from many sites.</p>
<h2 id="how-to-get-started">How to get started</h2>
<h3 id="1-find-an-aggregator-that-suits-you">1. Find an aggregator that suits you</h3>
<p>There are plenty of free and paid options out there, search for <code class="language-plaintext highlighter-rouge">feed aggregator</code> and choose one. Almost all of them provide an option to import and export, so don’t hesitate to experiment with different apps.</p>
<blockquote>
  <p>My personal choice is to self-host yarr (https://github.com/nkanaev/yarr)</p>
</blockquote>

<h3 id="2-search-for-the-content-feeds-you-like">2. Search for the content feeds you like</h3>
<p>This is probably the hardest part :( There’s a lot of data that is only available inside platforms, and not accessible without actually using the proprietary Apps (Meta is excellent in this matter). Here are some tips to find content that matters to you:</p>
<ul>
  <li>YouTube channels
<code class="language-plaintext highlighter-rouge">https://www.youtube.com/feeds/videos.xml?channel_id=</code></li>
  <li>Reddit
    <ul>
      <li>Front page: <a href="http://www.reddit.com/.rss">http://www.reddit.com/.rss</a></li>
      <li>A subreddit: <a href="http://www.reddit.com/r/news/.rss">http://www.reddit.com/r/news/.rss</a></li>
      <li>Private feeds: https://www.reddit.com/prefs/feeds</li>
    </ul>
  </li>
  <li>Weather
    <ul>
      <li>https://www.bbc.com/weather/about/17543675</li>
    </ul>
  </li>
  <li>HackerNews
    <ul>
      <li>https://hnrss.github.io/</li>
    </ul>
  </li>
  <li>GitHub releases
    <ul>
      <li>https://github.com/jellyfin/jellyfin/releases.atom</li>
    </ul>
  </li>
  <li>News
    <ul>
      <li>Almost all news website have a feed, search for <code class="language-plaintext highlighter-rouge">rss &lt;news provider&gt;</code>
        <h2 id="pro-tips">Pro tips</h2>
      </li>
    </ul>
  </li>
  <li><a href="https://github.com/DIYgod/RSSHub">RSSHub</a> is great to get feeds for stuff that is not easily available like Telegram channels, Spotify-only podcasts, etc.
    <ul>
      <li>I self-host it myself and it saves me a lot with content from “the outside”</li>
    </ul>
  </li>
  <li>Change the FOMO mentality; don’t beat yourself you for not being able to keep every reading up-to-date, or to miss a couple of posts. If it’s something really important, I’m sure the information will get to you in time :)</li>
</ul>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="rss" /><summary type="html"><![CDATA[Avoiding social media If you feel uncomfortable with the amount of time you spend in social media apps like Instagram browsing through ads and algorithmic content, you’re not alone. A lot of times I find myself unconsciously scrolling my timeline (or clicking across Stories) and loosing track of time. Here is an alternative to be up-to-date with the content you find relevant, without the distractions that these apps are famous for.]]></summary></entry><entry><title type="html">ActivityPub aka The Fediverse</title><link href="https://caioramos.dev/activity-pub-aka-the-fediverse.html" rel="alternate" type="text/html" title="ActivityPub aka The Fediverse" /><published>2023-07-06T00:00:00+00:00</published><updated>2023-07-06T00:00:00+00:00</updated><id>https://caioramos.dev/activity-pub-aka-the-fediverse</id><content type="html" xml:base="https://caioramos.dev/activity-pub-aka-the-fediverse.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Social media is usually (by the time I write this post) represented by big tech companies that control the content and data generated from the users of the platform. However, recently, we’re seeing a shift towards decentralized alternatives that may change the way we interact with social services.</p>

<p>When choosing a social network, we usually have three main considerations:</p>

<ul>
  <li>Format: networking, microblogging, link-aggregator, media sharing, short videos, etc</li>
  <li>Content: what content is being produced, and who’s interacting with it</li>
  <li>Owner: who dictates the rules and owns the data</li>
</ul>

<p>The common pattern is to usually have only one or two alternatives for each social media format as we know: Facebook, Twitter, Reddit, Instagram, YouTube, TikTok; all owned by huge companies. People tend to go where the content is, but what if content could be shared across media platforms?</p>

<h2 id="meet-the-activitypub-protocol">Meet the ActivityPub protocol</h2>

<p><a href="https://activitypub.rocks/">ActivityPub</a> is an open, decentralized social networking protocol. It provides a client/server API for creating, updating, and deleting content, as well as a federated server-to-server API for delivering notifications and content. It was developed by the W3C Consortium and launched in 2018.</p>

<p><img src="/assets/activity-pub.png" alt="ActivityPub" /></p>

<p>In summary, ActivityPub allows social networks to integrate and share stuff. This represents a decentralized model, where the content is spread across multiple “instances” but can be accessed by anyone in the “federated” network.</p>

<h2 id="implementations">Implementations</h2>

<p>Here are some open-source implementations of the ActivityPub:</p>

<ul>
  <li><a href="https://joinmastodon.org">Mastodon</a>: microblogging</li>
  <li><a href="https://joinpeertube.org">PeerTube</a>: video platform</li>
  <li><a href="https://pixelfed.org">Pixelfed</a>: image and video sharing</li>
  <li><a href="https://join-lemmy.org">Lemmy</a>: news aggregators and discussion forums</li>
</ul>

<p>For each one of these, you can choose different instances to create your account and interact with content from all of the other connected/federated instances. The instance you choose will hold your data and moderate the content generated by it’s users and communities.</p>

<p><img src="/assets/fediverse.png" alt="Fediverse" /></p>

<p>The instance owner can also decide whether or not block communication with other instances. If this happens, content is not accessible from one to the other and vice-versa. This process is called “defederation” and can happen when there’s a lack of trust between different instances, and/or to prevent spam, illicit activities, and so on.</p>

<h2 id="the-future-of-the-fediverse">The future of the Fediverse</h2>

<p>The future of the universe of ActivityPub implementations (also known as Fediverse or Federated universe) is unknown. The reality is that we have a mix of open-source applications, and Meta is coming to the game with <a href="https://www.threads.net">Threads</a>. Here are a few questions that only time can answer:</p>

<ul>
  <li>How the current fediverse (open-source projects) will react to the addition of Threads? Will we see a mass “defederation”?</li>
  <li>What’s Meta’s goal with federation? They’ll have access to a lot of user data for free, but will this be enough? Can we expect an <em>Embrace, extend, and extinguish</em> strategy?</li>
  <li>What’s the next “big addition” to the fediverse? Can we expect Reddit, Twitter, and other media companies to have their own implementations of the protocol?</li>
</ul>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="fediverse" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Raspberry Pi as Plex Media Server (with Cloudflare Zero Trust)</title><link href="https://caioramos.dev/raspberry-pi-as-plex-media-server.html" rel="alternate" type="text/html" title="Raspberry Pi as Plex Media Server (with Cloudflare Zero Trust)" /><published>2023-06-09T13:00:00+00:00</published><updated>2023-06-09T13:00:00+00:00</updated><id>https://caioramos.dev/raspberry-pi-as-plex-media-server</id><content type="html" xml:base="https://caioramos.dev/raspberry-pi-as-plex-media-server.html"><![CDATA[<h2 id="some-terminology">Some terminology</h2>

<p>Before we start, let’s explain some of the terminology that will be used in this text:</p>

<h3 id="raspberry-pi">Raspberry Pi</h3>

<p>Is a series of small single-board ARM-based computers. This experiment used the <a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/">Raspberry Pi 4 Model B</a> with 2GB of RAM.</p>

<h3 id="plex-media-server">Plex Media Server</h3>

<p>Is a <a href="https://www.plex.tv">free software</a> that allows users to create a client–server for movies, television shows, and music. Plex Media Server organizes movie and television content and adds posters, plot summaries, cast and crew lists, technical details, critical reviews, and subtitles. Plex Media Server is also capable of transcoding files if the codec is incompatible with the device playing the media.</p>

<p>A Plex Media Server can function as a home theater PC and can stream content to Plex’s front-end media player client applications that run on a myriad of devices and web browsers.</p>

<h3 id="cloudflare-zero-trust">Cloudflare Zero Trust</h3>

<p>Provides the power of Cloudflare’s global network to your internal teams and infrastructure. Cloudflare Zero Trust empowers users with secure, fast and seamless access to any device on the Internet.</p>

<h3 id="cloudflare-tunnel">Cloudflare Tunnel</h3>

<p>Establishes a secure outbound connection which runs in your infrastructure to connect the applications and machines to Cloudflare. Removes the need to configure IP addresses and ports to access your self-hosted applications.</p>

<h2 id="first-things-first">First things first</h2>

<h3 id="1-create-your-plex-account">1. Create your Plex account</h3>

<p>Go to https://www.plex.tv/sign-up/ and sign up.</p>

<h3 id="2-create-your-cloudflare-account-and-add-your-domain">2. Create your Cloudflare account and add your domain</h3>

<p>You can create your Cloudflare account at https://dash.cloudflare.com/sign-up, and configure your domain with this guide: https://developers.cloudflare.com/fundamentals/get-started/setup/add-site/</p>

<h3 id="3-flash-raspberry-pi-os">3. Flash Raspberry Pi OS</h3>

<p>Install Raspberry Pi Imager from https://www.raspberrypi.com/software/ and flash Raspberry Pi OS into your SD Card.</p>

<h2 id="media-server-up-and-running">Media server up and running</h2>

<p>You can install Plex Server with an official Plex package repository.</p>

<blockquote>
  <p>You may need to install HTTPS support for APT</p>
  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install apt-transport-https
</code></pre></div>  </div>
</blockquote>

<ul>
  <li><a href="https://support.plex.tv/articles/235974187-enable-repository-updating-for-supported-linux-server-distributions/">Enable repository updating for supported Linux server distributions (DEB-based distros).</a></li>
  <li><code class="language-plaintext highlighter-rouge">sudo apt-get install plexmediaserver</code></li>
  <li>Go to <code class="language-plaintext highlighter-rouge">http://localhost:32400/web</code> and finish you configuration</li>
</ul>

<h2 id="all-cloudflare-things">All Cloudflare things</h2>

<p>We want to be able to configure our media server from anywhere we need. This section will go through the configuration proccess to have an SSH shell accessible from anywhere, with proper auth and without oppening ports from our router.</p>

<p>Cloudflare Zero Trust will be used to manage access, and can be initialized by clicking in the sidebar button at your dashboard <img src="/assets/cloudflare.png" alt="cloudflare" /></p>

<h3 id="create-an-application">Create an application</h3>
<p>After adding your payment method, you will be redirected to the Cloudflare One dashboard. The first step is to add an application:</p>

<ul>
  <li>Go to <code class="language-plaintext highlighter-rouge">Access</code> -&gt; <code class="language-plaintext highlighter-rouge">Applications</code> -&gt; <code class="language-plaintext highlighter-rouge">Add an application</code></li>
  <li>Select <code class="language-plaintext highlighter-rouge">Self-hosted</code></li>
  <li>Name your application and set the domain to something like <code class="language-plaintext highlighter-rouge">myssh.yourdomain.com</code></li>
  <li>Setup your access policy, more information at https://developers.cloudflare.com/cloudflare-one/policies/access/</li>
  <li>In <code class="language-plaintext highlighter-rouge">Settings</code>, select SSH as the <code class="language-plaintext highlighter-rouge">Browser rendering</code> option</li>
</ul>

<p><img src="/assets/additional-settings.png" alt="settings" /></p>

<h3 id="create-a-tunnel">Create a tunnel</h3>
<p>The next step is to create a tunnel, so your application can be redirected to your Raspberry Pi network:</p>

<ul>
  <li>Go to <code class="language-plaintext highlighter-rouge">Access</code> -&gt; <code class="language-plaintext highlighter-rouge">Tunnels</code> -&gt; <code class="language-plaintext highlighter-rouge">Create a tunnel</code></li>
  <li>Name your tunnel and follow the instruction to install and run the connector</li>
  <li>Add a public host name that matches the one you added to your Application, like <code class="language-plaintext highlighter-rouge">myssh.yourdomain.com</code></li>
  <li>Select SSH as the service type</li>
  <li>Protect your public hostname with <code class="language-plaintext highlighter-rouge">Access</code> (select the application you just created)</li>
</ul>

<p><img src="/assets/tunnel-access.png" alt="tunnel" /></p>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="raspberry" /><summary type="html"><![CDATA[Some terminology]]></summary></entry><entry><title type="html">Publishing to IPFS using GitHub Actions</title><link href="https://caioramos.dev/publishing-to-ipfs-using-gh-actions.html" rel="alternate" type="text/html" title="Publishing to IPFS using GitHub Actions" /><published>2021-08-30T22:04:35+00:00</published><updated>2021-08-30T22:04:35+00:00</updated><id>https://caioramos.dev/publishing-to-ipfs-using-gh-actions</id><content type="html" xml:base="https://caioramos.dev/publishing-to-ipfs-using-gh-actions.html"><![CDATA[<h2 id="ipfs---interplanetary-file-system">IPFS - Interplanetary File System</h2>
<h3 id="edit-pinata-has-restricted-http-uploads-so-this-guide-is-deprecated-">Edit: Pinata has restricted HTTP uploads, so this guide is deprecated :(</h3>

<blockquote>
  <p>A peer-to-peer hypermedia protocol designed to preserve and grow humanity’s knowledge by making the web upgradeable, resilient, and more open.</p>
</blockquote>

<p>or</p>

<blockquote>
  <p>IPFS is a distributed system for storing and accessing files, websites, applications, and data</p>
</blockquote>

<p><a href="https://ipfs.io/">IPFS</a> allow users to host files in a peer-to-peer network with unique identification and content linking. It takes several good ideas from projects like BitTorrent, Git, Bitcoin, the web, and gathers them in a single tool. To learn more about IPFS visit the <a href="https://docs.ipfs.io/concepts/">docs</a>.</p>

<p>To understand this guide, we need to explain some concepts:</p>

<ul>
  <li>A <strong>Node</strong> or peer is the IPFS program that you run on your local computer to store/cache files and then connect to the IPFS network;</li>
  <li><strong>Pinning</strong> is the method of telling an IPFS node that particular data is important and so it will never be removed from that node’s cache;</li>
  <li>A <strong>Content Identifier (CID)</strong> is a self-describing content-addressed label used to point to the data stored in IPFS;</li>
  <li>An <strong>IPFS Gateway</strong> acts as a bridge between traditional web browsers and IPFS. Through the gateway, users can browse files and websites stored in IPFS as if they were stored on a traditional web server;</li>
  <li><strong>DNSLink</strong> uses DNS TXT records to map a DNS name, like ipfs.io, to an IPFS address. Because you can edit your DNS records, you can use them to always point to the latest version of an object in IPFS. Since DNSLink uses DNS records, you can assign names, paths, and sub-domains that are easy to type, read, and remember.</li>
</ul>

<h3 id="example">Example</h3>

<p>If you want to access a Wikipedia article, the URL usually looks like this</p>

<p><a href="https://en.wikipedia.org/wiki/InterPlanetary_File_System">https://en.wikipedia.org/wiki/InterPlanetary_File_System</a></p>

<p>When using IPFS, we don’t specify where the file is (location based), we ask for a specific file hash (content based) like</p>

<p><code class="language-plaintext highlighter-rouge">/ipns/k51qzi5u.../wiki/InterPlanetary_File_System</code></p>

<h2 id="hosting-websites-on-ipfs">Hosting websites on IPFS</h2>

<p>The main idea here is to host a full HTML/CSS/JS website using: a service for pinning files on the IPFS network (so we don’t need to run our own node), an IPFS Gateway (so users can access our website from their browsers) and a DNSLink entry (so a friendly URL will be possible). The chosen tools for this process are:</p>

<ul>
  <li><a href="https://www.pinata.cloud/">Pinata</a></li>
  <li><a href="https://www.cloudflare.com/pt-br/distributed-web-gateway/">Cloudflare distributed gateway</a></li>
  <li><a href="https://www.cloudflare.com/pt-br/dns/">Cloudflare DNS</a></li>
</ul>

<p>First, you will need to upload your website files to Pinata, usually the <code class="language-plaintext highlighter-rouge">build/</code> directory. After that, you will be able to view your website online. Something like <code class="language-plaintext highlighter-rouge">https://gateway.pinata.cloud/ipfs/QmWAJbBDon9Q...</code>.</p>

<p>After that, login to Cloudflare and setup the following entries to your DNS:</p>

<table>
  <tbody>
    <tr>
      <td>Type</td>
      <td>Name</td>
      <td>Content</td>
    </tr>
    <tr>
      <td>CNAME</td>
      <td>@</td>
      <td>cloudflare-ipfs.com</td>
    </tr>
    <tr>
      <td>TXT</td>
      <td>_dnslink</td>
      <td>dnslink=/ipfs/QmWAJbBDon9Q…</td>
    </tr>
  </tbody>
</table>

<p>Now you should be able to access your website using your own domain :)</p>

<h2 id="integrate-with-github-actions">Integrate with GitHub Actions</h2>

<p>Basically, two steps will be needed:</p>

<ol>
  <li>Upload and pin files to IPFS</li>
  <li>Update your DNSLink record</li>
</ol>

<p>For the first, we can use <a href="https://github.com/marketplace/actions/upload-to-ipfs">upload-to-ipfs</a> action. It is pretty straight forward and integrates well with Pinata.</p>

<p>For the second one, if your DNS is on Cloudflare, we can use its API, specifically the <a href="https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record">dns-records-for-a-zone-update-dns-record</a> method/endpoint.</p>

<p>Your YML file should look something like this</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload to IPFS</span>
  <span class="na">uses</span><span class="pi">:</span> <span class="s">aquiladev/ipfs-action@cb917...</span>
  <span class="na">id</span><span class="pi">:</span> <span class="s">upload-ipfs</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">./build</span>
    <span class="na">service</span><span class="pi">:</span> <span class="s">pinata</span>
    <span class="na">pinataKey</span><span class="pi">:</span> <span class="s">$secrets.PINATA_KEY</span>
    <span class="na">pinataSecret</span><span class="pi">:</span> <span class="s">$secrets.PINATA_SECRET</span>
    <span class="na">pinataPinName</span><span class="pi">:</span> <span class="s">Name</span>

<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update DNSLink on Cloudflare</span>
  <span class="na">env</span><span class="pi">:</span>
    <span class="na">CLOUDFLARE_ZONE_ID</span><span class="pi">:</span> <span class="s">$secrets.CLOUDFLARE_ZONE_ID</span>
    <span class="na">CLOUDFLARE_DNS_ID</span><span class="pi">:</span> <span class="s">$secrets.CLOUDFLARE_DNS_ID</span>
    <span class="na">CLOUDFLARE_API_KEY</span><span class="pi">:</span> <span class="s">$secrets.CLOUDFLARE_API_KEY</span>
    <span class="na">CLOUDFLARE_EMAIL</span><span class="pi">:</span> <span class="s">$secrets.CLOUDFLARE_EMAIL</span>
    <span class="na">IPFS_HASH</span><span class="pi">:</span> <span class="s">$steps.upload-ipfs.outputs.hash</span>
  <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
    <span class="s">curl --location --request PUT 'https://api.cloudflare.com/client/v4/zones/'"$CLOUDFLARE_ZONE_ID"'/dns_records/'"$CLOUDFLARE_DNS_ID"'' \</span>
    <span class="s">--header 'Content-Type: application/json' \</span>
    <span class="s">--header 'X-Auth-Key: '"$CLOUDFLARE_API_KEY"'' \</span>
    <span class="s">--header 'X-Auth-Email: '"$CLOUDFLARE_EMAIL"'' \</span>
    <span class="s">--data-raw '{</span>
        <span class="s">"type": "TXT",</span>
        <span class="s">"name": "_dnslink",</span>
        <span class="s">"content": "dnslink=/ipfs/'"$IPFS_HASH"'",</span>
        <span class="s">"ttl": 1</span>
    <span class="s">}'</span>
</code></pre></div></div>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="ipfs" /><summary type="html"><![CDATA[IPFS - Interplanetary File System Edit: Pinata has restricted HTTP uploads, so this guide is deprecated :(]]></summary></entry><entry><title type="html">Metaprogramming</title><link href="https://caioramos.dev/metaprogramming.html" rel="alternate" type="text/html" title="Metaprogramming" /><published>2021-02-09T22:04:35+00:00</published><updated>2021-02-09T22:04:35+00:00</updated><id>https://caioramos.dev/metaprogramming</id><content type="html" xml:base="https://caioramos.dev/metaprogramming.html"><![CDATA[<h2 id="definition">Definition</h2>

<blockquote>
  <p>Programs that generate programs</p>
</blockquote>

<p>Metaprogramming <em>may</em> allow developers to be more productive by reducing the amount of code that needs to be written manually. It is worth to notice that the way it is built affects (a lot) the productivity.</p>

<p>The two main use cases are:</p>

<ul>
  <li>Exposing internal execution mechanisms to code as APIs;</li>
  <li>Dynamic text expression execution that contains programming commands.</li>
</ul>

<h2 id="examples">Examples</h2>

<h3 id="base-code-generation">Base code generation</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#! /bin/sh</span>
<span class="nb">echo</span> <span class="s1">'#! /bin/sh'</span> <span class="o">&gt;</span> program
<span class="k">for </span>i <span class="k">in</span> <span class="si">$(</span><span class="nb">seq </span>992<span class="si">)</span>
<span class="k">do
  </span><span class="nb">echo</span> <span class="s2">"echo </span><span class="nv">$i</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> program
<span class="k">done
</span><span class="nb">chmod</span> +x program
</code></pre></div></div>

<h3 id="cc-macros">C/C++ macros</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define RADTODEG(x) ((x) * 57.29578)
</span></code></pre></div></div>

<h3 id="python-evalexec">Python eval/exec</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="nb">eval</span><span class="p">(</span><span class="s">"5 ** 7"</span><span class="p">)</span>
<span class="c1"># 78125
</span>
<span class="o">&gt;&gt;&gt;</span> <span class="n">program</span> <span class="o">=</span> <span class="s">'a = 5</span><span class="se">\n</span><span class="s">b = 10</span><span class="se">\n</span><span class="s">print("Sum = ", a+b)'</span>
<span class="o">&gt;&gt;&gt;</span> <span class="k">exec</span><span class="p">(</span><span class="n">program</span><span class="p">)</span>
<span class="c1"># Sum =  15
</span></code></pre></div></div>

<h2 id="in-ruby">In ruby</h2>

<p>The Ruby language offers by default multiple ways to “metaprogram”. Here are some real world examples:</p>

<h3 id="identifying-not-implemented-methods">Identifying not implemented methods</h3>

<p>Useful for generating infinity methods to classes</p>

<p>APIs</p>

<ul>
  <li><a href="https://apidock.com/ruby/BasicObject/method_missing">BasicObject/method_missing</a></li>
  <li><a href="https://apidock.com/ruby/Object/respond_to_missing%3F">Object/respond_to_missing?</a></li>
</ul>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Roman</span>
  <span class="no">ROMAN_TO_INT</span> <span class="o">=</span> <span class="p">{</span>
    <span class="ss">i: </span><span class="mi">1</span><span class="p">,</span>
    <span class="ss">v: </span><span class="mi">5</span><span class="p">,</span>
    <span class="ss">x: </span><span class="mi">10</span><span class="p">,</span>
    <span class="ss">l: </span><span class="mi">50</span><span class="p">,</span>
    <span class="ss">c: </span><span class="mi">100</span><span class="p">,</span>
    <span class="ss">d: </span><span class="mi">500</span><span class="p">,</span>
    <span class="ss">m: </span><span class="mi">1000</span>
  <span class="p">}.</span><span class="nf">freeze</span>

  <span class="k">def</span> <span class="nf">roman_to_int</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
    <span class="c1"># This implementation does not matter :p</span>
    <span class="n">numbers</span> <span class="o">=</span> <span class="n">str</span><span class="p">.</span><span class="nf">downcase</span><span class="p">.</span><span class="nf">chars</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="no">ROMAN_TO_INT</span><span class="p">[</span><span class="n">char</span><span class="p">.</span><span class="nf">to_sym</span><span class="p">]</span> <span class="p">}.</span><span class="nf">reverse</span>
    <span class="n">numbers</span><span class="p">.</span><span class="nf">inject</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">])</span> <span class="k">do</span> <span class="o">|</span><span class="n">result_number</span><span class="p">,</span> <span class="n">int</span><span class="o">|</span>
      <span class="n">result</span><span class="p">,</span> <span class="n">number</span> <span class="o">=</span> <span class="n">result_number</span>
      <span class="n">int</span> <span class="o">&gt;=</span> <span class="n">number</span> <span class="p">?</span> <span class="p">[</span><span class="n">result</span> <span class="o">+</span> <span class="n">int</span><span class="p">,</span> <span class="n">int</span><span class="p">]</span> <span class="p">:</span> <span class="p">[</span><span class="n">result</span> <span class="o">-</span> <span class="n">int</span><span class="p">,</span> <span class="n">number</span><span class="p">]</span>
    <span class="k">end</span><span class="p">.</span><span class="nf">first</span>
  <span class="k">end</span>

  <span class="kp">private</span>

  <span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="n">method_name</span><span class="p">)</span>
    <span class="n">str</span> <span class="o">=</span> <span class="n">method_name</span><span class="p">.</span><span class="nf">to_s</span>
    <span class="n">roman_to_int</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">roman</span> <span class="o">=</span> <span class="no">Roman</span><span class="p">.</span><span class="nf">new</span>
<span class="n">roman</span><span class="p">.</span><span class="nf">iii</span> <span class="c1"># =&gt; 3</span>
<span class="n">roman</span><span class="p">.</span><span class="nf">cix</span> <span class="c1"># =&gt; 109</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># activesupport/lib/active_support/string_inquirer.rb</span>
<span class="c1"># frozen_string_literal: true</span>

<span class="nb">require</span> <span class="s2">"active_support/core_ext/symbol/starts_ends_with"</span>

<span class="k">module</span> <span class="nn">ActiveSupport</span>
  <span class="c1"># Wrapping a string in this class gives you a prettier way to test</span>
  <span class="c1"># for equality. The value returned by &lt;tt&gt;Rails.env&lt;/tt&gt; is wrapped</span>
  <span class="c1"># in a StringInquirer object, so instead of calling this:</span>
  <span class="c1">#</span>
  <span class="c1">#   Rails.env == 'production'</span>
  <span class="c1">#</span>
  <span class="c1"># you can call this:</span>
  <span class="c1">#</span>
  <span class="c1">#   Rails.env.production?</span>
  <span class="c1">#</span>
  <span class="c1"># == Instantiating a new StringInquirer</span>
  <span class="c1">#</span>
  <span class="c1">#   vehicle = ActiveSupport::StringInquirer.new('car')</span>
  <span class="c1">#   vehicle.car?   # =&gt; true</span>
  <span class="c1">#   vehicle.bike?  # =&gt; false</span>
  <span class="k">class</span> <span class="nc">StringInquirer</span> <span class="o">&lt;</span> <span class="no">String</span>
    <span class="kp">private</span>
      <span class="k">def</span> <span class="nf">respond_to_missing?</span><span class="p">(</span><span class="n">method_name</span><span class="p">,</span> <span class="n">include_private</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
        <span class="n">method_name</span><span class="p">.</span><span class="nf">end_with?</span><span class="p">(</span><span class="s2">"?"</span><span class="p">)</span> <span class="o">||</span> <span class="k">super</span>
      <span class="k">end</span>

      <span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="n">method_name</span><span class="p">,</span> <span class="o">*</span><span class="n">arguments</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">method_name</span><span class="p">.</span><span class="nf">end_with?</span><span class="p">(</span><span class="s2">"?"</span><span class="p">)</span>
          <span class="nb">self</span> <span class="o">==</span> <span class="n">method_name</span><span class="p">[</span><span class="mi">0</span><span class="o">..-</span><span class="mi">2</span><span class="p">]</span>
        <span class="k">else</span>
          <span class="k">super</span>
        <span class="k">end</span>
      <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h3 id="dynamically-creating-methods">Dynamically creating methods</h3>

<p>Useful mainly when we need to generate methods from a given list</p>

<p>APIs</p>

<ul>
  <li><a href="https://apidock.com/ruby/Module/define_method">Module/define_method</a></li>
</ul>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># activerecord/lib/active_record/store.rb#L135</span>
<span class="c1"># ...</span>
<span class="n">keys</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="o">|</span>
  <span class="n">accessor_key</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">accessor_prefix</span><span class="si">}#{</span><span class="n">key</span><span class="si">}#{</span><span class="n">accessor_suffix</span><span class="si">}</span><span class="s2">"</span>

  <span class="n">define_method</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">accessor_key</span><span class="si">}</span><span class="s2">="</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span>
    <span class="n">write_store_attribute</span><span class="p">(</span><span class="n">store_attribute</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="n">define_method</span><span class="p">(</span><span class="n">accessor_key</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">read_store_attribute</span><span class="p">(</span><span class="n">store_attribute</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="n">define_method</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">accessor_key</span><span class="si">}</span><span class="s2">_changed?"</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">return</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">attribute_changed?</span><span class="p">(</span><span class="n">store_attribute</span><span class="p">)</span>
    <span class="n">prev_store</span><span class="p">,</span> <span class="n">new_store</span> <span class="o">=</span> <span class="n">changes</span><span class="p">[</span><span class="n">store_attribute</span><span class="p">]</span>
    <span class="n">prev_store</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="n">key</span><span class="p">)</span> <span class="o">!=</span> <span class="n">new_store</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
  <span class="k">end</span>

  <span class="n">define_method</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">accessor_key</span><span class="si">}</span><span class="s2">_change"</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">return</span> <span class="k">unless</span> <span class="n">attribute_changed?</span><span class="p">(</span><span class="n">store_attribute</span><span class="p">)</span>
    <span class="n">prev_store</span><span class="p">,</span> <span class="n">new_store</span> <span class="o">=</span> <span class="n">changes</span><span class="p">[</span><span class="n">store_attribute</span><span class="p">]</span>
    <span class="p">[</span><span class="n">prev_store</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="n">key</span><span class="p">),</span> <span class="n">new_store</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="n">key</span><span class="p">)]</span>
  <span class="k">end</span>

  <span class="n">define_method</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">accessor_key</span><span class="si">}</span><span class="s2">_was"</span><span class="p">)</span> <span class="k">do</span>
    <span class="k">return</span> <span class="k">unless</span> <span class="n">attribute_changed?</span><span class="p">(</span><span class="n">store_attribute</span><span class="p">)</span>
    <span class="n">prev_store</span><span class="p">,</span> <span class="n">_new_store</span> <span class="o">=</span> <span class="n">changes</span><span class="p">[</span><span class="n">store_attribute</span><span class="p">]</span>
    <span class="n">prev_store</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
  <span class="k">end</span>
<span class="c1"># ...</span>
</code></pre></div></div>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Loggable</span>
  <span class="k">def</span> <span class="nf">logger</span>
    <span class="vi">@logger</span> <span class="o">||=</span> <span class="n">default_logger</span>
  <span class="k">end</span>

  <span class="kp">private</span>

  <span class="k">def</span> <span class="nf">default_logger</span>
    <span class="no">Rails</span><span class="p">.</span><span class="nf">logger</span>
  <span class="k">end</span>

  <span class="sx">%i[info warn debug error]</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">severity</span><span class="o">|</span>
    <span class="n">define_method</span> <span class="n">severity</span> <span class="k">do</span> <span class="o">|</span><span class="n">message</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="o">|</span>
      <span class="n">data</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="nf">first</span> <span class="o">||</span> <span class="p">{}</span>
      <span class="n">data</span><span class="p">[</span><span class="ss">:class_name</span><span class="p">]</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">name</span> <span class="k">unless</span> <span class="n">data</span><span class="p">.</span><span class="nf">key?</span><span class="p">(</span><span class="ss">:class_name</span><span class="p">)</span>
      <span class="n">logger</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">severity</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h3 id="dynamic-method-calls">Dynamic method calls</h3>

<p>Usually for composing method calls with external input</p>

<p>APIs</p>

<ul>
  <li><a href="https://apidock.com/ruby/Object/send">Object/send</a></li>
  <li><a href="https://apidock.com/ruby/Object/public_send">Object/public</a></li>
</ul>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Book</span>
  <span class="nb">attr_accessor</span> <span class="ss">:title</span><span class="p">,</span> <span class="ss">:author</span><span class="p">,</span> <span class="ss">:length</span>

  <span class="k">def</span> <span class="nf">assign_values</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
    <span class="n">values</span><span class="p">.</span><span class="nf">each_key</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="o">|</span>
      <span class="nb">send</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">key</span><span class="si">}</span><span class="s2">="</span><span class="p">,</span> <span class="n">values</span><span class="p">[</span><span class="n">key</span><span class="p">])</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="n">book_info</span> <span class="o">=</span> <span class="p">{</span>
  <span class="ss">title: </span><span class="s1">'Forrest Gump'</span><span class="p">,</span>
  <span class="ss">author: </span><span class="s1">'Winston Groom'</span><span class="p">,</span>
  <span class="ss">length: </span><span class="mi">300</span>
<span class="p">}</span>

<span class="n">book</span> <span class="o">=</span> <span class="no">Book</span><span class="p">.</span><span class="nf">new</span>

<span class="c1"># vvvvv Not necessary vvvvv</span>
<span class="c1"># book.title = book_info[:title]</span>
<span class="c1"># book.author = book_info[:author]</span>
<span class="c1"># book.length = book_info[:length]</span>

<span class="n">book</span><span class="p">.</span><span class="nf">assign_values</span><span class="p">(</span><span class="n">book_info</span><span class="p">)</span>
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>Metaprogramming can become pretty natural and easy to use, that’s when the danger comes, with ultra generic classes and poor understandable code. Take a look at Martin Fowler’s article <a href="https://www.martinfowler.com/articles/rubyAtThoughtWorks.html">Is a Ruby Code-base Hard to Understand?</a></p>]]></content><author><name>Caio Ramos</name></author><category term="dev" /><category term="ruby" /><summary type="html"><![CDATA[Definition]]></summary></entry></feed>