<?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: Viktor Araujo</title>
    <description>The latest articles on DEV Community by Viktor Araujo (@viktor_araujo_f19ca3ee4cb).</description>
    <link>https://dev.to/viktor_araujo_f19ca3ee4cb</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%2F3073371%2F2b24626f-73db-4865-b7aa-d0759092dd84.png</url>
      <title>DEV Community: Viktor Araujo</title>
      <link>https://dev.to/viktor_araujo_f19ca3ee4cb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viktor_araujo_f19ca3ee4cb"/>
    <language>en</language>
    <item>
      <title>What I Learned Building My First Ruby Gem (Peeking Behind the Rails Curtain)</title>
      <dc:creator>Viktor Araujo</dc:creator>
      <pubDate>Tue, 22 Apr 2025 17:11:38 +0000</pubDate>
      <link>https://dev.to/viktor_araujo_f19ca3ee4cb/what-i-learned-building-my-first-ruby-gem-peeking-behind-the-rails-curtain-58cc</link>
      <guid>https://dev.to/viktor_araujo_f19ca3ee4cb/what-i-learned-building-my-first-ruby-gem-peeking-behind-the-rails-curtain-58cc</guid>
      <description>&lt;p&gt;When you’re working on Rails apps every day, gems feel like magic. You install them, maybe tweak a config file, and boom — they work. But when I built my first Ruby gem, &lt;a href="https://github.com/vkaraujo/audit_log" rel="noopener noreferrer"&gt;audit_log_vk&lt;/a&gt; (In case you are wondering, I'm Vk, that's why that name), I realized just how much was happening behind the curtain. As a developer, it was eye-opening to see what really goes into making a gem that feels seamless to the end user.&lt;/p&gt;

&lt;p&gt;Here are some of the most surprising things I discovered — and why building a gem, even a small one, is totally worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A Gem Is Just a Folder — But with Specific Files That Unlock Everything
&lt;/h2&gt;

&lt;p&gt;The first surprise was that a gem is really just a Ruby project with the right folder structure. But the magic happens because of a few specific files:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;audit_log_vk.gemspec&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This file defines the gem itself — name, summary, author, license, and (importantly) what files to include when it’s packaged. It’s like a &lt;code&gt;package.json&lt;/code&gt; in the Node.js world, but with more manual responsibility.&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;# Set the gem version by referencing the constant inside lib/audit_log_vk/version.rb&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;AuditLog&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;

&lt;span class="c1"&gt;# Define which files to include in the gem package&lt;/span&gt;
&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__dir__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Use Git to list all version-controlled files, separated by null characters&lt;/span&gt;
  &lt;span class="sb"&gt;`git ls-files -z`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reject&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;f&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Exclude this gemspec file itself and unwanted paths (tests, CI configs, etc.)&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;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_with?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="sx"&gt;%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile]&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;🔍 &lt;strong&gt;What surprised me:&lt;/strong&gt; I had to explicitly list what goes into the gem — otherwise files I thought were “just there” wouldn’t be included at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;lib/audit_log_vk.rb&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the gem’s main entry point. It defines the namespace and loads everything else:&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_relative&lt;/span&gt; &lt;span class="s2"&gt;"audit_log/version"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"audit_log/config"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"audit_log/context"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"audit_log/entry"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"audit_log/model"&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"audit_log/helpers"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s the place where you glue everything together. Until I wrote it myself, I never thought about how a gem decides what to load.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;lib/audit_log_vk/version.rb&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;And here is where you will manage the gems version, and versioning is simple but important. I quickly realized I was now responsible for semantic versioning — which is something we don't have to think about that often in the day to day.&lt;/p&gt;

&lt;p&gt;Here is a cheatsheet to guide you:&lt;/p&gt;

&lt;h3&gt;
  
  
  📌 Semantic Versioning Cheatsheet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;MAJOR.MINOR.PATCH

