<?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: hatsu</title>
    <description>The latest articles on DEV Community by hatsu (@hatsu38).</description>
    <link>https://dev.to/hatsu38</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%2F524628%2F20ec5ca3-aca9-4aab-8f40-e0ba8466346d.png</url>
      <title>DEV Community: hatsu</title>
      <link>https://dev.to/hatsu38</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hatsu38"/>
    <language>en</language>
    <item>
      <title>Introducing the `script` Folder in Rails 8 and a New Gem for Browser-Based Data Migration</title>
      <dc:creator>hatsu</dc:creator>
      <pubDate>Fri, 20 Dec 2024 16:47:01 +0000</pubDate>
      <link>https://dev.to/hatsu38/introducing-the-script-folder-in-rails-8-and-a-new-gem-for-browser-based-data-migration-2fdj</link>
      <guid>https://dev.to/hatsu38/introducing-the-script-folder-in-rails-8-and-a-new-gem-for-browser-based-data-migration-2fdj</guid>
      <description>&lt;p&gt;I usually write in Japanese, so please pardon any awkward phrasing in my English.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;None of us want to log into a production environment via SSH and manually fiddle with data using &lt;code&gt;rails c&lt;/code&gt;, right? Surely no one is doing that… or at least, let’s hope not.&lt;/p&gt;

&lt;p&gt;That said, in some workplaces that aren’t fully set up yet, it might still happen occasionally. I’ve found myself doing it now and then, and I started wondering if there was a better way.&lt;/p&gt;

&lt;p&gt;With that in mind, I experimented with a potential solution by creating a new Gem. (Full disclosure: I’m quite new to making Gems.)&lt;/p&gt;

&lt;p&gt;This Gem also ties into the &lt;code&gt;script&lt;/code&gt; folder, a feature introduced in Rails 8.&lt;/p&gt;

&lt;p&gt;In this article, I’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The challenges of data migration&lt;/li&gt;
&lt;li&gt;An introduction to the relatively unknown &lt;code&gt;script&lt;/code&gt; folder in Rails 8&lt;/li&gt;
&lt;li&gt;The new Gem I’ve created to tackle these issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But first, let’s jump right into what I made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gem I Created: Scriptor
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;What can it do?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse your &lt;code&gt;script&lt;/code&gt; folder’s code right in the browser&lt;/li&gt;
&lt;li&gt;Execute scripts from the &lt;code&gt;script&lt;/code&gt; folder via the browser&lt;/li&gt;
&lt;li&gt;View execution history of those scripts through the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the repository here:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/hatsu38/scriptor" rel="noopener noreferrer"&gt;https://github.com/hatsu38/scriptor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before I dive into the background of why I made it, let’s talk about how data migration is often handled.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Do You Handle Data Migration?
&lt;/h2&gt;

&lt;p&gt;Data migration strategies often come up in discussions. Common approaches include using &lt;a href="https://github.com/Shopify/maintenance_tasks" rel="noopener noreferrer"&gt;maintenance_tasks gem&lt;/a&gt;, &lt;a href="https://github.com/ilyakatz/data-migrate" rel="noopener noreferrer"&gt;data_migrate gem&lt;/a&gt;, or simply running &lt;code&gt;rake tasks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I personally rely on the &lt;a href="https://github.com/Shopify/maintenance_tasks" rel="noopener noreferrer"&gt;maintenance_tasks gem&lt;/a&gt; at work. The &lt;a href="https://railsguides.jp/active_record_migrations.html#%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B" rel="noopener noreferrer"&gt;Rails Guide&lt;/a&gt; even recommends using maintenance_tasks instead of manipulating data directly in migrations.&lt;/p&gt;

&lt;p&gt;However, if your needs aren’t extensive enough to justify maintenance_tasks, it’s still common to write and run a &lt;code&gt;rake task&lt;/code&gt;. Before maintenance_tasks came along, that was often the go-to solution.&lt;/p&gt;

&lt;p&gt;Among the variety of options available, a new but relatively unknown feature was introduced with Ruby on Rails 8.0.0: the &lt;code&gt;script&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This feature was added via this pull request:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/rails/rails/pull/52335" rel="noopener noreferrer"&gt;https://github.com/rails/rails/pull/52335&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  About the &lt;code&gt;script&lt;/code&gt; Folder
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://railsguides.jp/getting_started.html#%E3%83%96%E3%83%AD%E3%82%B0%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B" rel="noopener noreferrer"&gt;Rails Guide&lt;/a&gt; describes the &lt;code&gt;script&lt;/code&gt; folder as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A place to store disposable or general-purpose scripts and benchmarks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/script/USAGE" rel="noopener noreferrer"&gt;script/USAGE&lt;/a&gt; file further clarifies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate a one-off or general purpose script, such as a data migration script, cleanup script, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, the &lt;code&gt;script&lt;/code&gt; folder is well-suited for one-off data migrations. Quietly and without much fanfare, Rails 8 introduced a first-party solution for these single-run data migration needs.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Use the &lt;code&gt;script&lt;/code&gt; Folder
&lt;/h2&gt;