| Type of Change    | Version Example | When to Use It                                                  |
|-------------------|------------------|-----------------------------------------------------------------|
| 🔥 &lt;span class="k"&gt;**&lt;/span&gt;MAJOR&lt;span class="k"&gt;**&lt;/span&gt;      | &lt;span class="sb"&gt;`&lt;/span&gt;2.0.0&lt;span class="sb"&gt;`&lt;/span&gt;         | Breaking changes — not backward compatible                      |
| ✨ &lt;span class="k"&gt;**&lt;/span&gt;MINOR&lt;span class="k"&gt;**&lt;/span&gt;      | &lt;span class="sb"&gt;`&lt;/span&gt;1.1.0&lt;span class="sb"&gt;`&lt;/span&gt;         | New features or enhancements — backward compatible              |
| 🐛 &lt;span class="k"&gt;**&lt;/span&gt;PATCH&lt;span class="k"&gt;**&lt;/span&gt;      | &lt;span class="sb"&gt;`&lt;/span&gt;1.0.1&lt;span class="sb"&gt;`&lt;/span&gt;         | Bug fixes or small improvements — no new features or breaks     |

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. bin/console Gave Me a Playground
&lt;/h2&gt;

&lt;p&gt;This was a hidden gem (pun intended). bin/console lets you load your gem in a REPL before it’s published or used in an app. It's a IRB session with your gem preloaded, which lets you&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load and test your gem’s classes and methods.&lt;/li&gt;
&lt;li&gt;Call stuff manually to see if it works.&lt;/li&gt;
&lt;li&gt;Play with configs, helpers, etc.&lt;/li&gt;
&lt;li&gt;Avoid having to spin up a full Rails app for quick tests.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# bin/console&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/setup"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"audit_log_vk"&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Console loaded. Try AuditLogVk::Something.new"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You run it with:&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;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;console&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧠 &lt;strong&gt;What blew my mind:&lt;/strong&gt; You don’t need a full Rails app to test your gem. You can load it, call its methods, and experiment — right from a console.&lt;/p&gt;

&lt;p&gt;This helped me iterate faster without spinning up a full environment. It’s great for debugging, experimenting, or just figuring out what your gem actually exposes.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Generators Can Inject Files Into the User’s App
&lt;/h2&gt;

&lt;p&gt;One of the coolest parts of building &lt;code&gt;audit_log_vk&lt;/code&gt; was creating a Rails generator to install setup files into the user’s app — just like &lt;code&gt;rails generate devise:install&lt;/code&gt; or &lt;code&gt;sidekiq:install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We added a custom generator at:&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;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;generators&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;audit_log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;install_generator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside, we used two key 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;def&lt;/span&gt; &lt;span class="nf"&gt;copy_initializer&lt;/span&gt;
  &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="s2"&gt;"initializer.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"config/initializers/audit_log.rb"&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;copy_migration&lt;/span&gt;
  &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"%Y%m%d%H%M%S"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;copy_file&lt;/span&gt; &lt;span class="s2"&gt;"migration.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"db/migrate/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_create_audit_log_entries.rb"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup means that when someone runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate audit_log:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…they automatically get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A ready-to-edit initializer: &lt;code&gt;config/initializers/audit_log.rb&lt;/code&gt; (based on a template we created)&lt;/li&gt;
&lt;li&gt;A migration to create the audit log entries table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ties into the internal config system defined in lib/audit_log/config.rb, which supports:&lt;br&gt;
    &lt;code&gt;AuditLog.configure { ... }&lt;/code&gt;&lt;br&gt;
    &lt;code&gt;AuditLog.configuration.actor_method&lt;/code&gt;&lt;br&gt;
    &lt;code&gt;AuditLog.reset_configuration!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧩 &lt;strong&gt;Why it’s powerful:&lt;/strong&gt; You’re not just writing Ruby logic — you’re building a full developer experience. You control what gets scaffolded, how defaults are set, and how easy it is for someone to get started with your gem. &lt;/p&gt;

&lt;p&gt;If you’ve ever run &lt;code&gt;rails generate devise:install&lt;/code&gt;, &lt;code&gt;sidekiq:install&lt;/code&gt;, or even &lt;code&gt;pundit:install&lt;/code&gt;, you’ve used generators built exactly like this. They copy templates, set up initializers, and sometimes even inject routes or modify existing files.&lt;/p&gt;

&lt;p&gt;It’s one of the best ways to make your gem feel polished and user-friendly.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Releasing a Gem Is a Real Publishing Process
&lt;/h2&gt;

&lt;p&gt;Once the gem is ready, here’s how I pushed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git tag &lt;span class="nt"&gt;-a&lt;/span&gt; v0.1.0 &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial release"&lt;/span&gt;
git push origin v0.1.0
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds the gem&lt;/li&gt;
&lt;li&gt;Pushes it to &lt;a href="https://rubygems.org/gems/audit_log_vk" rel="noopener noreferrer"&gt;RubyGems.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pushes the tag to GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚨 &lt;strong&gt;What I didn’t expect:&lt;/strong&gt; Publishing is final. If you make a mistake, you have to bump the version again. That pressure forces you to double-check everything.&lt;/p&gt;

&lt;p&gt;RubyGems enforces &lt;strong&gt;immutable versioning&lt;/strong&gt; for security and consistency:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❗ &lt;strong&gt;Once a gem version is published, it’s permanent and locked.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Even if you yank it (unlist), you still &lt;strong&gt;can’t re-use that version number&lt;/strong&gt; again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if you push &lt;code&gt;1.0.0&lt;/code&gt;, realize something's broken, and try to fix it — you must bump the version (e.g., to &lt;code&gt;1.0.1&lt;/code&gt; or &lt;code&gt;1.1.0&lt;/code&gt;) before re-releasing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts: You Don’t Have to Be an Expert to Make a Gem
&lt;/h2&gt;

&lt;p&gt;I’m not a senior dev. I didn’t build a complex DSL or a massive ORM layer. But building a gem — even a small one — showed me a side of Ruby and Rails I’d never seen before:&lt;/p&gt;

&lt;p&gt;The structure behind libraries&lt;br&gt;
The responsibility of packaging and versioning&lt;br&gt;
The tools that make other developers’ lives easier&lt;/p&gt;

&lt;p&gt;I know there’s still a lot I haven’t explored — things like &lt;code&gt;Railtie&lt;/code&gt;, &lt;code&gt;Engine&lt;/code&gt;, advanced integration with Rails lifecycle hooks, or deeper metaprogramming patterns. This gem didn’t require them, but I definitely plan to experiment with those concepts in future projects.&lt;/p&gt;

&lt;p&gt;It was one of the best things I’ve done to grow as a developer. If you’ve only ever consumed gems, try building one — even something small. You’ll learn more than you expect.&lt;/p&gt;




&lt;p&gt;Got questions about gem structure, publishing, or testing? Leave a comment or reach out — I’m always happy to share what I learned (and what confused me).&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading &amp;amp; Resources
&lt;/h2&gt;

&lt;p&gt;Official RubyGems guide to creating and publishing a gem from scratch:&lt;br&gt;
&lt;a href="https://guides.rubygems.org/make-your-own-gem/" rel="noopener noreferrer"&gt;📦 RubyGems Guides – Make Your Own Gem&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Explains how &lt;code&gt;bundle gem&lt;/code&gt; scaffolds a new gem and what each file is for:&lt;br&gt;
&lt;a href="https://bundler.io/guides/creating_gem.html" rel="noopener noreferrer"&gt;📚 Bundler – Creating a Gem&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Everything you need to know about creating generators that copy templates, run commands, or modify apps:&lt;br&gt;
&lt;a href="https://guides.rubyonrails.org/generators.html" rel="noopener noreferrer"&gt;📖 Rails Generators &amp;amp; Templates&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Learn how to version your gem responsibly with the standard &lt;code&gt;MAJOR.MINOR.PATCH&lt;/code&gt; format:&lt;br&gt;
&lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;💎 Semantic Versioning (semver.org)&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;A detailed list of all available fields in a &lt;code&gt;.gemspec&lt;/code&gt; file:&lt;br&gt;
&lt;a href="https://guides.rubygems.org/specification-reference/" rel="noopener noreferrer"&gt;📘 RubyGems Specification Reference&lt;/a&gt;  &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