&lt;p&gt;Using the &lt;code&gt;script&lt;/code&gt; folder is straightforward. Just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails generate script my_script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command generates a file at &lt;code&gt;script/my_script.rb&lt;/code&gt; with the following default content:&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;"../config/environment"&lt;/span&gt;

&lt;span class="c1"&gt;# Your code goes here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can modify this file to include any Rails code you need.&lt;br&gt;
For example, you might write a quick data migration like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"../../config/environment"&lt;/span&gt;

&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ARGV&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&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="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;post&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;body: &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post updated!"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Post created!"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run it, simply do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby script/my_script.rb hello world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is handy for simple, one-off scripts. For more background, you might check out this discussion:&lt;br&gt;
&lt;a href="https://discuss.rubyonrails.org/t/is-there-any-official-way-to-organize-one-off-scripts/74186" rel="noopener noreferrer"&gt;Is there any “official” way to organize one-off scripts?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Running These Scripts from the Browser and Managing Execution History
&lt;/h2&gt;

&lt;p&gt;While the script folder is convenient, there’s still a catch: I don’t want to SSH into production to run these scripts. Ideally, I’d like to run them from a browser and keep an execution log for auditing purposes.&lt;/p&gt;

&lt;p&gt;That’s exactly why I created Scriptor.&lt;/p&gt;

&lt;p&gt;Once again, here’s a quick look:&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/hatsu38/scriptor" rel="noopener noreferrer"&gt;https://github.com/hatsu38/scriptor&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A Quick Overview of Scriptor
&lt;/h2&gt;

&lt;p&gt;Scriptor allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse scripts from the script folder via the browser&lt;/li&gt;
&lt;li&gt;Execute these scripts from the browser&lt;/li&gt;
&lt;li&gt;View a history of past executions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, let’s say your script folder looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ tree script                                       
script
├── migration
│   ├── hoges.rb
│   └── posts.rb
└── my_script.rb

2 directories, 3 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigating to &lt;a href="http://localhost:3000/scriptor" rel="noopener noreferrer"&gt;http://localhost:3000/scriptor&lt;/a&gt; displays a list of these scripts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksjv9yl3z3ks7jo5eutl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fksjv9yl3z3ks7jo5eutl.png" alt="スクリーンショット 2024-12-20 2.05.09.png" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on a script’s detail page shows its code:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4rwf4cqmlxjx8tfzgi6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4rwf4cqmlxjx8tfzgi6d.png" alt="FireShot Capture 068 - Scriptor - localhost.png" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this page, you can click the &lt;code&gt;🛠️Run Script&lt;/code&gt; button to execute the script. If the script requires arguments, you can input them into a text field before execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo944phax4bh8m4swit5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo944phax4bh8m4swit5x.png" alt="スクリーンショット 2024-12-20 2.36.33.png" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom of the page, you can view the execution history:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd5pril64rfhpjd97jyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd5pril64rfhpjd97jyy.png" alt="スクリーンショット 2024-12-20 2.36.38.png" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The execution history stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status (running, success, error)&lt;/li&gt;
&lt;li&gt;Start time&lt;/li&gt;
&lt;li&gt;End time&lt;/li&gt;
&lt;li&gt;Executed command&lt;/li&gt;
&lt;li&gt;Targeted file&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It’s easy to get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add the gem:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add scriptor
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Run the installer:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails generate scriptor:install
&lt;/code&gt;&lt;/pre&gt;



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

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;mount Scriptor::Engine =&amp;gt; "/scriptor"&lt;/code&gt; to &lt;code&gt;config/routes.rb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a migration file &lt;code&gt;****_create_scriptor_executions.scriptor.rb&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Place your scripts in the script folder, and they’ll appear at &lt;a href="http://localhost:3000/scriptor" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="http://localhost:3000/scriptor" rel="noopener noreferrer"&gt;http://localhost:3000/scriptor&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;That’s it! You’ve got a handy way to run and manage scripts directly from the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Rails 8 introduced the script folder as a first-party solution for running one-off scripts, including simple data migrations. Wanting to leverage this feature more conveniently, I ended up building a Rails Engine Gem—Scriptor—to execute these scripts from the browser and log their histories.&lt;/p&gt;

&lt;p&gt;I’m new to Gem development, but I had a lot of fun making it, and I think it turned out pretty well. Of course, it’s still missing some features like English documentation and comprehensive tests, so contributions are welcome. Stars on GitHub are also much appreciated!&lt;/p&gt;

&lt;p&gt;Check out the repo:&lt;br&gt;
&lt;a href="https://github.com/hatsu38/scriptor" rel="noopener noreferrer"&gt;https://github.com/hatsu38/scriptor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

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