<?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: Mario Alberto Chávez</title>
    <description>The latest articles on DEV Community by Mario Alberto Chávez (@mario_chavez).</description>
    <link>https://dev.to/mario_chavez</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%2F181972%2Fb622d20c-3b09-4399-b1c1-533ffc6a30a9.png</url>
      <title>DEV Community: Mario Alberto Chávez</title>
      <link>https://dev.to/mario_chavez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mario_chavez"/>
    <language>en</language>
    <item>
      <title>Rails MCP Server: Context-Efficient Tool Architecture</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Wed, 10 Dec 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/rails-mcp-server-context-efficient-tool-architecture-27a</link>
      <guid>https://dev.to/mario_chavez/rails-mcp-server-context-efficient-tool-architecture-27a</guid>
      <description>&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%2F2gox7awllsolm01bmktz.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%2F2gox7awllsolm01bmktz.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learn how Rails MCP Server’s new architecture reduces context consumption through progressive tool discovery, Rails introspection, and Prism static analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Context Budget Problem&lt;/li&gt;
&lt;li&gt;Progressive Tool Discovery&lt;/li&gt;
&lt;li&gt;Rails Introspection: Beyond Regex Parsing&lt;/li&gt;
&lt;li&gt;Prism Static Analysis&lt;/li&gt;
&lt;li&gt;Detail Levels and Filtering&lt;/li&gt;
&lt;li&gt;Sandboxed Ruby Execution&lt;/li&gt;
&lt;li&gt;Improved Discovery UX&lt;/li&gt;
&lt;li&gt;Interactive Configuration Tool&lt;/li&gt;
&lt;li&gt;How I Use the New Architecture&lt;/li&gt;
&lt;li&gt;Upgrading to the New Version&lt;/li&gt;
&lt;li&gt;Looking Forward&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Context Budget Problem
&lt;/h2&gt;

&lt;p&gt;Last week I came across an &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;Anthropic blog post&lt;/a&gt; that fundamentally changed how I think about MCP server design. The insight was simple but profound: every tool you register with an MCP server gets sent to Claude at the start of each session.&lt;/p&gt;

&lt;p&gt;With the Rails MCP Server’s previous 12 tools, that meant roughly 2,400 tokens consumed before asking a single question. On large Rails codebases where I’m already sharing multiple files and documentation, this context overhead matters. I’ve hit conversation limits mid-feature more times than I’d like to admit.&lt;/p&gt;

&lt;p&gt;The Anthropic post suggested a different approach: progressive disclosure. Instead of loading all tool definitions upfront, let the AI discover them when relevant. This resonated with how I already work—I don’t need Claude to know about route analysis when I’m focused on model validations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive Tool Discovery
&lt;/h2&gt;

&lt;p&gt;The most significant change in this release is the reduction from 12 registered tools to just 4. The other tools didn’t disappear—they became internal analyzers that Claude discovers on-demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The four registered tools:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;switch_project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Select which Rails project to analyze&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discover available analyzers by keyword or category&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;execute_tool&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run any analyzer by name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;execute_ruby&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sandboxed Ruby execution for complex queries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The previous tools like &lt;code&gt;analyze_models&lt;/code&gt;, &lt;code&gt;get_routes&lt;/code&gt;, and &lt;code&gt;get_schema&lt;/code&gt; are now internal analyzers. Claude finds them through &lt;code&gt;search_tools&lt;/code&gt; and invokes them through &lt;code&gt;execute_tool&lt;/code&gt;. This pattern reduces the initial context footprint by roughly 67%.&lt;/p&gt;

&lt;p&gt;When I start a session now, Claude only knows about these four capabilities. If I ask about database structure, Claude can search for relevant tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
search\_tools(query: "database schema")

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

&lt;/div&gt;



&lt;p&gt;This returns information about &lt;code&gt;get_schema&lt;/code&gt; without having loaded all nine analyzers upfront. Claude then invokes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
execute\_tool(tool\_name: "get\_schema", params: { table\_name: "users" })

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

&lt;/div&gt;



&lt;p&gt;The workflow feels natural—Claude discovers what it needs when it needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails Introspection: Beyond Regex Parsing
&lt;/h2&gt;

&lt;p&gt;While refactoring the tool architecture, I realized something uncomfortable: the previous implementation relied heavily on regex parsing. Patterns like &lt;code&gt;has_many\s+:(\w+)&lt;/code&gt; worked most of the time, but missed edge cases—associations with options, multi-line declarations, dynamic associations.&lt;/p&gt;

&lt;p&gt;This release replaces regex parsing with proper Rails introspection. When you analyze a model now, the server actually asks Rails about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model.reflect_on_all_associations # All associations with options
Model.validators # All validations with conditions
Model.defined_enums # Enum definitions and values
Model.columns_hash # Column details from the database

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

&lt;/div&gt;



&lt;p&gt;For routes, instead of parsing the text output of &lt;code&gt;rails routes&lt;/code&gt;, the server accesses route objects directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rails.application.routes.routes.map do |route|
  {
    verb: route.verb,
    path: route.path.spec.to_s,
    controller: route.defaults[:controller],
    action: route.defaults[:action],
    constraints: route.constraints
  }
end

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

&lt;/div&gt;



&lt;p&gt;For controllers, I now use &lt;code&gt;action_methods&lt;/code&gt; instead of scanning for &lt;code&gt;def&lt;/code&gt; statements, and &lt;code&gt;_process_action_callbacks&lt;/code&gt; to find before/after actions accurately.&lt;/p&gt;

&lt;p&gt;The difference in accuracy is significant. Associations with &lt;code&gt;through:&lt;/code&gt;, &lt;code&gt;class_name:&lt;/code&gt;, or &lt;code&gt;foreign_key:&lt;/code&gt; options are now captured correctly. Validations show their conditions. Callbacks include their &lt;code&gt;only:&lt;/code&gt; and &lt;code&gt;except:&lt;/code&gt; filters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prism Static Analysis
&lt;/h2&gt;

&lt;p&gt;Rails introspection tells you what exists at runtime, but sometimes you need to understand the code structure itself. For this, I added Prism static analysis.&lt;/p&gt;

&lt;p&gt;Prism is Ruby’s new parser that ships with Ruby 3.3+. It provides a clean AST that I can traverse to extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Callbacks and their method references&lt;/li&gt;
&lt;li&gt;Scope definitions&lt;/li&gt;
&lt;li&gt;Included concerns and modules&lt;/li&gt;
&lt;li&gt;Method definitions with line numbers&lt;/li&gt;
&lt;li&gt;Instance variables assigned per action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;analysis_type&lt;/code&gt; parameter lets you choose what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute_tool(
  tool_name: "analyze_models",
  params: {
    model_name: "User",
    analysis_type: "full" # "introspection", "static", or "full"
  }
)

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

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;analysis_type: "full"&lt;/code&gt;, Claude gets both the runtime reflection data and the static code analysis. With &lt;code&gt;analysis_type: "static"&lt;/code&gt;, it gets just the AST-derived information—useful when you want to understand code structure without loading the Rails environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detail Levels and Filtering
&lt;/h2&gt;

&lt;p&gt;Another insight from the Anthropic post: intermediate results consume tokens too. When you ask about routes, do you really need the complete output of all 200 routes with constraints and defaults?&lt;/p&gt;

&lt;p&gt;Every analyzer now supports a &lt;code&gt;detail_level&lt;/code&gt; parameter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;names&lt;/code&gt; — Minimal output, just identifiers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;summary&lt;/code&gt; — Names plus brief descriptions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;full&lt;/code&gt; — Complete information (the previous default)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For routes specifically, I added filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute_tool(
  tool_name: "get_routes",
  params: {
    controller: "api/v1/users",
    verb: "GET",
    detail_level: "summary"
  }
)

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

&lt;/div&gt;



&lt;p&gt;Instead of 200 routes, Claude might get 5. The token savings compound across a conversation.&lt;/p&gt;

&lt;p&gt;For models and schemas, batch operations reduce round-trips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute_tool(
  tool_name: "analyze_models",
  params: {
    model_names: ["User", "Post", "Comment"],
    detail_level: "associations"
  }
)

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

&lt;/div&gt;



&lt;p&gt;One call, three models, associations only. No source code, no validations, no columns—just the relationships Claude needs for the current task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sandboxed Ruby Execution
&lt;/h2&gt;

&lt;p&gt;The most powerful addition is &lt;code&gt;execute_ruby&lt;/code&gt;. Sometimes the predefined analyzers aren’t enough—you need a custom query that doesn’t fit the standard patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute_ruby(code: &amp;lt;&amp;lt;~RUBY)
  User.joins(:posts)
      .group("users.id")
      .having("COUNT(posts.id) &amp;gt; 10")
      .pluck(:email)
RUBY

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

&lt;/div&gt;



&lt;p&gt;This runs in a sandboxed environment with strict security controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No file writes&lt;/strong&gt; — Cannot create, modify, or delete files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No system calls&lt;/strong&gt; — &lt;code&gt;system()&lt;/code&gt;, backticks, and &lt;code&gt;exec&lt;/code&gt; are blocked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No network access&lt;/strong&gt; — Cannot make HTTP requests or open sockets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No sensitive file reads&lt;/strong&gt; — &lt;code&gt;.env&lt;/code&gt;, credentials, and &lt;code&gt;.gitignore&lt;/code&gt;‘d files are protected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project-scoped&lt;/strong&gt; — Cannot access files outside the project directory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout protected&lt;/strong&gt; — Maximum 60 seconds execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sandbox provides helper methods for common operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read_file("app/models/user.rb") # Safe file reading
file_exists?("config/database.yml") # Existence check (false for sensitive files)
list_files("app/models/**/*.rb") # Glob with filtering
project_root # Project path constant

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

&lt;/div&gt;



&lt;p&gt;I use this for complex queries that would otherwise require multiple tool calls or for analysis patterns specific to my projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improved Discovery UX
&lt;/h2&gt;

&lt;p&gt;After testing the new architecture with real conversations, I noticed AI agents sometimes struggled with the initial learning curve. Which tool reads files? What helpers are available in &lt;code&gt;execute_ruby&lt;/code&gt;? The progressive discovery approach works well once you know the patterns, but getting started needed to be smoother.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Start Guide
&lt;/h3&gt;

&lt;p&gt;Now when you switch projects, you get an immediate orientation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Switched to project: my_finances at path: /Users/mario/projects/my_finances

Quick Start:
• Get project overview: execute_tool("project_info")
• Read a file: execute_ruby("puts read_file('config/routes.rb')")
• Find files: execute_ruby("puts Dir.glob('app/models/*.rb').join('\n')")
• Analyze models: execute_tool("analyze_models", { model_name: "User" })
• Get routes: execute_tool("get_routes")
• Get schema: execute_tool("get_schema", { table_name: "users" })
• Search available tools: search_tools()

Helpers in execute_ruby: read_file(path), file_exists?(path), list_files(pattern), project_root
Note: Always use `puts` in execute_ruby to see output.

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

&lt;/div&gt;



&lt;p&gt;This eliminates the cold-start problem. Claude immediately knows the most common patterns without searching for them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;puts&lt;/code&gt; Problem
&lt;/h3&gt;

&lt;p&gt;One subtle issue kept appearing: &lt;code&gt;execute_ruby&lt;/code&gt; would return “Code executed successfully (no output)” when users forgot to wrap expressions in &lt;code&gt;puts&lt;/code&gt;. Now the no-output message includes helpful hints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code executed successfully (no output).

Hint: Use `puts` to see results, e.g.:
  puts read_file('config/routes.rb')
  puts User.count
  puts Dir.glob('app/models/*.rb')

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

&lt;/div&gt;



&lt;p&gt;Small friction points like this matter more than I initially realized. Every confused moment is context wasted on troubleshooting instead of actual work.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Agent Guide
&lt;/h3&gt;

&lt;p&gt;For teams using this with custom AI setups, I created a comprehensive guide at &lt;code&gt;docs/AGENT.md&lt;/code&gt; covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool selection decision trees&lt;/li&gt;
&lt;li&gt;Common pitfalls and how to avoid them&lt;/li&gt;
&lt;li&gt;Error handling and fallback strategies&lt;/li&gt;
&lt;li&gt;Integration patterns with other MCP servers (like &lt;a href="https://github.com/maquina-app/nvim-mcp-server" rel="noopener noreferrer"&gt;Neovim MCP&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The guide is written for AI consumption—structured, explicit, with clear examples for each scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive Configuration Tool
&lt;/h2&gt;

&lt;p&gt;Managing MCP server configuration used to involve editing YAML files and JSON configs manually. With multiple Rails projects, documentation guides, and Claude Desktop integration, this became tedious. So I built &lt;code&gt;rails-mcp-config&lt;/code&gt;—an interactive terminal UI for all configuration tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-config

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

&lt;/div&gt;



&lt;p&gt;The tool presents a clean menu organized by frequency of use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────┐
│ Rails MCP Server - Configuration │
╰──────────────────────────────────────────────────╯

Projects
  List projects
  Add project
  Add current directory (my-rails-app)
  Edit project
  Remove project
  Validate all projects
────────────────────
Guides
  Download guides
  Import custom guides
  Manage custom guides
────────────────────
Setup
  Claude Desktop integration
  Open config file
────────────────────
Exit

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project Management
&lt;/h3&gt;

&lt;p&gt;Adding a project is now a guided process. If you run the tool from within a Rails project directory, it offers to add that directory with one confirmation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Directory: ~/projects/my-rails-app

Project name: [my-rails-app]
✓ Added project 'my-rails-app' -&amp;gt; ~/projects/my-rails-app

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

&lt;/div&gt;



&lt;p&gt;The tool validates paths, checks for Gemfiles, and uses home-relative paths (&lt;code&gt;~/projects/...&lt;/code&gt;) for portability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation Guides
&lt;/h3&gt;

&lt;p&gt;Downloading Rails, Turbo, Stimulus, and Kamal guides is now visual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select guides to download:
  [x] rails (✓ downloaded)
  [x] turbo (not downloaded)
  [] stimulus (not downloaded)
  [] kamal (not downloaded)

⠸ Fetching files...
✓ rails: 2 downloaded, 45 skipped
✓ turbo: 12 downloaded, 0 skipped

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

&lt;/div&gt;



&lt;p&gt;You can also import your own markdown documentation and manage custom guides through the same interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Desktop Integration
&lt;/h3&gt;

&lt;p&gt;The most useful feature for new users is automatic Claude Desktop configuration. The tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detects your existing Claude Desktop config&lt;/li&gt;
&lt;li&gt;Shows current MCP server settings if configured&lt;/li&gt;
&lt;li&gt;Offers to add or update the Rails MCP Server configuration&lt;/li&gt;
&lt;li&gt;Automatically finds the correct Ruby and server executable paths&lt;/li&gt;
&lt;li&gt;Creates timestamped backups before any changes&lt;/li&gt;
&lt;li&gt;Supports both STDIO (recommended) and HTTP modes
&amp;lt;!-- end list --&amp;gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ Claude Desktop config file found
✓ Rails MCP Server is configured

Current configuration:
  command: /Users/mario/.rubies/ruby-3.3.0/bin/ruby
  args: /Users/mario/.gem/ruby/3.3.0/bin/rails-mcp-server

What would you like to do?
  View full config
  Update Rails MCP Server config
  Back to menu

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

&lt;/div&gt;



&lt;p&gt;For HTTP mode with &lt;code&gt;mcp-remote&lt;/code&gt;, the tool detects &lt;code&gt;npx&lt;/code&gt; and guides you through the URL configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhanced Terminal UI
&lt;/h3&gt;

&lt;p&gt;The tool uses &lt;a href="https://github.com/charmbracelet/gum" rel="noopener noreferrer"&gt;Gum&lt;/a&gt; when available for a polished experience with styled prompts, spinners, and tables. Without Gum, it falls back to a functional basic terminal interface—no dependencies required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Optional: Install Gum for enhanced UI
brew install gum # macOS
sudo apt install gum # Debian/Ubuntu

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

&lt;/div&gt;



&lt;p&gt;The legacy command-line tools (&lt;code&gt;rails-mcp-setup-claude&lt;/code&gt;, &lt;code&gt;rails-mcp-server-download-resources&lt;/code&gt;) still work for scripting and backward compatibility, but for interactive use, &lt;code&gt;rails-mcp-config&lt;/code&gt; is now the recommended approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Use the New Architecture
&lt;/h2&gt;

&lt;p&gt;My workflow has evolved with these changes. When I start a session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Switch to project my_finances.

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

&lt;/div&gt;



&lt;p&gt;I immediately see the Quick Start guide, so Claude knows the patterns without any additional discovery.&lt;/p&gt;

&lt;p&gt;When I need to explore unfamiliar territory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search for tools related to controllers and views.

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

&lt;/div&gt;



&lt;p&gt;Claude finds &lt;code&gt;analyze_controller_views&lt;/code&gt; and explains what it can do. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze the UsersController with full introspection and static analysis.

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

&lt;/div&gt;



&lt;p&gt;For focused work where I know what I need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get the schema for the transactions table, just the columns and indexes.

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;detail_level: "summary"&lt;/code&gt; is implicit in “just the columns”—Claude understands the intent and uses minimal output.&lt;/p&gt;

&lt;p&gt;For complex questions that span multiple concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute_ruby(code: &amp;lt;&amp;lt;~RUBY)
  # Find models with callbacks that touch the database
  Dir.glob("app/models/**/*.rb").select do |file|
    content = File.read(file)
    content.match?(/after_save|after_create|after_update/) &amp;amp;&amp;amp;
    content.match?(/\.save|\.update|\.create/)
  end
RUBY

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

&lt;/div&gt;



&lt;p&gt;This kind of query would be tedious with the standard tools but trivial with direct Ruby execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading to the New Version
&lt;/h2&gt;

&lt;p&gt;The upgrade is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install rails-mcp-server

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

&lt;/div&gt;



&lt;p&gt;After installation, run the interactive configuration tool to set up or verify your configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-config

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

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Add your Rails projects&lt;/li&gt;
&lt;li&gt;Download documentation guides&lt;/li&gt;
&lt;li&gt;Configure Claude Desktop integration (with automatic path detection)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new architecture is backward compatible in terms of functionality—everything the previous version could do, this version can do. The interface changed from direct tool calls to discovery and execution, but Claude adapts naturally.&lt;/p&gt;

&lt;p&gt;If you prefer manual configuration or scripting, the legacy commands still work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-setup-claude # Configure Claude Desktop
rails-mcp-server-download-resources rails # Download guides

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

&lt;/div&gt;



&lt;p&gt;For Prism static analysis to work, your Rails projects need Ruby 3.3+ or the Prism gem installed. If Prism isn’t available, the server gracefully falls back to introspection-only analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;While version 1.4.0 focused on context optimization, my current exploration for 1.4.1 is focused on &lt;strong&gt;agent portability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I am working on a &lt;code&gt;--single-project&lt;/code&gt; flag to support ephemeral environments like the &lt;strong&gt;GitHub Copilot Agent&lt;/strong&gt;. These agents run in temporary GitHub Actions environments where no &lt;code&gt;projects.yml&lt;/code&gt; exists and no global configuration can be persisted. The new flag will allow the server to skip loading &lt;code&gt;projects.yml&lt;/code&gt;, treat the current directory as the sole project, and auto-switch to it immediately on startup.&lt;/p&gt;

&lt;p&gt;This same feature unlocks support for &lt;strong&gt;Claude Code&lt;/strong&gt;. Because Claude Code uses worktrees where each session is a separate working directory. By leveraging STDIO mode with the &lt;code&gt;--single-project&lt;/code&gt; flag, each worktree can spawn its own isolated MCP server instance without port conflicts. This also allows the &lt;code&gt;.mcp.json&lt;/code&gt; configuration to be committed directly to version control, making the setup reproducible for the entire team.&lt;/p&gt;

&lt;p&gt;The combination of reduced tool registration, Rails introspection, Prism analysis, and these upcoming compatibility features makes the Rails MCP Server not just smarter, but more adaptable to the rapidly evolving ecosystem of AI agents.&lt;/p&gt;

&lt;p&gt;Source code is available at &lt;a href="https://github.com/maquina-app/rails-mcp-server" rel="noopener noreferrer"&gt;https://github.com/maquina-app/rails-mcp-server&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariochavez.io/desarrollo/rails/ai-tools/development-workflow/2025/06/03/rails-mcp-server-enhanced-documentation-access/" rel="noopener noreferrer"&gt;Rails MCP Server: Enhanced Documentation Access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariochavez.io/desarrollo/2025/04/20/rails-mcp-server-with-sse/" rel="noopener noreferrer"&gt;Rails MCP Server with HTTP Server-Sent Events support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariochavez.io/desarrollo/2025/03/21/rails-mcp-server-enhancing-ai-assisted-development/" rel="noopener noreferrer"&gt;Rails MCP Server - Enhancing AI-Assisted Development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>performance</category>
      <category>rails</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Rails Upgrades with AI: A Real-World Success Story</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Thu, 27 Nov 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/rails-upgrades-with-ai-a-real-world-success-story-4ccc</link>
      <guid>https://dev.to/mario_chavez/rails-upgrades-with-ai-a-real-world-success-story-4ccc</guid>
      <description>&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%2Fx9hp2mfasj1ndnsd7coy.jpg" 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%2Fx9hp2mfasj1ndnsd7coy.jpg" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few weeks ago, I introduced the &lt;strong&gt;Rails Upgrade Skill&lt;/strong&gt; for Anthropic’s Claude. You can check out the repository here: &lt;a href="https://github.com/maquina-app/rails-upgrade-skill" rel="noopener noreferrer"&gt;https://github.com/maquina-app/rails-upgrade-skill&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Yesterday, a client’s Ruby on Rails application—focused on EMR (Electronic Medical Records)—was successfully &lt;strong&gt;deployed to Rails 8&lt;/strong&gt; with only a minor, quickly resolved issue! Just a week prior, this same application was running on the End-of-Life (EOL) version, &lt;strong&gt;Rails 7.1&lt;/strong&gt;.&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%2Fkg62csgp09k4vrkyryv3.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%2Fkg62csgp09k4vrkyryv3.png" alt="Image: Upgrade to 7.2 summary report" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the &lt;strong&gt;Rails Upgrade Skill&lt;/strong&gt; , I was able to manage this rapid transition. The process involved first upgrading the application to &lt;strong&gt;Rails 7.2&lt;/strong&gt; and deploying it to production, and then, days later, performing the jump to &lt;strong&gt;Rails 8&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To be honest, the changes to the existing codebase were minor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A few broken &lt;strong&gt;specs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;One &lt;strong&gt;gem&lt;/strong&gt; that hadn’t been updated for recent Rails versions.&lt;/li&gt;
&lt;li&gt;Two small issues that were not caught by the test suite but were easily fixed &lt;em&gt;before&lt;/em&gt; they became real-world problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently, I even have a Work-In-Progress (WIP) Pull Request for the &lt;strong&gt;Rails 8.1&lt;/strong&gt; version of this application. It’s blocked only because the &lt;strong&gt;Mongoid&lt;/strong&gt; gem doesn’t yet support the latest released gems in Rails 8.1, but the core upgrade code is ready in the repository. Once again, the skill performed most of the work; we’re just waiting for the gem to be released to continue the upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Power of the Skill 💡
&lt;/h3&gt;

&lt;p&gt;The skill doesn’t just provide a high-level analysis; it gives &lt;strong&gt;clear, actionable guidance&lt;/strong&gt; on the changes between versions. This includes:&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%2F32324p8x6uejbw41o2uo.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%2F32324p8x6uejbw41o2uo.png" alt="Image: Files changes report" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step-by-step actions&lt;/strong&gt; with a tailored script to check for breaking changes.&lt;/li&gt;
&lt;li&gt;Precise instructions for &lt;strong&gt;creating a branch&lt;/strong&gt; and &lt;strong&gt;running necessary commands&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Exactly &lt;strong&gt;what files to modify&lt;/strong&gt; and the &lt;strong&gt;rationale&lt;/strong&gt; behind each change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crucially, it makes merging &lt;strong&gt;configuration-specific changes&lt;/strong&gt; much easier—a task that is often painful when using the &lt;code&gt;rails app:update&lt;/code&gt; command.&lt;/p&gt;




&lt;h3&gt;
  
  
  Related posts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mariochavez.io/desarrollo/2025/11/12/upgrading-rails-ai-skill/" rel="noopener noreferrer"&gt;Upgrading Rails applications with an AI skill&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>ai</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Vibecoding the Physical: How AI Helped Me Bind My Photobook</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Sat, 22 Nov 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/vibecoding-the-physical-how-ai-helped-me-bind-my-photobook-2jfd</link>
      <guid>https://dev.to/mario_chavez/vibecoding-the-physical-how-ai-helped-me-bind-my-photobook-2jfd</guid>
      <description>&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%2Fxl6qt09fiar5jlrpx9sl.jpg" 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%2Fxl6qt09fiar5jlrpx9sl.jpg" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, I reach for Claude Desktop just to “vibecode.” I start with a spark—a very simple idea—and I iterate, building complexity layer by layer. Usually, I don’t even specify the tech stack. By default, Sonnet tends to choose React.&lt;/p&gt;

&lt;p&gt;Here’s the catch: my knowledge of React is basic. I can’t strictly tell if the code is idiomatic or “correct.” I can only treat the app as a black box—testing it to see if it works as expected. I leave the architectural decisions to the model and focus purely on the outcome.&lt;/p&gt;

&lt;p&gt;But this time, the outcome was personal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Artist’s Need
&lt;/h2&gt;

&lt;p&gt;Besides being a Software Engineer, I am a Visual Artist and contemporary photographer. Since 2022, I’ve been working on a long-term project that is finally becoming my first photobook.&lt;/p&gt;

&lt;p&gt;Recently, my book editor and designer sent me a draft PDF. It looked great on screen, but a photobook is a physical object. I needed to “feel” the book before giving feedback. Electronic documents don’t have weight; they don’t have page turns.&lt;/p&gt;

&lt;p&gt;I have the knowledge to print at home on a consumer printer. I also know the process required to take a linear PDF and rearrange the pages into “signatures” (groups of folded sheets) for traditional binding. But doing this manually is tedious and error-prone. One calculation mistake, one wrong paper flip, and the page order is ruined.&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%2Fs5pk1jdad1i11umox34o.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%2Fs5pk1jdad1i11umox34o.png" alt="Image: Understanding Signature Binding documentation section" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Solution
&lt;/h2&gt;

&lt;p&gt;I needed a tool, not a math lesson. I opened Claude and pitched the idea: a &lt;strong&gt;“Photobook Signature and Printing Layout Calculator.”&lt;/strong&gt;&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%2F3ayn0psyy8q2ym0c5nwa.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%2F3ayn0psyy8q2ym0c5nwa.png" alt="Image: Signatures visualization" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted to upload my PDF and have the tool calculate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How many signatures do I need for binding?&lt;/li&gt;
&lt;li&gt;How to rearrange (impose) the pages for printing?&lt;/li&gt;
&lt;li&gt;How to optimize paper usage based on my printer’s specific paper size?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After about 10 iterations, Sonnet nailed it. It built a fully client-side React application. It visualizes the grid, handles the imposition math, and even generates a visual guide for printing.&lt;/p&gt;

&lt;p&gt;Because I was “vibecoding,” I didn’t stop at the code. I asked Claude to generate a comprehensive user manual for me (the Photographer) and a technical architecture document for “Future Me” (the Developer).&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%2F60j38z68gse8ep6gauvz.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%2F60j38z68gse8ep6gauvz.png" alt="Image: Printing layout" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Engineer’s Return
&lt;/h2&gt;

&lt;p&gt;This is where the process gets interesting. The React app worked, but as an engineer, I felt a bit detached from the “how.” I wanted to ground the project in a stack I actually mastered.&lt;/p&gt;

&lt;p&gt;So, I asked for a &lt;strong&gt;Spike&lt;/strong&gt; :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can you make a spike and convert this application to Rails? Don’t worry about the full application; assume that it is there and you are only creating the needed files… Think harder about what can be pushed to the backend with ActiveStorage, and what needs to happen in the front end with ERB, Tailwind, Hotwire, and Stimulus.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The vibe shifted. Sonnet didn’t get the Rails architecture perfect on the first try, but because I know Rails deeply, I could spot the flaws immediately. I knew where the N+1 queries would hide, how to better utilize ActiveStorage, or where a specific Gem would be more efficient than custom logic.&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%2F50kwakjafl09uf39clnw.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%2F50kwakjafl09uf39clnw.png" alt="Image: The Ruby on Rails spike" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;I have found this to be a powerful workflow for testing ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vibecode in the unknown:&lt;/strong&gt; Let AI build the prototype in a stack it prefers (like React). Focus purely on the user experience and solving the pain point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spike into the known:&lt;/strong&gt; Once the logic is proven, ask AI to port it to my core stack (Rails).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refine with expertise:&lt;/strong&gt; Use my engineering seniority to polish the code that I now fully understand.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result? I have a tool that solves my artistic problem, and it saved me from the manual process to get the layout right. Now, I can go back to my book editing software and rearrange pages to the suggested layout, then finally print my draft book.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>learning</category>
      <category>ai</category>
      <category>react</category>
    </item>
    <item>
      <title>Upgrading Rails applications with an AI skill</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Wed, 12 Nov 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/upgrading-rails-applications-with-an-ai-skill-1fa3</link>
      <guid>https://dev.to/mario_chavez/upgrading-rails-applications-with-an-ai-skill-1fa3</guid>
      <description>&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%2Fooxumwu8rs7cc5hgc8ee.jpg" 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%2Fooxumwu8rs7cc5hgc8ee.jpg" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What if you could use an AI skill to handle the most tedious, error-prone parts of a Ruby on Rails upgrade? As someone who’s been in the Rails trenches since 2008, I’ve spent countless hours on this exact task.&lt;/p&gt;

&lt;p&gt;In my time as a founder, contractor, and company owner, I’ve upgraded applications from nearly every era. The early days of 2.x, 3.x, and 4.x were battles against breaking API changes, specific and complex monkey patches, and gems that were abandoned over time.&lt;/p&gt;

&lt;p&gt;Things got better after Rails 5.2, but one command still triggers a familiar headache: &lt;code&gt;rails app:update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The real challenge isn’t the command itself. It’s the high-stakes, manual process that follows: &lt;strong&gt;meticulously merging your application’s custom configurations&lt;/strong&gt; with the framework’s new defaults. Even with great tools like &lt;a href="https://railsdiff.org/" rel="noopener noreferrer"&gt;RailsDiff&lt;/a&gt;, it’s a slow, manual process where a single mistake can cause subtle, cascading bugs.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Spark of an Idea
&lt;/h3&gt;

&lt;p&gt;I’ve been thinking about how to improve this process for a while. When Anthropic first announced the Model Context Protocol (MCP), I saw a post on X (formerly Twitter) asking if AI could finally solve this exact config-merging nightmare. The idea struck a chord, but I was swamped with work.&lt;/p&gt;

&lt;p&gt;A few weeks ago, that idea resurfaced. A client’s application on Rails 7.1 was approaching its end-of-life (EOL) date, and I was staring down another upgrade. But this time, something was different: Anthropic had just launched &lt;strong&gt;Skills&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As I was planning the upgrade, I was open to considering if one of these new &lt;strong&gt;Skills&lt;/strong&gt; could be the solution. I realized a “skill” wasn’t just a general-purpose AI; it was a specialized, tool-driven capability. Could this be the key to &lt;em&gt;finally&lt;/em&gt; automating the most painful part of a Rails upgrade?&lt;/p&gt;

&lt;p&gt;I had to find out.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing the &lt;code&gt;rails-upgrade-skill&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The result of that experiment is the &lt;strong&gt;Rails Upgrade Assistant Skill&lt;/strong&gt; (or &lt;code&gt;rails-upgrade-skill&lt;/code&gt; in its repository). This is a specialized &lt;strong&gt;AI skill&lt;/strong&gt; that uses Claude’s intelligence to guide the entire upgrade process.&lt;/p&gt;

&lt;p&gt;It’s built using official Rails CHANGELOGs to intelligently plan your upgrade path for any version from &lt;strong&gt;Rails 7.0 through 8.1.1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes this skill an &lt;em&gt;intelligent&lt;/em&gt; upgrade assistant?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intelligent Analysis:&lt;/strong&gt; It calls the Rails MCP Server to read &lt;strong&gt;your actual project files&lt;/strong&gt; , understand &lt;strong&gt;your customizations&lt;/strong&gt; , and provide personalized guidance—not generic advice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom Code Preservation:&lt;/strong&gt; It automatically detects custom configurations and provides specific warnings (e.g., about custom SSL middleware or autoload paths) with clear instructions on how to migrate them safely, ensuring your code logic isn’t lost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sequential Enforcement:&lt;/strong&gt; It prevents the dangerous practice of skipping versions. If you ask for a multi-hop upgrade (e.g., 7.0 to 8.1), it automatically plans the correct sequential path (7.0 → 7.1 → 7.2 → 8.0 → 8.1) and guides you through each hop separately.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Make It Your Own
&lt;/h3&gt;

&lt;p&gt;This skill is currently &lt;strong&gt;opinionated and tightly coupled to my personal workflow&lt;/strong&gt;. It’s built to work with two other tools I’ve made:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/maquina-app/rails-mcp-server" rel="noopener noreferrer"&gt;Rails MCP Server&lt;/a&gt;: Used to gather deep information about your Rails application (like version, file contents, routes).
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/maquina-app/nvim-mcp-server" rel="noopener noreferrer"&gt;Neovim MCP Server&lt;/a&gt;: Used to allow Claude to apply the identified changes directly to your files in Neovim (the &lt;strong&gt;Interactive Mode&lt;/strong&gt; ).
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But this is the beauty of Skills. You don’t have to use my stack. I invite you to &lt;strong&gt;fork the repo&lt;/strong&gt; and edit the &lt;code&gt;SKILL.md&lt;/code&gt; file. You can instruct the skill on how &lt;em&gt;you&lt;/em&gt; want it to get project information or apply changes, tailoring it to your own editor and workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Use the Skill: A Step-by-Step Guide
&lt;/h2&gt;

&lt;p&gt;Here’s how it works in my setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Installation
&lt;/h3&gt;

&lt;p&gt;First, clone the repository and run the build script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; git clone [https://github.com/maquina-app/rails-upgrade-skill.git](https://github.com/maquina-app/rails-upgrade-skill.git)
&amp;gt; cd rails-upgrade-skill
&amp;gt; bin/build

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

&lt;/div&gt;



&lt;p&gt;This command zips the skill into a &lt;code&gt;/build&lt;/code&gt; folder. Next, open your Claude Desktop app, go to &lt;strong&gt;Settings &amp;gt; Capabilities&lt;/strong&gt; , and scroll down to &lt;strong&gt;Skills&lt;/strong&gt;. You can upload the &lt;code&gt;build/rails-upgrade-skill.zip&lt;/code&gt; file there.&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%2Fal6rn0hs9a6jdc1r2ysp.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%2Fal6rn0hs9a6jdc1r2ysp.png" alt="Install the skill" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set Your Project Context
&lt;/h3&gt;

&lt;p&gt;In a new chat, you need to tell Claude which project you’re working on. If you’re using my Rails MCP Server, the command is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Switch to my_app project and show me the project information&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This activates your Rails application’s context and allows the skill to gather its details.&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%2Fvjt94c9xszamq98zvyyt.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%2Fvjt94c9xszamq98zvyyt.png" alt="Switch to your application context" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Activate the Upgrade Skill
&lt;/h3&gt;

&lt;p&gt;Once the context is set, you can invoke the AI skill with a simple prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Help me upgrade my Rails application to next available version&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The skill takes over and kicks off a three-step process, which can be done in two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mode 1 (Report-Only):&lt;/strong&gt; Claude provides the comprehensive report, and you apply changes manually. This is recommended for your first time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mode 2 (Interactive):&lt;/strong&gt; Claude guides you and offers to apply changes directly to your open files in Neovim.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fz94pyl9ic5hj9xv18okb.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%2Fz94pyl9ic5hj9xv18okb.png" alt="Activate the skill" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Three-Phase Process
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Detect Deprecations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The skill generates a bash script for you. You copy and run this script in your application’s root directory. It scans your application for deprecation patterns and breaking changes, saving findings to a .txt file, which you then paste back into Claude.&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%2Fj43ifaqewmgnd8ujh8fg.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%2Fj43ifaqewmgnd8ujh8fg.png" alt="Run the script" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Generate a Comprehensive Report&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the core value. Using the project analysis and the deprecation findings, the skill generates a detailed report. It breaks down changes by priority (HIGH, MEDIUM, LOW) and component (ActiveRecord, ActionMailer, etc.).&lt;/p&gt;

&lt;p&gt;Crucially, it &lt;strong&gt;intelligently merges the new Rails defaults with your existing custom settings&lt;/strong&gt; and provides &lt;strong&gt;OLD vs. NEW code examples&lt;/strong&gt; with explanation.&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%2Fnn0kc2t62hdo1bdkq5yi.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%2Fnn0kc2t62hdo1bdkq5yi.png" alt="The comprehensive report" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Apply the Changes (The app:update Report)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The final report summarizes all files needing changes. If you are using the Interactive Mode with the Neovim MCP server, you can review the proposed changes and then tell Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All files are ready on nvim buffers, update them all&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude will then apply all the approved changes directly to your open files.&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%2Fy53jc8f2gyf47p83tke9.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%2Fy53jc8f2gyf47p83tke9.png" alt="The app:update report" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Final Mile: After the Skill
&lt;/h2&gt;

&lt;p&gt;This AI skill handles the most complex parts of the upgrade, getting you 90% of the way there. You still need to follow the standard Rails procedure:&lt;/p&gt;

&lt;p&gt;First, update your gems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bundle update

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

&lt;/div&gt;



&lt;p&gt;Next, run the &lt;code&gt;rails app:update&lt;/code&gt; command. Run it with &lt;strong&gt;confidence&lt;/strong&gt;. When the command flags conflicts in config files, you can usually &lt;strong&gt;skip&lt;/strong&gt; overwriting them, knowing the logic has already been intelligently merged by the skill. You then let the command proceed to create &lt;em&gt;new&lt;/em&gt; files, like initializers and migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; rails app:update

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

&lt;/div&gt;



&lt;p&gt;And the last, most crucial step: &lt;strong&gt;run your test suite&lt;/strong&gt; and test the application manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I’ve already used this skill to upgrade a client application and several of my own projects, turning hours of tedious work into a process that takes just a few minutes of interaction time.&lt;/p&gt;

&lt;p&gt;This, for me, represents the true power of AI in development: combining deep, specialized domain knowledge (the nuances of a Rails upgrade) with new tools like Anthropic Skills.&lt;/p&gt;

&lt;p&gt;Please, &lt;strong&gt;give the &lt;a href="https://github.com/maquina-app/rails-upgrade-skill" rel="noopener noreferrer"&gt;rails-upgrade-skill&lt;/a&gt; a try&lt;/strong&gt;. Fork it, adapt it to your own workflow, and let me know what you think.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>ai</category>
    </item>
    <item>
      <title>Rails MCP Server: Enhanced Documentation Access</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Tue, 03 Jun 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/rails-mcp-server-enhanced-documentation-access-2jin</link>
      <guid>https://dev.to/mario_chavez/rails-mcp-server-enhanced-documentation-access-2jin</guid>
      <description>&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%2Fxbyit9zzv9d1ib0txihh.jpg" 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%2Fxbyit9zzv9d1ib0txihh.jpg" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails MCP Server: Enhanced Documentation Access and AI Workflow Integration
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Updated Rails MCP Server now provides consistent, up-to-date Rails documentation across multiple LLM clients with enhanced proxy support and Neovim integration.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Comprehensive Documentation Resources&lt;/li&gt;
&lt;li&gt;The Documentation Challenge&lt;/li&gt;
&lt;li&gt;Five Resource Categories Available&lt;/li&gt;
&lt;li&gt;Setting Up Documentation Resources&lt;/li&gt;
&lt;li&gt;MCP Proxy Integration for Better Compatibility&lt;/li&gt;
&lt;li&gt;Rails MCP + Neovim MCP Integration Workflow&lt;/li&gt;
&lt;li&gt;Documentation as a Shared Resource&lt;/li&gt;
&lt;li&gt;Resource Management and Best Practices&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The latest updates to &lt;strong&gt;Rails MCP Server&lt;/strong&gt; introduce significant improvements to documentation access, better integration capabilities, and enhanced workflow support that have transformed how I work with Rails projects using AI assistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comprehensive Documentation Resources
&lt;/h2&gt;

&lt;p&gt;The most significant addition in this release is the comprehensive &lt;strong&gt;Resources and Documentation system&lt;/strong&gt;. This addresses a critical need in AI-assisted development: providing LLM clients with consistent, up-to-date documentation that can be shared across multiple AI sessions and different LLM providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Documentation Challenge
&lt;/h3&gt;

&lt;p&gt;When working with AI assistants on Rails projects, I frequently encountered situations where the LLM’s training data was outdated or incomplete regarding specific framework features. This led to suggestions based on deprecated APIs or missing newer functionality. The resource system solves this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ensuring accuracy&lt;/strong&gt; : LLMs receive the exact same official documentation that developers reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining consistency&lt;/strong&gt; : Multiple AI sessions can access identical documentation, ensuring consistent guidance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staying current&lt;/strong&gt; : Documentation can be updated to match specific Rails versions or framework releases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharing context&lt;/strong&gt; : The same documentation resources work across different LLM clients and providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Five Resource Categories Available
&lt;/h3&gt;

&lt;p&gt;The server now provides access to five complete documentation libraries:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Rails Guides Documentation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt; : Official Ruby on Rails 8.0.2 documentation - all 50+ guides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage&lt;/strong&gt; : Getting started to advanced topics including Active Record, Action Pack, security, and deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case&lt;/strong&gt; : Comprehensive Rails framework reference&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Turbo Framework Documentation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt; : Complete Hotwire Turbo framework documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; : Handbook and reference sections covering Turbo Drive, Frames, and Streams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case&lt;/strong&gt; : Modern Rails frontend development with Hotwire&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Stimulus JavaScript Framework Documentation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt; : Full Stimulus documentation for building interactive components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; : Handbook tutorials and API reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case&lt;/strong&gt; : JavaScript interactions in Rails applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Kamal Deployment Documentation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt; : Comprehensive Kamal deployment tool documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage&lt;/strong&gt; : Installation, configuration, commands, and deployment strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case&lt;/strong&gt; : Modern Rails application deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. Custom Documentation Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt; : Import and access your own markdown documentation files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt; : Project-specific guides, API documentation, team standards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use case&lt;/strong&gt; : Maintaining consistent project documentation across AI sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Documentation Resources
&lt;/h2&gt;

&lt;p&gt;Setting up documentation is straightforward with the dedicated download tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Download official framework documentation
rails-mcp-server-download-resources rails
rails-mcp-server-download-resources turbo
rails-mcp-server-download-resources stimulus
rails-mcp-server-download-resources kamal

# Import your custom documentation
rails-mcp-server-download-resources --file /path/to/your/docs/

# Force update existing resources
rails-mcp-server-download-resources --force rails

# Verbose output for troubleshooting
rails-mcp-server-download-resources --verbose turbo

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

&lt;/div&gt;



&lt;p&gt;Once downloaded, you can access guides naturally in conversation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can you load the Rails getting started guide?
Show me the Turbo Frames documentation.
I need help with Stimulus controllers - can you show me that guide?
Load the Kamal deployment guide so I can understand the process.

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  MCP Proxy Integration for Better Compatibility
&lt;/h2&gt;

&lt;p&gt;Building on the &lt;a href="https://mariochavez.io/desarrollo/2025/04/20/rails-mcp-server-with-sse/" rel="noopener noreferrer"&gt;previous release with HTTP SSE support&lt;/a&gt;, this update includes comprehensive documentation for using &lt;strong&gt;MCP proxies&lt;/strong&gt; to address Ruby version manager compatibility issues that many developers face with Claude Desktop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up MCP Proxy for Enhanced Compatibility
&lt;/h3&gt;

&lt;p&gt;For users who want to leverage the HTTP/SSE capabilities or work around Ruby version manager issues:&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Start Rails MCP Server in HTTP Mode
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-server --mode http

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Install and Configure MCP Proxy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install the Node.js based MCP proxy
npm install -g mcp-remote

# Run the proxy, pointing to your running Rails MCP Server
npx mcp-remote http://localhost:6029/mcp/sse

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Configure Claude Desktop for Proxy Usage
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mcpServers": {
    "railsMcpServer": {
      "command": "npx",
      "args": ["mcp-remote", "http://localhost:6029/mcp/sse"]
    }
  }
}

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

&lt;/div&gt;



&lt;p&gt;This setup allows STDIO-only clients to communicate through the proxy while benefiting from HTTP/SSE capabilities and avoiding Ruby version conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails MCP + Neovim MCP Integration Workflow
&lt;/h2&gt;

&lt;p&gt;I’ve significantly improved my development workflow by combining the Rails MCP Server with the &lt;a href="https://github.com/maquina-app/nvim-mcp-server" rel="noopener noreferrer"&gt;&lt;strong&gt;Neovim MCP Server&lt;/strong&gt;&lt;/a&gt;. This combination creates a powerful development environment where I can seamlessly work with both my Rails codebase and my active editing context.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Two-Server Development Approach
&lt;/h3&gt;

&lt;p&gt;Here’s how I use both MCP servers together for enhanced productivity:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails MCP Server handles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project structure analysis and exploration&lt;/li&gt;
&lt;li&gt;Database schema exploration and relationships&lt;/li&gt;
&lt;li&gt;Route inspection and API endpoint analysis&lt;/li&gt;
&lt;li&gt;Model relationship analysis and validations&lt;/li&gt;
&lt;li&gt;Access to comprehensive Rails ecosystem documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Neovim MCP Server provides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time access to currently open buffers&lt;/li&gt;
&lt;li&gt;Context about active editing sessions&lt;/li&gt;
&lt;li&gt;Bridge between editor state and AI assistance&lt;/li&gt;
&lt;li&gt;Live file content without manual specification&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Optimized Development Session Workflow
&lt;/h3&gt;

&lt;p&gt;When I start a development session, I begin with context gathering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Switch to project my_rails_app, don't analyze anything yet.
Show me which files I have open in my "my_rails_app" Neovim instance.

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

&lt;/div&gt;



&lt;p&gt;This gives me both project context from Rails MCP and my current editing context from Neovim MCP. Then I can work more efficiently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load the User model from Rails and also get the user_controller.rb that I have open in Neovim.
Can you also load the Rails Active Record associations guide for reference?

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits of the Dual-Server Approach
&lt;/h3&gt;

&lt;p&gt;This approach allows me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Work with live context&lt;/strong&gt; : See exactly what I’m editing without manually specifying files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access comprehensive documentation&lt;/strong&gt; : Get official guides loaded instantly for reference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain project focus&lt;/strong&gt; : Switch between Rails projects while keeping editor context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid manual file management&lt;/strong&gt; : Let the tools handle finding and loading the right files&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Managing Token Usage Effectively
&lt;/h3&gt;

&lt;p&gt;I’ve refined my prompting strategy to manage Claude’s context window more effectively:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session Start:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Switch to project my_finances, don't analyze the project or any loaded files until you are told to.

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Loading Files:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load the following files from my Neovim buffers, do not analyze or try to load dependencies unless you are told to.

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Session Continuation:&lt;/strong&gt; When reaching conversation limits, I summarize and continue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summarize this chat. Describe the goal and add relevant information about what was done. Include the project name and list any files loaded, generated or shared.

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Documentation as a Shared Resource
&lt;/h2&gt;

&lt;p&gt;The resource system transforms documentation from a static reference into a dynamic, shared asset. When I load Rails validation documentation in one conversation, another AI session can access the same exact information. This consistency is particularly valuable when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Training team members&lt;/strong&gt; : Everyone gets the same documentation regardless of which AI tool they use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining project standards&lt;/strong&gt; : Documentation stays consistent across different development sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working with multiple LLM providers&lt;/strong&gt; : The same Rails 8.0.2 guides work identically with Claude, ChatGPT, or other MCP-compatible clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensuring version alignment&lt;/strong&gt; : Projects can specify exact documentation versions to match their Rails installation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of switching to browser tabs or searching through docs, I can load exactly the guide I need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm working on validations - can you load the Rails validations guide?
Show me the Turbo Streams reference for this real-time update feature.
Load my custom API documentation so we can follow the established patterns.

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

&lt;/div&gt;



&lt;p&gt;The system handles both official framework documentation and custom project documentation seamlessly, making it a true companion for Rails development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Management and Best Practices
&lt;/h2&gt;

&lt;p&gt;Managing resources is straightforward with clear commands and automatic organization:&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Organization Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Download once, use everywhere&lt;/strong&gt; : Resources are stored locally and available across all conversations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic updates&lt;/strong&gt; : Re-download to get the latest documentation versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom integration&lt;/strong&gt; : Import your project-specific documentation alongside official guides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent organization&lt;/strong&gt; : Resources are categorized and easily discoverable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resource Storage and Management
&lt;/h3&gt;

&lt;p&gt;Resources are stored in platform-specific locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt; : &lt;code&gt;~/.config/rails-mcp/resources/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt; : &lt;code&gt;%APPDATA%\rails-mcp\resources\&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each resource category maintains a manifest file tracking downloaded guides, versions, and update timestamps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Optimization Tips
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Selective downloads&lt;/strong&gt; : Only download resources you actively use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular updates&lt;/strong&gt; : Keep documentation current with framework releases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom documentation&lt;/strong&gt; : Organize project-specific guides with descriptive filenames&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version alignment&lt;/strong&gt; : Match documentation versions to your Rails installation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For complete details on setting up and using resources, I’ve created a comprehensive &lt;a href="//docs/RESOURCES.md"&gt;Resources Guide&lt;/a&gt; that covers everything from basic setup to advanced usage patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I update Rails MCP Server documentation resources?
&lt;/h3&gt;

&lt;p&gt;Re-run the download command for any resource category to get the latest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-server-download-resources rails

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Can I use Rails MCP Server with other AI clients besides Claude Desktop?
&lt;/h3&gt;

&lt;p&gt;Yes! The MCP proxy setup allows compatibility with any MCP-compatible client. The HTTP/SSE endpoints work with various LLM providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s the difference between Rails MCP Server and Neovim MCP Server?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rails MCP Server&lt;/strong&gt; : Analyzes Rails project structure, database, routes, and provides Rails ecosystem documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neovim MCP Server&lt;/strong&gt; : Provides access to currently open files in your editor sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How do I troubleshoot Ruby version manager issues with Claude Desktop?
&lt;/h3&gt;

&lt;p&gt;Use the MCP proxy setup to bypass Ruby version conflicts, or create a symbolic link as described in the &lt;a href="https://github.com/maquina-app/rails-mcp-server#ruby-version-manager-users" rel="noopener noreferrer"&gt;Ruby Version Manager Users section&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I import custom documentation alongside official Rails guides?
&lt;/h3&gt;

&lt;p&gt;Yes! Use the &lt;code&gt;--file&lt;/code&gt; option to import your own markdown files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-server-download-resources --file /path/to/your/docs/

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;This enhanced Rails MCP Server has become an integral part of my Rails development workflow. The combination of comprehensive documentation access, improved integration options, and seamless editor integration through the Neovim MCP Server creates a development environment where AI assistance feels natural and productive.&lt;/p&gt;

&lt;p&gt;The ability to instantly access official documentation, work with live editing context, and maintain project focus has significantly improved my development velocity and the quality of my AI-assisted coding sessions.&lt;/p&gt;

&lt;p&gt;I encourage you to try both servers together and explore the new resource system. The documentation access alone makes this upgrade worthwhile, and the improved integration options ensure it works well regardless of your development setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source code and detailed setup instructions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/maquina-app/rails-mcp-server" rel="noopener noreferrer"&gt;Rails MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/maquina-app/nvim-mcp-server" rel="noopener noreferrer"&gt;Neovim MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Related Posts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mariochavez.io/desarrollo/2025/04/20/rails-mcp-server-with-sse/" rel="noopener noreferrer"&gt;Rails MCP Server with HTTP Server-Sent Events support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mariochavez.io/desarrollo/2024/12/15/model-context-protocol-development/" rel="noopener noreferrer"&gt;Model Context Protocol: Enhancing AI Development Workflows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;The Rails MCP Server continues to evolve based on real-world usage. If you have suggestions or encounter issues, please feel free to open an issue or contribute to the project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>railsmcpserver</category>
      <category>modelcontextprotocol</category>
      <category>railsdocumentation</category>
      <category>aidevelopment</category>
    </item>
    <item>
      <title>Rails MCP Server with HTTP Server-Sent Events support</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Sun, 20 Apr 2025 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/rails-mcp-server-with-http-server-sent-events-support-38ik</link>
      <guid>https://dev.to/mario_chavez/rails-mcp-server-with-http-server-sent-events-support-38ik</guid>
      <description>&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%2Fhia6bdz8efwpl7qxvdza.jpg" 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%2Fhia6bdz8efwpl7qxvdza.jpg" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Rails MCP Server 1.1.0 with HTTP SSE support
&lt;/h1&gt;

&lt;p&gt;I’m excited to announce this new version of Rails MCP Server, version 1.1.0! This release introduces significant internal refactoring through the integration of the &lt;a href="https://github.com/jlowin/fastmcp" rel="noopener noreferrer"&gt;FastMCP gem&lt;/a&gt;, providing better code organization and adding support for HTTP Server-Sent Events (SSE).&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s New in v1.1.0
&lt;/h2&gt;

&lt;h3&gt;
  
  
  FastMCP Integration
&lt;/h3&gt;

&lt;p&gt;The most substantial change in this release is the internal refactoring to use the FastMCP gem. This refactoring has provided several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved Code Organization&lt;/strong&gt; : I’ve made the codebase more modular and easier to maintain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved tools&lt;/strong&gt; : I’ve reviewed the code for each tool to fix issues with edge cases and to make the tools honor &lt;code&gt;.gitignore&lt;/code&gt; file when scanning the codebase for files. This is important for large codebases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Added two new tools&lt;/strong&gt; : There are two new tools, analyze controller and views and analyze environment configuration. Analyze controllers and views finds all the relationships with the routes, views, stimulus controllers and controller actions in detail. Analyze environment configuration compares the configuration for inconsistencies between environments, missing configuration, and possible security issues, &lt;em&gt;it does not leak keys or passwords to the LLM&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTTP Server-Sent Events (SSE) Support
&lt;/h3&gt;

&lt;p&gt;With this release, I’ve added HTTP Server-Sent Events (SSE) support for real-time communication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support for other clients:&lt;/strong&gt; Clients with support for HTTP Server-Sent Events (SSE) can now interact with the Rails MCP Server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using HTTP Mode with SSE
&lt;/h2&gt;

&lt;p&gt;The Rails MCP Server can now run in two distinct modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;STDIO mode (default)&lt;/strong&gt;: Communicates over standard input/output for direct integration with clients like Claude Desktop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP mode&lt;/strong&gt; : Runs as an HTTP server with JSON-RPC and Server-Sent Events (SSE) endpoints.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To start in HTTP mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Start in HTTP mode on the default port (6029)
rails-mcp-server --mode http

# Start in HTTP mode on a custom port
rails-mcp-server --mode http -p 8080

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

&lt;/div&gt;



&lt;p&gt;When running in HTTP mode, the server provides two endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON-RPC endpoint: &lt;code&gt;http://localhost:&amp;lt;port&amp;gt;/mcp/messages&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;SSE endpoint: &lt;code&gt;http://localhost:&amp;lt;port&amp;gt;/mcp/sse&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables integration with web applications, dashboard tools, and other systems that need to communicate with the Rails MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading from Earlier Versions
&lt;/h2&gt;

&lt;p&gt;Upgrading to v1.1.0 should be straightforward for most users. The API remains backward compatible, so existing code should continue to work without modification. To upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install 'rails-mcp-server' # This installs the current version 1.1.0

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Under the Hood
&lt;/h2&gt;

&lt;p&gt;I’ve streamlined the codebase significantly by refactoring to use FastMCP. The FastMCP gem provides a robust implementation of the MCP protocol, handling message encoding/decoding, connection management, and event dispatching.&lt;/p&gt;

&lt;p&gt;I built the SSE implementation on top of Rack and integrated it seamlessly with FastMCP’s event system. This allows for efficient real-time updates and event streaming while maintaining compatibility with the Model Context Protocol standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with MCP Inspector
&lt;/h2&gt;

&lt;p&gt;The Rails MCP Server v1.1.0 works seamlessly with MCP Inspector, making it easier than ever to test and debug your MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install and run MCP Inspector with your Rails MCP Server
npm -g install @modelcontextprotocol/inspector

# Run the inspector and connect to the Rails MCP Server via STDIO or HTTP SSE
npx @modelcontextprotocol/inspector

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I use the Rails MCP Server
&lt;/h2&gt;

&lt;p&gt;I use the server with Claude Desktop from Anthropic. It does not yet support HTTP SSE; it can only communicate via STDIO. Adding HTTP SSE facilitates people using other clients to use the server easily, as using the server with Claude Desktop is not simple if you use a Ruby version manager.&lt;/p&gt;

&lt;p&gt;Claude Desktop starts a process to run the server but with a &lt;em&gt;no login&lt;/em&gt; session, which means that your Ruby version manager is not initialized and macOS uses the bundled 2.6 Ruby version. There is no real fix for this situation, just a workaround. Please look at the &lt;a href="https://github.com/maquina-app/rails-mcp-server?tab=readme-ov-file#ruby-version-manager-users" rel="noopener noreferrer"&gt;README&lt;/a&gt; to see how to make it work for &lt;strong&gt;rbenv&lt;/strong&gt; ; a similar solution is required for other Ruby managers.&lt;/p&gt;

&lt;p&gt;When I start a session with Claude, my first prompt is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Switch to project my_finances, don't analyze the project or any loaded files until you are told to.

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

&lt;/div&gt;



&lt;p&gt;I do this because the Sonnet model can start very creatively and begin analyzing all possible files in the project, consuming its quota.&lt;/p&gt;

&lt;p&gt;I work on a feature per chat, so I load only the files that are related to the work that I want to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load the following file or files, do not analyze or try to load dependencies unless you are told to.

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

&lt;/div&gt;



&lt;p&gt;Again, here I pass the list of files to load but I stop Sonnet from trying to analyze without knowing yet what I want to do. My next prompt is to explain what I want to do, and then I ask it to analyze the files and maybe load more if needed.&lt;/p&gt;

&lt;p&gt;While working with Claude Desktop, it’s easy to reach the chat quota. I’m prepared at the end to continue with the feature if it’s not done yet. I use the following prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Summarize this chat. Describe what is the goal and add any relevant information about what was done. Include the project name and the list with a brief description of any file loaded, generated or shared with you.

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

&lt;/div&gt;



&lt;p&gt;Then I copy the output and start a new chat with the following prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here is a summary of a previous chat. Switch to the project if a given name exists. Don't load any reference files until you are told to. Also, don't analyze the project or any loaded files until you are told to.
Just comprehend in a general way what was done before.

If you are told to generate data, always generate synthetic data unless you are told otherwise.

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

&lt;/div&gt;



&lt;p&gt;And continue with the cycle. Claude Desktop doesn’t write to the disk; I always review the generated code and copy it manually. I modify the code when it makes sense and share it back with Claude.&lt;/p&gt;

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

&lt;p&gt;I believe this release of Rails MCP Server v1.1.0 represents a significant step forward in terms of code quality and feature set. The integration with FastMCP provides a more maintainable foundation, while the addition of SSE support expands the communication options available to applications using the server.&lt;/p&gt;

&lt;p&gt;I encourage you to upgrade to this latest version and explore the new capabilities it offers. As always, I welcome feedback and contributions from the community.&lt;/p&gt;

&lt;p&gt;Source code is available at &lt;a href="https://github.com/maquina-app/rails-mcp-server" rel="noopener noreferrer"&gt;https://github.com/maquina-app/rails-mcp-server&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>opensource</category>
      <category>news</category>
    </item>
    <item>
      <title>Rails MCP Server - Enhancing AI-Assisted Development</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Fri, 21 Mar 2025 18:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/rails-mcp-server-enhancing-ai-assisted-development-32j8</link>
      <guid>https://dev.to/mario_chavez/rails-mcp-server-enhancing-ai-assisted-development-32j8</guid>
      <description>&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%2F16yarjdnbm66e7n368e4.jpeg" 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%2F16yarjdnbm66e7n368e4.jpeg" alt="Image description" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI has changed the way we write and deploy code to production. LLMs are getting better and better at understanding and also at generating code. It started with chats, where you can make questions and share code, then evolved to suggest auto-completion inside the editor and later the inclusion of chats in the same editor.&lt;/p&gt;

&lt;p&gt;Nowadays we have editors that are created specifically to understand the code base and write code with just prompts, and a button to accept the changes and update the code base.&lt;/p&gt;

&lt;p&gt;But, there is a shortcoming when working with AI, mainly because of the bias of the LLMs that are better at generating code for the JavaScript ecosystem, and not that good with code in almost every other programming language.&lt;/p&gt;

&lt;p&gt;Still, AI is great for generating boilerplate code or for green field projects. Moreover, the possibility to extend LLM model knowledge with techniques and protocols that help with the time to deploy, adding significant value, even if it requires more iterations to help the LLM get the correct context.&lt;/p&gt;

&lt;p&gt;In my case, I can’t take advantage of the new IDEs that integrate AI into their core; I don’t use IDEs, I use Neovim. There are tools to make Neovim work like those IDEs, but still, that is something that I don’t want to do.&lt;/p&gt;

&lt;p&gt;With my way of work, I prefer to use Claude Desktop with the Sonnets models. My workflow is simple; I create projects with a prompt that provides general instructions for the model, something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are an expert Ruby on Rails developer. Help me to write professional and well-crafted code; don’t explain it unless required. You are familiar with TailwindCSS, Turbo Rails, Hotwire, and Stimulus. Also, don’t write tests unless you are required. All tests should be with minitest in unit test style.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, in the knowledge base of the project, I add documentation and texts that provide context in greater detail, along with code style, what to do, and what not to do.&lt;/p&gt;

&lt;p&gt;Once my projects are configured, then I can start doing some work. I mainly work on brownfield projects with large code bases, so sharing the whole code base is not an option. Before I start the task at hand, I collect fragments of code that can be relevant to what I need to accomplish.&lt;/p&gt;

&lt;p&gt;I even have a bash script that can copy the full content of a file along with the file path. The AI generates new code or changes with my instructions since the AI can’t write directly to my files, this allows me to review the code and assess if it will work as expected and if the code style is consistent with my code base.&lt;/p&gt;

&lt;p&gt;If I spot something that I can write better or might produce an error, I don’t ask the AI to fix it. I just copy what makes sense to Neovim, update the code, and copy it back to the AI so it can have the latest version. I work this way because I don’t Vibe code. I need to keep some quality in the code, and I also need to fully understand what is happening because that code powers companies and their customers.&lt;/p&gt;

&lt;p&gt;This workflow works great so far, but when I learned about the Model Context Protocol or MCP, I wondered how I might be able to use it to improve how I share code with Claude Desktop. So, I embarked on a quest to create a Rails MCP server; actually, Claude Desktop wrote about 80% of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Rails MCP Server can do?
&lt;/h2&gt;

&lt;p&gt;The Rails MCP Server provides a set of tools that allow Claude to interact directly with my Rails projects. This enables a more seamless workflow when I need AI assistance with my codebase. Here are the capabilities:&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Navigation
&lt;/h3&gt;

&lt;p&gt;I can easily switch between different Rails projects using the &lt;code&gt;switch_project&lt;/code&gt; tool. This is particularly useful when I’m working on multiple applications and need Claude’s assistance with different codebases throughout my workday.&lt;/p&gt;

&lt;p&gt;The Rails MCP Server follows the XDG Base Directory Specification for configuration files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On macOS: &lt;code&gt;$XDG_CONFIG_HOME/rails-mcp&lt;/code&gt; or &lt;code&gt;~/.config/rails-mcp&lt;/code&gt; if XDG_CONFIG_HOME is not set&lt;/li&gt;
&lt;li&gt;On Windows: &lt;code&gt;%APPDATA%\rails-mcp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server automatically creates these directories and an empty &lt;code&gt;projects.yml&lt;/code&gt; file on the first run. To configure my projects, I edit the &lt;code&gt;projects.yml&lt;/code&gt; file to include my Rails projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;store: "~/projects/store"
blog: "~/projects/rails-blog"

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

&lt;/div&gt;



&lt;p&gt;Each key in the YAML file is a project name (used with the &lt;code&gt;switch_project&lt;/code&gt; tool), and each value is the path to the project directory.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can you switch to the ‘store’ project so we can explore it?”&lt;/li&gt;
&lt;li&gt;“I’d like to analyze my ‘blog’ application. Please switch to that project first.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Project Overview
&lt;/h3&gt;

&lt;p&gt;With the &lt;code&gt;get_project_info&lt;/code&gt; tool, Claude can provide me with a comprehensive overview of my Rails application, including its version, directory structure, and configuration. This gives Claude the necessary context to understand my application’s architecture before diving into specific tasks.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Now that we’re in the blog project, can you give me an overview of the project structure and Rails version?”&lt;/li&gt;
&lt;li&gt;“Tell me about this Rails application. What version is it running and how is it organized?”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  File Exploration
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;list_files&lt;/code&gt; tool allows Claude to scan my project directories and locate specific files. I can filter by directory path or file pattern, making it easy to find what I need.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can you list all the model files in this project?”&lt;/li&gt;
&lt;li&gt;“Show me all the controller files in the app/controllers directory.”&lt;/li&gt;
&lt;li&gt;“List all the JavaScript files in the app/javascript directory.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Examination
&lt;/h3&gt;

&lt;p&gt;Using the &lt;code&gt;get_file&lt;/code&gt; tool, Claude can retrieve the complete content of any file in my project with syntax highlighting. This eliminates my need to manually copy and paste code snippets, streamlining my workflow.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can you show me the content of the User model file?”&lt;/li&gt;
&lt;li&gt;“I need to see what’s in app/controllers/products_controller.rb. Can you retrieve that file?”&lt;/li&gt;
&lt;li&gt;“Please show me the application.rb file so I can check the configuration settings.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Route Analysis
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;get_routes&lt;/code&gt; tool provides access to all HTTP routes defined in my Rails application. Claude can analyze the routing structure to help me understand API endpoints, URL patterns, and controller actions.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can you show me all the routes defined in this application?”&lt;/li&gt;
&lt;li&gt;“I need to understand the API endpoints available in this project. Can you list the routes?”&lt;/li&gt;
&lt;li&gt;“Show me the routing configuration for this Rails app so I can see how the URLs are structured.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Model Inspection
&lt;/h3&gt;

&lt;p&gt;With the &lt;code&gt;get_models&lt;/code&gt; tool, Claude can examine my Active Record models in detail. It can list all models or provide comprehensive information about a specific model, including its schema, associations, and code implementation.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can you list all the models in this Rails project?”&lt;/li&gt;
&lt;li&gt;“I’d like to understand the User model in detail. Can you show me its schema, associations, and code?”&lt;/li&gt;
&lt;li&gt;“Show me the Product model’s definition, including its relationships with other models.”&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Schema Investigation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;get_schema&lt;/code&gt; tool allows Claude to access my database schema information. It can show the complete database structure or focus on a specific table, displaying columns, data types, constraints, and relationships.&lt;/p&gt;

&lt;p&gt;Example prompts I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Can you show me the complete database schema for this Rails application?”&lt;/li&gt;
&lt;li&gt;“I’d like to see the structure of the users table. Can you retrieve that schema information?”&lt;/li&gt;
&lt;li&gt;“Show me the columns and their data types in the products table.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Rails MCP Server doesn’t just save me time—it enhances Claude’s understanding of my codebase, leading to more accurate and relevant assistance. Whether I’m troubleshooting an issue, implementing a new feature, or refactoring existing code, Claude can now access the context it needs to provide helpful suggestions tailored to my specific project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Installing the Rails MCP Server is straightforward. I start by installing the gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install rails-mcp-server

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

&lt;/div&gt;



&lt;p&gt;After installation, the &lt;code&gt;rails-mcp-server&lt;/code&gt; and &lt;code&gt;rails-mcp-setup-claude&lt;/code&gt; executables are available in my PATH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Desktop Integration
&lt;/h2&gt;

&lt;p&gt;I run the setup script which automatically configures Claude Desktop and sets up the proper XDG-compliant directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails-mcp-setup-claude

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

&lt;/div&gt;



&lt;p&gt;The script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates the appropriate config directory for my platform&lt;/li&gt;
&lt;li&gt;Creates an empty &lt;code&gt;projects.yml&lt;/code&gt; file if it doesn’t exist&lt;/li&gt;
&lt;li&gt;Updates my Claude Desktop configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After running the script, I restart Claude Desktop to apply the changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ruby Version Manager Users
&lt;/h3&gt;

&lt;p&gt;Claude Desktop launches the MCP server using my system’s default Ruby environment, bypassing version manager initialization (e.g., rbenv, RVM). The MCP server needs to use the same Ruby version where it was installed, as MCP server startup failures can occur when using an incompatible Ruby version.&lt;/p&gt;

&lt;p&gt;Since I use a Ruby version manager (rbenv), I create a symbolic link to my Ruby shim to ensure the correct version is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ln -s /home/mario/.rbenv/shims/ruby /usr/local/bin/ruby

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

&lt;/div&gt;



&lt;p&gt;I replace the path with my actual path for the Ruby shim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you want access to the source code, here is the link &lt;a href="https://github.com/maquina-app/rails-mcp-server" rel="noopener noreferrer"&gt;https://github.com/maquina-app/rails-mcp-server&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Rails MCP Server is a significant step forward in how I interact with AI assistants like Claude as a Ruby on Rails developer. I’ve learned that AI outputs are only as good as the context I provide—when Claude has access to the right files and project structures, the quality and accuracy of its suggestions improve dramatically. Instead of manually copying snippets or trying to explain complex relationships between models, I can simply ask Claude to examine the relevant files directly, maintaining control over my development process while enhancing my existing Neovim workflow with contextually aware AI assistance.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Nobuild with Rails and Importmap</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Thu, 28 Nov 2024 18:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/nobuild-with-rails-and-importmap-37m</link>
      <guid>https://dev.to/mario_chavez/nobuild-with-rails-and-importmap-37m</guid>
      <description>&lt;p&gt;The latest versions of Ruby on Rails have focused on simplicity across different aspects of the framework, accompanied by the promise to return to the “one-man framework” (where a single developer can effectively build and maintain an entire application).&lt;/p&gt;

&lt;p&gt;Importmap Rails library is based on the principle that modern web browsers have caught up with the ECMAScript specification and can interpret ES Modules (ESM). As a web standard, Importmap allows you to control how JavaScript modules are resolved in the browser and manage dependencies and versions without the need to transpile or bundle the code sent to the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Importmap Web Standard Works
&lt;/h2&gt;

&lt;p&gt;It all starts with a &lt;code&gt;script&lt;/code&gt; tag of type &lt;code&gt;importmap&lt;/code&gt; defined in your application’s main layout or web page. Inside this tag, a JSON object defines aliases and their corresponding paths to the source code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script type="importmap"&amp;gt;
  {
    "imports": {
      "application": "/assets/application.js",
      "local-time": "https://cdn.jsdelivr.net/npm/local-time@3.0.2/app/assets/javascripts/local-time.es2017-esm.min.js",
      "utils": "/assets/utils.js"
    }
  }
&amp;lt;/script&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;In the same map, you can mix library paths pointing to a CDN or using local resources. To use libraries from this map, reference the alias name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- Below the importmap script --&amp;gt;
&amp;lt;script type="module"&amp;gt;import "application"&amp;lt;/script&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;And in your &lt;em&gt;application.js&lt;/em&gt;, import needed dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// application.js

import LocalTime from "local-time";
LocalTime.start();

import "utils";

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

&lt;/div&gt;



&lt;p&gt;Importmap support is present in browsers Chrome 89+, Safari 16.4+, Firefox 108+, and Edge 89+. For older browsers, include a polyfill:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script
  async
  src="https://ga.jspm.io/npm:es-module-shims@1.10.1/dist/es-module-shims.js"
&amp;gt;&amp;lt;/script&amp;gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Importmap Works in Ruby on Rails
&lt;/h2&gt;

&lt;p&gt;Importmap functionality in Ruby on Rails follows the same standard described above and offers an easy way to create maps and version files. Using a web application named &lt;strong&gt;heroImage&lt;/strong&gt; as an example (source code available on &lt;a href="https://github.com/mariochavez/inspiration" rel="noopener noreferrer"&gt;Github&lt;/a&gt;), let’s explore the implementation.&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%2F5jbhr4s5i4n1007blwe9.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%2F5jbhr4s5i4n1007blwe9.png" alt="heroImage website" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you create a new Rails 8 application, the &lt;strong&gt;importmap-rails&lt;/strong&gt; gem is added and installed by default. A file &lt;em&gt;config/importmap.rb&lt;/em&gt; is created where you can &lt;em&gt;pin&lt;/em&gt; the JavaScript code needed in your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin "application"

pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"

pin_all_from "app/javascript/controllers", under: "controllers", preload: false

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pin&lt;/code&gt; keyword takes up to three arguments. The first one is required, as it is the alias of the JavaScript code. &lt;code&gt;pin "application"&lt;/code&gt; is a shortcut for file &lt;em&gt;application.js&lt;/em&gt; with alias &lt;em&gt;application&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin "application", to: "application.js"

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

&lt;/div&gt;



&lt;p&gt;When alias and file names differ, use the keyword &lt;code&gt;to:&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin "@hotwired/turbo-rails", to: "turbo.min.js"

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pin_all_from&lt;/code&gt; keyword helps reference multiple files at once. The first argument is the path where the JavaScript files are located, and the &lt;code&gt;under:&lt;/code&gt; argument prefixes the alias for each file. The generated alias uses the &lt;em&gt;under&lt;/em&gt; prefix and the file name, like &lt;code&gt;controllers/alert-controller&lt;/code&gt; for &lt;code&gt;alert_controller.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;To visualize the Importmap JSON file, execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/importmap json

{
  "imports": {
    "@hotwired/turbo-rails": "//127.0.0.1:4700/assets/turbo.min-fae85750.js",
    "@hotwired/stimulus": "//127.0.0.1:4700/assets/stimulus.min-4b1e420e.js",
    "@hotwired/stimulus-loading": "//127.0.0.1:4700/assets/stimulus-loading-1fc53fe7.js",
    "application": "//127.0.0.1:4700/assets/application-b1902c45.js",
    "controllers/application": "//127.0.0.1:4700/assets/controllers/application-fab0f35b.js",
    "controllers": "//127.0.0.1:4700/assets/controllers/index-c3f5d3c4.js",
    "controllers/alert_controller": "//127.0.0.1:4700/assets/controllers/alert_controller-caf203bf.js",
    "controllers/file_controller": "//127.0.0.1:4700/assets/controllers/file_controller-5da5fdc5.js",
    "controllers/notifications_controller": "//127.0.0.1:4700/assets/controllers/notifications_controller-88e2cc65.js",
    "controllers/service_worker_controller": "//127.0.0.1:4700/assets/controllers/service_worker_controller-ad4a24d3.js",
    "controllers/share_controller": "//127.0.0.1:4700/assets/controllers/share_controller-fe28ed00.js"
  }
}

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

&lt;/div&gt;



&lt;p&gt;Rails resolves all JavaScript through the &lt;a href="https://github.com/rails/propshaft" rel="noopener noreferrer"&gt;Propshaft&lt;/a&gt; gem, which resolves the physical path of the JavaScript code, maps to the &lt;em&gt;/assets&lt;/em&gt; web path, and adds the digest to each file for better caching and invalidations.&lt;/p&gt;

&lt;p&gt;Propshaft discovers physical paths from the asset’s configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rails.application.config.assets.paths

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

&lt;/div&gt;



&lt;p&gt;Ensure your files exist in any of the registered paths or add your own path to be discovered by Propshaft and Importmap.&lt;/p&gt;

&lt;p&gt;Importmap in Rails allows you to specify how the browser should load JavaScript files. There are two options: &lt;code&gt;preload&lt;/code&gt; (default) and no preload. Preload tells the browser to download files as soon as possible. Importmap generates a link tag with &lt;code&gt;rel="modulepreload"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link
  rel="modulepreload"
  href="https://heroimage.co/assets/turbo.min-fae85750.js"
/&amp;gt;
&amp;lt;link
  rel="modulepreload"
  href="https://heroimage.co/assets/stimulus-loading-1fc53fe7.js"
/&amp;gt;
&amp;lt;link
  rel="modulepreload"
  href="https://heroimage.co/assets/stimulus-loading-1fc53fe7.js"
/&amp;gt;
&amp;lt;link
  rel="modulepreload"
  href="https://heroimage.co/assets/application-b1902c45.js"
/&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;If you set the &lt;code&gt;preload&lt;/code&gt; argument to &lt;code&gt;false&lt;/code&gt;, the link tag is not generated and the browser downloads the file when needed.&lt;/p&gt;

&lt;p&gt;With Rails’ Importmap, you can also &lt;em&gt;pin&lt;/em&gt; JavaScript code from a CDN using the &lt;code&gt;to:&lt;/code&gt; argument for the URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin "local-time", to: "https://cdn.jsdelivr.net/npm/local-time@3.0.2/app/assets/javascripts/local-time.es2017-esm.min.js"

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

&lt;/div&gt;



&lt;p&gt;The Importmap includes a CLI to &lt;strong&gt;pin&lt;/strong&gt; or &lt;strong&gt;unpin&lt;/strong&gt; JavaScript code into &lt;em&gt;config/importmap.rb&lt;/em&gt; file. It also includes commands to update, audit, and inspect versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/importmap --help
Commands:
  importmap audit # Run a security audit
  importmap help [COMMAND] # Describe available commands or one specific command
  importmap json # Show the full importmap in json
  importmap outdated # Check for outdated packages
  importmap packages # Print out packages with version numbers
  importmap pin [*PACKAGES] # Pin new packages
  importmap unpin [*PACKAGES] # Unpin existing packages
  importmap update # Update outdated package pins

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

&lt;/div&gt;



&lt;p&gt;When using the &lt;em&gt;pin&lt;/em&gt; command for a JavaScript package, instead of setting the &lt;code&gt;to:&lt;/code&gt; argument to the CDN, Importmap resolves package dependencies and downloads the package and dependencies to &lt;em&gt;vendor/javascript&lt;/em&gt;, allowing the Rails application to serve those files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bin/importmap pin local-time
Pinning "local-time" to vendor/javascript/local-time.js via download from https://ga.jspm.io/npm:local-time@3.0.2/app/assets/javascripts/local-time.es2017-esm.js


# config/importmap.rb
...
pin "local-time" # @3.0.2

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

&lt;/div&gt;



&lt;p&gt;This approach works well when your package has simple dependencies or well-defined dependencies in the JavaScript package. If that’s not the case, it becomes challenging to use with Importmap vendoring the code at &lt;em&gt;vendor/javascript&lt;/em&gt;. It might work with the URL and manual dependency addition, or you can &lt;a href="https://mariochavez.io/desarrollo/2024/08/09/no-build-javascript-rails-importmap/" rel="noopener noreferrer"&gt;tweak the vendored code&lt;/a&gt; to make it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Work with Rails Gems - Engines - and Importmap?
&lt;/h2&gt;

&lt;p&gt;There are two approaches to creating Ruby on Rails gems compatible with Importmap. The first approach allows your gem to provide JavaScript code, which you can choose to pin in the Importmap configuration. This is how the &lt;strong&gt;turbo-rails&lt;/strong&gt; and &lt;strong&gt;stimulus-rails&lt;/strong&gt; gems are implemented.&lt;/p&gt;

&lt;p&gt;Place your JavaScript code in the &lt;em&gt;app/assets/javascripts&lt;/em&gt; folder of your gem. You may need an additional process that minifies the JavaScript files and generates JavaScript map files. Then, inside the &lt;code&gt;Engine&lt;/code&gt; class, define an &lt;code&gt;initializer&lt;/code&gt; hook to declare your JavaScript code with Propshaft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module MyEngine
  class Engine &amp;lt; ::Rails::Engine
    # Additional code
    PRECOMPILE_ASSETS = %w( my_javascript.js my_javascript.min.js my_javascript.min.js.map ).freeze

    initializer "my_engine.assets" do
      if Rails.application.config.respond_to?(:assets)
        Rails.application.config.assets.precompile += PRECOMPILE_ASSETS
      end
    end
  end
end

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

&lt;/div&gt;



&lt;p&gt;The second option uses an Importmap configuration file. If your engine has its layout template and the views are isolated from the host application, and the engine doesn’t need to share the JavaScript code with the host application, you can create an Importmap configuration file at &lt;em&gt;config/importmap.rb&lt;/em&gt;, set your pins, place your JavaScript code at &lt;em&gt;app/javascript&lt;/em&gt;, and configure the engine with an initializer.&lt;/p&gt;

&lt;p&gt;Open your &lt;code&gt;engine.rb&lt;/code&gt; Ruby file and add the Importmap configuration file and a sweeper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initializer "my-engine.importmap", after: "importmap" do |app|
  MyEngine.importmap.draw(root.join("config/importmap.rb"))
  MyEngine.importmap.cache_sweeper(watches: root.join("app/javascript"))

  ActiveSupport.on_load(:action_controller_base) do
    before_action { MyEngine.importmap.cache_sweeper.execute_if_updated }
  end
end

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

&lt;/div&gt;



&lt;p&gt;Specify the Importmap to use in your engine’s layout template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= javascript_importmap_tags "application", importmap: MyEngine.importmap %&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;For sharing JavaScript code with the host application, like Stimulus controllers, create a partial Importmap configuration file and set the engine to merge it with the main one in the host application.&lt;/p&gt;

&lt;p&gt;Create an Importmap configuration file at &lt;em&gt;config/importmap.rb&lt;/em&gt; and add the JavaScript pins to share with the host application. If you have dependencies for external packages, add those via a generator or installer to the host application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin_all_from File.expand_path("../app/assets/javascripts/controllers", __dir__ ), under: "controllers", preload: false

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

&lt;/div&gt;



&lt;p&gt;Open your &lt;code&gt;engine.rb&lt;/code&gt; file and add an initializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initializer "maquina.importmap", before: "importmap" do |app|
  app.config.importmap.paths &amp;lt;&amp;lt; Engine.root.join("config/importmap.rb")
end

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  What are the Advantages of Using Importmap?
&lt;/h2&gt;

&lt;p&gt;From a Ruby on Rails developer perspective, the main advantage of using Importmap is the freedom from requiring a JavaScript runtime-like node and freedom from the &lt;em&gt;node_modules&lt;/em&gt; dependency.&lt;/p&gt;

&lt;p&gt;Additionally, you don’t need an additional process in development mode to transpile and minify the JavaScript code. You rely on web standards to serve the code to the browser. Deploying your Rails application behind a reverse proxy offers several benefits. First, if you enable the HTTP/2 protocol, your browser can fetch multiple files with a single HTTP connection, and downloading many small JavaScript files won’t impact performance.&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%2Fo1blmmb5ljc5sn0zw33s.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%2Fo1blmmb5ljc5sn0zw33s.png" alt="Web tools" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enabling your proxy to use gzip or brotli compression ensures you are sending very small files while maintaining readability when using browser developer tools. If you change one file, you only need to invalidate that specific file, which the browser will download. The browser knows that a file was modified because of the fingerprint that Propshaft adds to all files.&lt;/p&gt;

&lt;p&gt;Using a reverse proxy like &lt;a href="https://github.com/basecamp/thruster" rel="noopener noreferrer"&gt;Thruster&lt;/a&gt; along with Puma offloads the assets serving from the Rails application. Thruster can cache assets and serve them when a client requests a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Not to Use Importmap
&lt;/h2&gt;

&lt;p&gt;There are cases where you should avoid using Importmap in a Rails application. If you are building a SPA application with React, Vue, or any other similar tool, there is a high likelihood you are writing your code with TypeScript. In this case, you should stick with the bundling strategy.&lt;/p&gt;

&lt;p&gt;Additionally, if you need to support older browsers, bundling with code transpilation is a better option.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>importmap</category>
      <category>javascript</category>
      <category>ruby</category>
    </item>
    <item>
      <title>No build for Javascript libraries with Rails and Importmap</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Fri, 09 Aug 2024 18:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/no-build-for-javascript-libraries-with-rails-and-importmap-57a</link>
      <guid>https://dev.to/mario_chavez/no-build-for-javascript-libraries-with-rails-and-importmap-57a</guid>
      <description>&lt;p&gt;As a web developer working with Ruby on Rails, you’re always looking for ways to streamline your workflow and make your applications more efficient. Enter Importmap - a powerful feature that simplifies how you manage JavaScript libraries in your Rails projects. In this post, we’ll dive into what Importmap is, how it works, and how you can use it for your projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Importmap?
&lt;/h2&gt;

&lt;p&gt;Importmap is a way to map JavaScript module names to their actual locations. They allow you to use &lt;code&gt;import&lt;/code&gt; statements in your JavaScript code without needing a bundler like Webpack or Rollup. This means you can use modern JavaScript modules directly in the browser, making your development process simpler and faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Importmap work?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mapping&lt;/strong&gt; : Importmaps create a mapping between a module name and its location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser Support&lt;/strong&gt; : Modern browsers use this mapping to load modules when they encounter &lt;code&gt;import&lt;/code&gt; statements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rails Integration&lt;/strong&gt; : Rails 7+ includes built-in support for Importmap, making it easy to use in your projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Pinning&lt;/strong&gt; : By default, Rails pins libraries locally, copying the code to your project into the &lt;code&gt;vendor/javascript&lt;/code&gt; folder. While this might feel similar to how JavaScript libraries were vendored in older Rails projects, Importmap provides a modern tool for managing dependencies and versions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practical tips for using Importmap in Rails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Setting up Importmap
&lt;/h3&gt;

&lt;p&gt;For a new Rails application, Importmap is set up by default. If you want to add it to an existing application, then follow these steps.&lt;/p&gt;

&lt;p&gt;To get started with Importmap in your Rails project, add this to your Gemfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem "importmap-rails"

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

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle install
bin/rails importmap:install

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

&lt;/div&gt;



&lt;p&gt;This sets up the necessary files and configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Adding JavaScript libraries
&lt;/h3&gt;

&lt;p&gt;To add a library, use the &lt;code&gt;pin&lt;/code&gt; option with &lt;code&gt;bin/importmap&lt;/code&gt; command. Let’s use Chart.js as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bin/importmap pin chart.js
Pinning "chart.js" to vendor/javascript/chart.js.js via download from https://ga.jspm.io/npm:chart.js@4.4.3/dist/chart.js
Pinning "@kurkle/color" to vendor/javascript/@kurkle/color.js via download from https://ga.jspm.io/npm:@kurkle/color@0.3.2/dist/color.esm.js

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

&lt;/div&gt;



&lt;p&gt;This command performs the following steps::&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download Chart.js and its dependencies.&lt;/li&gt;
&lt;li&gt;Place the files in the &lt;code&gt;vendor/javascript&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;config/importmap.rb&lt;/code&gt; file with the correct local paths.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the contents of &lt;code&gt;vendor/javascript&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; ls vendor/javascript
8.5k 9 Aug 12:53 -N  @kurkle--color.js
186k 9 Aug 12:53 -N  chart.js.js

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;config/importmap.rb&lt;/code&gt; looks as follows with the pinned libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin 'chart.js' # @4.4.3
pin '@kurkle/color', to: '@kurkle--color.js' # @0.3.2

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

&lt;/div&gt;



&lt;p&gt;Now, let’s create a Stimulus controller to use Chart.js. First, make sure you have Stimulus installed in your Rails application. Then, create a new file &lt;code&gt;app/javascript/controllers/chart_controller.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Controller } from "@hotwired/stimulus"
import { Chart, registerables } from 'chart.js'

Chart.register(...registerables)

export default class extends Controller {
  static targets = ['canvas']

  connect() {
    const ctx = this.canvasTarget.getContext('2d')
    this.chart = new Chart(ctx, {
  type: 'line',
  data: {
   labels: ['January', 'February', 'March', 'April', 'May', 'June'],
   datasets: [{
    label: 'Sample Data',
    data: [65, 59, 80, 81, 56, 55],
    borderColor: 'rgba(75, 192, 192, 1)',
    backgroundColor: 'rgba(75, 192, 192, 0.2)',
    borderWidth: 2
   }]
  },
  options: {
   scales: {
    y: {
     beginAtZero: true
    }
   }
  }
 })
  }

  disconnect() {
    this.chart.destroy()
  }
}

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

&lt;/div&gt;



&lt;p&gt;Now you can use this Stimulus controller in your Rails view. For example, in &lt;code&gt;app/views/home/index.html.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div data-controller="chart"&amp;gt;
  &amp;lt;canvas data-chart-target="canvas" width="400" height="200"&amp;gt;&amp;lt;/canvas&amp;gt;
&amp;lt;/div&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;This setup allows you to easily create charts in your Rails application using Chart.js through a Stimulus controller. The chart data can be dynamically passed from your Rails backend to the frontend using Stimulus values.&lt;/p&gt;

&lt;p&gt;But if you try to load the page, the graph is not displayed. After looking at the developer tools in the browser, you will find the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failed to register controller: chart (controllers/chart_controller)
  TypeError: Importing a module script failed.

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

&lt;/div&gt;



&lt;p&gt;The error is not helpful, but it gives you a clue that there is a problem with the dependencies in the Stimulus &lt;code&gt;chart_controller&lt;/code&gt;. Looking at the network tab in the browser tools, you find out that there is a missing file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL: http://localhost:3000/_/6La3kzg5.js
Status: 404 Not Found
Source: Network

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

&lt;/div&gt;



&lt;p&gt;Looking at the code of the file &lt;code&gt;vendor/javascript/chart.js.js&lt;/code&gt;, almost at the beginning of the file, you find the following reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import{r as t,c ...
... as Jt}from"../_/6La3kzg5.js" ...

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

&lt;/div&gt;



&lt;p&gt;It seems like &lt;a href="https://jspm.org" rel="noopener noreferrer"&gt;jspm.io&lt;/a&gt; adds a dependency which Importmap is unable to identify and download with the rest of the files. This is a known issue documented in the Importmap repository, as &lt;a href="https://github.com/rails/importmap-rails/pull/235" rel="noopener noreferrer"&gt;Download all the files associated with a package from a CDN&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Pinning from CDNs
&lt;/h3&gt;

&lt;p&gt;While Importmap pins libraries locally by default, it also allows you to pin from a CDN instead. Using this method, the library is served directly from the CDN instead of our Rails application.&lt;/p&gt;

&lt;p&gt;To try to find a solution, change the pins in the &lt;code&gt;config/importmap.rb&lt;/code&gt; file to point to the CDN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Using JSPM.io (default)
pin 'chart.js', to: 'https://ga.jspm.io/npm:chart.js@4.3.0/dist/chart.js'
pin '@kurkle/color', to: 'https://ga.jspm.io/npm:@kurkle/color@0.3.2/dist/color.esm.js'

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

&lt;/div&gt;



&lt;p&gt;After making the change, reload the page. Now the graph is there, loading the libraries from the CDN solved the issue of missing references. If you are ok with this approach, then you can stop here.&lt;/p&gt;

&lt;p&gt;If your goal is to serve and cache the libraries without a dependency on external CDN, then continue to the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Customizing local pinning
&lt;/h3&gt;

&lt;p&gt;With the option to choose a CDN from which to vendor JavaScript libraries, you should try another CDN, like &lt;a href="//www.jsdelivr.com"&gt;JSDeliver&lt;/a&gt;. Each CDN bundles dependencies differently, so you might get lucky trying another option.&lt;/p&gt;

&lt;p&gt;Remove all files from &lt;code&gt;vendor/javascript&lt;/code&gt; and remove the pins to Chart.js and @kurkle/color from &lt;code&gt;config/importmap.rb&lt;/code&gt;. The Importmap’s pin command accepts option &lt;code&gt;-f&lt;/code&gt; to specify a specific CDN different from JSpm.io.&lt;/p&gt;

&lt;p&gt;The first try to pin Chart.js fails with an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bin/importmap pin chart.js -f jsdelivr
Pinning "chart.js" to vendor/javascript/chart.js.js via download from https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.js
/.rbenv/versions/3.3.4/lib/ruby/3.3.0/net/http.rb:1603:in `initialize': Failed to open TCP connection to cdn.jsdelivr.net:443 (execution expired) (Net::OpenTimeout)

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

&lt;/div&gt;



&lt;p&gt;For the second try, add the library @kurkle/color to the pin command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; bin/importmap pin chart.js @kurkle/color -f jsdelivr
Pinning "@kurkle/color" to vendor/javascript/@kurkle/color.js via download from https://cdn.jsdelivr.net/npm/@kurkle/color@0.3.2/dist/color.esm.js
Pinning "chart.js" to vendor/javascript/chart.js.js via download from https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.js

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

&lt;/div&gt;



&lt;p&gt;This time it succeeded, the new files are placed in &lt;code&gt;vendor/javascript&lt;/code&gt; and the file &lt;code&gt;config/importmap.rb&lt;/code&gt; contains the pins. Reloading the page in your application shows that you have the same error as before, the missing file name is different but still the same result.&lt;/p&gt;

&lt;p&gt;It is time to try something different. Remove the files from &lt;code&gt;vendor/javascript&lt;/code&gt; one more time. Navigate to that directory and open your browser to &lt;a href="https://www.jsdelivr.com/" rel="noopener noreferrer"&gt;https://www.jsdelivr.com/&lt;/a&gt;. Search for Chart.js and from the code block copy the URL and download the library using curl. Also, search for @kurkle/color and download it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; curl -o chart.js.js 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/+esm'
&amp;gt; curl -o @kurkle--color.js 'https://cdn.jsdelivr.net/npm/@kurkle/color@0.3.2/+esm'

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

&lt;/div&gt;



&lt;p&gt;Once again, reload your application page. One more time you will get an error in the browser developer tools, but this time the error is different. It says that @kurkle/color failed to be loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Error] Failed to load resource: the server responded with a status of 404 (Not Found) http://localhost:3000/npm/@kurkle/color@0.3.2/+esm
[Error] Failed to register controller: chart (controllers/chart_controller) – TypeError: Importing a module script failed.
TypeError: Importing a module script failed.
 error

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

&lt;/div&gt;



&lt;p&gt;The reference to &lt;code&gt;npm/@kurkle/color@0.3.2/+esm&lt;/code&gt; appears at the beginning of chart.js.js file. You need to replace this reference for the @kurkle/color that you have pinned in &lt;code&gt;config/importmap.rb&lt;/code&gt;. Use &lt;code&gt;sed&lt;/code&gt; command to replace the value as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sed -i '' 's|/npm/@kurkle/color@0.3.2/+esm|@kurkle/color|g' chart.js.js

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

&lt;/div&gt;



&lt;p&gt;After this change, reload the application page. Now the graph is rendered and there are no errors related to missing references.&lt;/p&gt;

&lt;p&gt;Not in all cases when pinning libraries from a CDN you will have this problem, but if there is a case, now you know how to diagnose and try to fix it. I would expect that newer versions of Importmap will have a feature to understand and resolve hidden references that libraries might have.&lt;/p&gt;

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

&lt;p&gt;Importmap in Ruby on Rails offers a streamlined approach to managing JavaScript libraries like Chart.js. By simplifying your workflow, leveraging the power of modern browsers, and providing local access to your dependencies, you can focus more on creating functionality and less on configuration. Give Importmap a try in your next Rails project and experience the benefits for yourself!&lt;/p&gt;

&lt;p&gt;Remember, while Importmap is powerful and the default local pinning provides good version control, you still have the flexibility to use various CDNs when needed. Consider your specific needs and performance requirements when deciding on your Importmap strategy.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Sound to Script: Using OpenAI’s Whisper Model and Whisper.cpp</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Sun, 10 Dec 2023 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/sound-to-script-using-openais-whisper-model-and-whispercpp-naf</link>
      <guid>https://dev.to/mario_chavez/sound-to-script-using-openais-whisper-model-and-whispercpp-naf</guid>
      <description>&lt;p&gt;AI offers a different set of inputs and outputs for inferences. One of the many inference models is Automatic Speech Recognition (ASR). Using OpenAI’s Whiper model makes transcribing pre-recorded or live audio possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ggerganov/whisper.cpp" rel="noopener noreferrer"&gt;Whisper.cpp&lt;/a&gt; implements OpenAI’s Whisper model, which allows you to run this model on your machine. It could be done running your CPU, Apple’s Core ML from M processors, or using a dedicated GPU unit. You can run the smaller or larger Whisper model; Whisper.cpp also supports running quantized models, which require less memory and disk space using the &lt;a href="https://github.com/ggerganov/ggml" rel="noopener noreferrer"&gt;GGML library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, let’s see how to use Whisper.cpp to process a video to get subtitle SRT format.&lt;/p&gt;

&lt;p&gt;First, you need to clone the Whisper.cpp repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone [https://github.com/ggerganov/whisper.cpp](https://github.com/ggerganov/whisper.cpp)

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

&lt;/div&gt;



&lt;p&gt;If you are running Whisper.cpp from your CPU, change to the code folder and run the make command:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Then download a Whiper model in ggml format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash ./models/download-ggml-model.sh base.en

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

&lt;/div&gt;



&lt;p&gt;You can find available models here: &lt;a href="https://huggingface.co/ggerganov/whisper.cpp" rel="noopener noreferrer"&gt;https://huggingface.co/ggerganov/whisper.cpp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the model in place, run the following command to test it with a sample audio in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main -f samples/jfk.wav

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

&lt;/div&gt;



&lt;p&gt;You should see the model being loaded; at the end, it displays the inferred text from the audio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main: processing 'samples/jfk.wav' (176000 samples, 11.0 sec), 4 threads, 1 processors, 5 beams + best of 5, lang = en, task = transcribe, timestamps = 1 ...
[00:00:00.000 --&amp;gt; 00:00:11.000] And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country.

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

&lt;/div&gt;



&lt;p&gt;Now, I’ll focus on getting Whisper.cpp to work with Apple’s silicon processor to get better performance at inference.&lt;/p&gt;

&lt;p&gt;You need Python installed to prepare Whisper models for running with Apple’s Core ML. The best way to set up Python is to install it via &lt;a href="https://docs.conda.io/projects/miniconda/en/latest/" rel="noopener noreferrer"&gt;Miniconda&lt;/a&gt; and create an environment for Whisper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conda create -n py310-whisper python=3.10 -y
conda activate py310-whisper

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

&lt;/div&gt;



&lt;p&gt;With Python ready and activated, install the following dependencies for Core ML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install ane_transformers
pip install openai-whisper
pip install coremltools

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

&lt;/div&gt;



&lt;p&gt;Next, generate a Core ML model off the downloaded base.en Whisper model. If you downloaded a different model, update the command to reflect that change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./models/generate-coreml-model.sh base.en

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

&lt;/div&gt;



&lt;p&gt;Finally, you need to compile Whisper.cpp with Core ML support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make clean
WHISPER_COREML=1 make -j

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

&lt;/div&gt;



&lt;p&gt;Running the Core ML model with Whisper.cpp Core ML support produces faster inference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main -m models/ggml-base.en.bin -f samples/jfk.wav

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

&lt;/div&gt;



&lt;p&gt;With these tools ready, you can move to a different scenario where the audio needs to be extracted from a video, passed to Whisper.cpp, and produced the subtitle file in SRT format.&lt;/p&gt;

&lt;p&gt;To extract audio from a video, &lt;a href="https://ffmpeg.org" rel="noopener noreferrer"&gt;ffmpeg&lt;/a&gt; is the best tool for this job. Whisper.cpp needs audio in 16-bit format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i video.mp4 -ar 16000 -ac 1 -c:a pcm_s16le video.wav

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

&lt;/div&gt;



&lt;p&gt;The output is a WAV audio file that you can use to produce a transcription into a JSON file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./main -m models/ggml-base.en.bin -f video.wav -oj -ojf video
....

    "params": {
            "model": "models/ggml-medium.en-q5_0.bin",
            "language": "en",
            "translate": false
    },
    "result": {
            "language": "en"
    },
    "transcription": [
            {
                    "timestamps": {
                            "from": "00:00:00,720",
                            "to": "00:00:08,880"
                    },
                    "offsets": {
                            "from": 720,
                            "to": 8880
                    },
                    "text": " Hi, everyone. Would you let people in why? Okay. Yes. My name is Selma. For those of you that don't",
                    "tokens": [
                            {
                                    "text": " Hi",
                                    "timestamps": {
                                            "from": "00:00:00,000",
                                            "to": "00:00:00,240"
                                    },
                                    "offsets": {
                                            "from": 0,
                                            "to": 240
                                    },
                                    "id": 15902,
                                    "p": 0.882259
                            },
                            {
                                    "text": ",",
                                    "timestamps": {
                                            "from": "00:00:00,240",
                                            "to": "00:00:00,470"
                                    },
                                    "offsets": {
                                            "from": 240,
                                            "to": 470
                                    },
....

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

&lt;/div&gt;



&lt;p&gt;This JSON file can be transformed to meet our needs. In our case, we want to produce an SRT format for video player subtitles. This last part is done with a Ruby script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;json_data = JSON.parse(File.read(json_file_path))

transcription = json_data['transcription']
srt_content = ""

transcription.each_with_index do |entry, index|
  from_time = entry['timestamps']['from']
  to_time = entry['timestamps']['to']
  text = entry['text']

  srt_content += "#{index + 1}\\n"
  srt_content += "#{from_time} --&amp;gt; #{to_time}\\n"
  srt_content += "#{text}\\n\\n"
end

File.write(srt_file_path, srt_content)

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

&lt;/div&gt;



&lt;p&gt;It depends on the length of the extracted audio file; this process can take a few seconds or several minutes to complete.&lt;/p&gt;

&lt;p&gt;The following is a Ruby script performs all three actions at once: extract audio, transcribe, and transform into SRT file format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'json'
require 'tmpdir'

def extract_audio(dir, input_video_path)
  # Generate temporary WAV file path based on the input video
  temp_wav_file_path = "#{dir}/#{File.basename(input_video_path, '.*')}.wav"

  # Construct the ffmpeg command
  ffmpeg_command = "ffmpeg -i '#{input_video_path}' -ar 16000 -ac 1 -c:a pcm_s16le '#{temp_wav_file_path}'"

  # Execute the ffmpeg command
  puts ffmpeg_command
  system(ffmpeg_command)

  # Check if the command was successful
  if $?.success?
    puts "Audio extracted successfully to #{temp_wav_file_path}"
    return temp_wav_file_path
  else
    puts "Error extracting audio. Please check your ffmpeg installation and the input video file."
    exit(1)
  end
end

def process_wav(dir, wav_file_path)
  # Path to the main command and binary file
  path = '~/Development/llm/whisper.cpp/'
  main_command = 'main'
  model_file = 'models/ggml-medium.en-q5_0.bin'

  # Generate temporary JSON file path based on the WAV file
  temp_json_file_path = "#{dir}/#{File.basename(wav_file_path, '.*')}"

  # Construct the full command with quotes around file paths
  full_command = "#{path}#{main_command} -m #{path}#{model_file} -f '#{wav_file_path}' -oj -ojf '#{temp_json_file_path}'"

  # Execute the command
  puts full_command
  system(full_command)

  # Check if the command was successful
  if $?.success?
    puts "Processing completed successfully for #{wav_file_path}"
    return "#{temp_json_file_path}.wav.json"
  else
    puts "Error processing the WAV file. Please check your command and the input file."
    exit(1)
  end
end

def process_json_to_srt(json_file_path, srt_file_path)
  json_data = JSON.parse(File.read(json_file_path))

  # Extract 'transcription' array from JSON
  transcription = json_data['transcription']
  srt_content = ""

  transcription.each_with_index do |entry, index|
    from_time = entry['timestamps']['from']
    to_time = entry['timestamps']['to']
    text = entry['text']

    srt_content += "#{index + 1}\n"
    srt_content += "#{from_time} --&amp;gt; #{to_time}\n"
    srt_content += "#{text}\n\n"
  end

  # Write SRT content to the new file
  File.write(srt_file_path, srt_content)

  puts "SRT file created at #{srt_file_path}"
end

# Check if the video file path is provided as a command-line argument
if ARGV.empty?
  puts "Usage: ruby combined_script.rb path/to/your/video.mp4"
  exit(1)
else
  video_path = ARGV[0]

  Dir.mktmpdir do |dir|
    # Extract audio
    wav_file_path = extract_audio(dir, video_path)

    # Process audio to obtain JSON transcript
    json_file_path = process_wav(dir, wav_file_path)

    # Convert JSON to SRT
    srt_file_path = "#{File.dirname(video_path)}/#{File.basename(video_path, '.*')}.srt"
    process_json_to_srt(json_file_path, srt_file_path)
  end
end

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

&lt;/div&gt;



&lt;p&gt;It needs a few changes in the &lt;code&gt;process_wav&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;First, you need to update the path to your Whisper binaries. And second, the relative path to the binaries of your model. After these changes, you can create subtitles for your video with the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby transcribe.rb video.mp4

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

&lt;/div&gt;



&lt;p&gt;After script completion, you will have a video.srt file next to your video file.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Full-text search with SQLite and Rails</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Fri, 01 Sep 2023 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/full-text-search-with-sqlite-and-rails-1cp0</link>
      <guid>https://dev.to/mario_chavez/full-text-search-with-sqlite-and-rails-1cp0</guid>
      <description>&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%2Fg1v9oz3zo9lyag848m54.jpg" 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%2Fg1v9oz3zo9lyag848m54.jpg" alt="Full-text search with SQLite and Rails" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, I planned to move a few small Ruby on Rails applications with Heroku using &lt;a href="https://kamal-deploy.org/" rel="noopener noreferrer"&gt;Kamal&lt;/a&gt; onto a single server. All applications use PostgreSQL as the database because it is simple to use with Heroku and because they take advantage of Postgres’ full-text search.&lt;/p&gt;

&lt;p&gt;Because of the size and usage of those applications, I was not convinced that I wanted to manage my own PostgreSQL server or pay for a managed service. So, I started to consider replacing the database with &lt;a href="https://www.sqlite.org/index.html" rel="noopener noreferrer"&gt;SQLite&lt;/a&gt;. At least in my feed at &lt;a href="https://x.com/mario_chavez" rel="noopener noreferrer"&gt;X&lt;/a&gt;, I have seen so many posts in the past few months on how good and fast it is.&lt;/p&gt;

&lt;p&gt;I wanted something like the &lt;a href="https://github.com/Casecommons/pg_search#pg_search_scope" rel="noopener noreferrer"&gt;pg_search&lt;/a&gt; gem, where I could define a scope name and the fields to be indexed. On the SQLite side, I found that it has an extension called &lt;a href="https://www.sqlite.org/fts5.html" rel="noopener noreferrer"&gt;FTS5&lt;/a&gt;, which is defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;FTS5 is an SQLite &lt;a href="https://www.sqlite.org/c3ref/module.html" rel="noopener noreferrer"&gt;virtual table module&lt;/a&gt; that provides &lt;a href="https://en.wikipedia.org/wiki/Full_text_search" rel="noopener noreferrer"&gt;full-text search&lt;/a&gt; functionality to database applications. In their most elementary form, full-text search engines allow the user to efficiently search a large collection of documents for the subset that contains one or more instances of a search term. The search functionality provided to World Wide Web users by Google is, among other things, a full-text search engine, as it allows users to search for all documents on the web that contain, for example, the term “fts5”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This looked promising, so I started implementing a quick and dirty solution for my needs. Starting from a model Post with title and content attributes, I want to define a &lt;code&gt;full_search&lt;/code&gt; scope and indicate the attributes to index for full-text search.&lt;/p&gt;

&lt;p&gt;Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Post &amp;lt; ApplicationRecord
  include SqliteSearch

  search_scope(:title, :content)
end

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

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;SqliteSearch&lt;/code&gt; is the module that does the heavy work. Before continuing with the implementation, there are decisions to be made regarding how to store the indexed data in the database. A virtual table is required to store the indexed data; one option is to create a single table for this purpose with a &lt;code&gt;record_type&lt;/code&gt; and &lt;code&gt;record_id&lt;/code&gt; fields to identify the data per model type, just like the way it works for ActiveStorage or ActionText in Rails.&lt;/p&gt;

&lt;p&gt;The second option is to create a table per indexed model. I chose this option since not all my models require this functionality. The migration for this table is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PostSearch &amp;lt; ActiveRecord::Migration[7.0]
  def up
    execute("CREATE VIRTUAL TABLE fts_posts USING fts5(title, content, post_id)")
  end

  def down
    execute("DROP TABLE IF EXISTS fts_posts")
  end
end

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

&lt;/div&gt;



&lt;p&gt;The table name follows the Rails convention, but it adds the prefix &lt;code&gt;fts_&lt;/code&gt;. The table fields are the ones that need indexing with the addition of the original record id with the foreign key convention. SQLite virtual tables don’t have data types, primary keys, constraints, or indexes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SqliteSearch&lt;/code&gt; module needs to implement a way to add or update the model data to the search index. This is done using the &lt;strong&gt;ActiveRecord&lt;/strong&gt; callbacks for save and destroy commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module SqliteSearch
  extend ActiveSupport::Concern

  private def update_search_index
    primary_key = self.class.primary_key
    table_name = self.class.table_name
    foreign_key = self.class.to_s.foreign_key

    search_attrs = @@search_scope_attrs.each_with_object({}) { |attr, acc|
      acc[attr] = quote_string(send(attr) || "")
    }
    id_value = attributes[primary_key]

    sql_delete = &amp;lt;&amp;lt;~SQL.strip
      DELETE FROM fts_#{table_name} WHERE #{foreign_key} = #{id_value};
    SQL
    self.class.connection.execute(sql_delete)

    sql_insert = &amp;lt;&amp;lt;~SQL.strip
      INSERT INTO fts_#{table_name}(#{search_attrs.keys.join(", ")}, #{foreign_key})
      VALUES (#{search_attrs.values.map { |value| "'#{value}'" }.join(", ")}, #{attributes[primary_key]});
    SQL
    self.class.connection.execute(sql_insert)
  end

  private def delete_search_index
    primary_key = self.class.primary_key
    table_name = self.class.table_name
    foreign_key = self.class.to_s.foreign_key
    id_value = attributes[primary_key]

    sql_delete = &amp;lt;&amp;lt;~SQL.strip
      DELETE FROM fts_#{table_name} WHERE #{foreign_key} = #{id_value};
    SQL
    self.class.connection.execute(sql_delete)
  end

  included do
    after_save_commit :update_search_index
    after_destroy_commit :delete_search_index

    scope_foreign_key = to_s.foreign_key
    scope :full_search, -&amp;gt;(query) {
      return none if query.blank?

      sql = &amp;lt;&amp;lt;~SQL.strip
        SELECT #{scope_foreign_key} AS id FROM fts_#{table_name}
        WHERE fts_#{table_name} = '#{query}' ORDER BY rank;
      SQL
      ids = connection.execute(sql).map(&amp;amp;:values).flatten
      where(id: ids)
    }
  end

  class_methods do
    def search_scope(*attrs)
      @@search_scope_attrs = attrs
    end

    def rebuild_search_index(*ids)
      target_ids = Array(ids)
      target_ids = self.ids if target_ids.empty?

      scope_foreign_key = to_s.foreign_key

      delete_where = Array(ids).any? ? "WHERE #{scope_foreign_key} IN (#{ids.join(", ")})" : ""
      sql_delete = &amp;lt;&amp;lt;~SQL.strip
        DELETE FROM fts_#{table_name} #{delete_where};
      SQL
      connection.execute(sql_delete)

      target_ids.each do |id|
        record = where(id: id).pluck(*@@search_scope_attrs, :id).first
        if record.present?
          id = record.pop

          sql_insert = &amp;lt;&amp;lt;~SQL.strip
            INSERT INTO fts_#{table_name}(#{@@search_scope_attrs.join(", ")}, #{scope_foreign_key})
            VALUES (#{record.map { |value| "'#{quote_string(value)}'" }.join(", ")}, #{id});
          SQL
          connection.execute(sql_insert)
        end
      end
    end

    def quote_string(s)
      s.gsub("\\", '\&amp;amp;\&amp;amp;').gsub("'", "''")
    end
  end
end

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

&lt;/div&gt;



&lt;p&gt;The class method &lt;code&gt;search_scope&lt;/code&gt; tells the module the attributes we need to index. The &lt;code&gt;update_search_index&lt;/code&gt; callback is called when the record is created or updated. Since SQLite, virtual tables don’t support upsert, which is a way to tell the database to insert a record if it doesn’t exist or to update it if it does. Also, the Rails SQLite adapter does not support multiple statements in a single execution call. &lt;/p&gt;

&lt;p&gt;These limitations forced me to make two additional database calls: the first to delete the indexed data for a specific record, and the second to insert the new indexed data. It’s not very performant, but for a small database, it might be just fine. The &lt;code&gt;delete_search_index&lt;/code&gt; callback removes the indexed data when a record is deleted.&lt;/p&gt;

&lt;p&gt;The module also implements a class method, &lt;code&gt;rebuild_search_index&lt;/code&gt;, which optionally receives an array of record ids to re-index. If no ids are passed, then it rebuilds the index for all records.&lt;/p&gt;

&lt;p&gt;Finally, a scope full_search is added to fetch records based on the full-text search index. You can use keywords like “AND”, “OR,” or “NOT” to refine your search or any other special character supported by SQLite FTS5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Post.full_search("Ruby OR Rails NOT Javascript")

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

&lt;/div&gt;



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

&lt;p&gt;Adding this module to my applications allowed me to explore the idea of moving from PostgreSQL for these small applications. Not because PostgreSQL is bad, but because it might be too much for the current application’s needs.&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>fulltextsearch</category>
      <category>rails</category>
    </item>
    <item>
      <title>Working with Rails Engines, Importmap and TailwindCSS for assets.</title>
      <dc:creator>Mario Alberto Chávez</dc:creator>
      <pubDate>Wed, 23 Aug 2023 06:00:00 +0000</pubDate>
      <link>https://dev.to/mario_chavez/working-with-rails-engines-importmap-and-tailwindcss-for-assets-pn3</link>
      <guid>https://dev.to/mario_chavez/working-with-rails-engines-importmap-and-tailwindcss-for-assets-pn3</guid>
      <description>&lt;p&gt;Rails engines are one of my favorite tools when I want to isolate reusable functionality for Rails applications. An example of this is the NoPassword gem, which allows users to login into an application just with their email and the received link and code.&lt;/p&gt;

&lt;p&gt;With Rails 3.1, the engines were mountable and integrated with the Asset Pipeline to manage the engine’s assets. This continued to work this way until Webpack was introduced to Rails in version 5.1. At this moment, it was not clear how to make Webpack work with the engine’s assets or any other gem that included assets.&lt;/p&gt;

&lt;p&gt;Rails 7 introduced CSS and JS bundling, along with the Propshaft gem to manage and serve assets. Also, introduced Importmaps and TailwindCSS as options for not having Node as a dependency, but in both cases without any official word on how to make these work with engines.&lt;/p&gt;

&lt;p&gt;NOTE: Importmap’s README explains how to composite multiple Impormap configurations. &lt;a href="https://github.com/rails/importmap-rails#composing-import-maps" rel="noopener noreferrer"&gt;https://github.com/rails/importmap-rails#composing-import-maps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working on the NoPassword engine and a few private others, I decided for them to use Importmaps, TailwindCSS, and Propshaft. It led me to figure out a way to use the engine assets. The rest of this post describes my successful journey with assets and engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importmap engine configuration
&lt;/h2&gt;

&lt;p&gt;First, let’s try the route described in Importmap’s README file.&lt;/p&gt;

&lt;p&gt;To install Importmap in an engine project, it requires adding the gem to the dummy app and adding the dependency to the engine’s gem spec file.&lt;/p&gt;

&lt;p&gt;Install Importmap and add it as a dependency to the Gemfile as follows:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;And add it to the gem spec file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec.add_dependency "importmap-rails", "~&amp;gt; 1.2", "&amp;gt;= 1.2.1"

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

&lt;/div&gt;



&lt;p&gt;Then run the bundle command and navigate to the test/dummy app to execute the installer in the dummy app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd test/dummy &amp;amp;&amp;amp; bin/rails importmap::install

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

&lt;/div&gt;



&lt;p&gt;Following the README instructions for composite Importmaps, open the &lt;code&gt;lib/my_engine/engine.rb&lt;/code&gt; file and add the following hook - remember to replace &lt;strong&gt;**&lt;/strong&gt;** *\ **&lt;em&gt;****&lt;/em&gt;* my-engine &lt;strong&gt;**&lt;/strong&gt;** *\ **&lt;em&gt;****&lt;/em&gt;* with the name of your engine -:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module MyEngine
  class Engine &amp;lt; ::Rails::Engine
    # ...
    initializer "my-engine.importmap", before: "importmap" do |app|
      app.config.importmap.paths &amp;lt;&amp;lt; Engine.root.join("config/importmap.rb")
      # ...
    end
  end
end

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

&lt;/div&gt;



&lt;p&gt;Then create the file &lt;code&gt;config/importmap.rb&lt;/code&gt; and pin the engine’s javascript assets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin_all_from File.expand_path("../app/assets/javascript", __dir__ )

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

&lt;/div&gt;



&lt;p&gt;To test this setup, two Stimulus controllers were added, one in the engine’s &lt;code&gt;app/assets/javascript&lt;/code&gt; and another one at the dummy app &lt;code&gt;test/dummy/app/javascript/controllers&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/javascript/controllers/host_controller.js (Dummy APP)
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    console.log("Connected")
    this.element.textContent = "Hello World! This is a Javascript from the Host"
  }
}

console.log("Loaded")

# app/assets/javascript/engine_controller.js (Engine)
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    console.log("Connected")
    this.element.textContent = "Hello World! This is a Javascript from the Engine"
  }
}

console.log("Loaded")

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

&lt;/div&gt;



&lt;p&gt;Set up Stimulus in the dummy app by adding the gem and running the installer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd test/dummy
bundle add stimulus-rails
./bin/rails stimulus:install

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

&lt;/div&gt;



&lt;p&gt;The Dummy app has a &lt;strong&gt;HomeController&lt;/strong&gt; with an index action. The index template has two divs. One for &lt;code&gt;host_controller.js&lt;/code&gt; and the second one for &lt;code&gt;engine_controller.js&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Engine's Stimulus controller (Host app)&amp;lt;/h1&amp;gt;
&amp;lt;div data-controller="host"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div data-controller="engine"&amp;gt;&amp;lt;/div&amp;gt;

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

&lt;/div&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%2Fww756in5794v47v54rc9.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%2Fww756in5794v47v54rc9.png" alt="Importmap" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, this setup does not work, at least for my use case, where I expect the engine to have Stimulus controllers available in the host application, the Dummy app in this case. The engine’s controller is there in the imports manifest but is not loaded in the context of the Stimulus application.&lt;/p&gt;

&lt;p&gt;The way that I make this work is to first organize the engine’s Stimulus controllers with the following directory structure: &lt;code&gt;app/assets/javascript/my_engine/controllers&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, modify the Dummy app’s &lt;code&gt;config/importmap.rb&lt;/code&gt; and add the following line at the end of the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin_all_from MyEngine::Engine.root.join("app/assets/javascript/my_engine/controllers"), under: "controllers", to: "my_engine/controllers"

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

&lt;/div&gt;



&lt;p&gt;With this change, reload the page, and now it is working! The engine’s Stimulus controller is loaded by the host application.&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%2Ftvlcrdiipf450nrmy6kh.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%2Ftvlcrdiipf450nrmy6kh.png" alt="Importmap" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Importmap repository, there is an open issue about how to make the gem work with engines, and the user muriloime mentions this solution &lt;a href="https://github.com/rails/importmap-rails/issues/58" rel="noopener noreferrer"&gt;https://github.com/rails/importmap-rails/issues/58&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, what about making Importmap work with the engine’s own views? In the case of the NoPassword gem, it provides views for the login process and uses a Stimulus controller to display errors; it does not depend on the host application in any way.&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%2Fs98a5n430nc026ih8fi3.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%2Fs98a5n430nc026ih8fi3.gif" alt="No Password" width="612" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, add Stimulus as a gem dependency to the gem spec file and execute the bundle command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spec.add_dependency "stimulus-rails", "~&amp;gt; 1.2"

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

&lt;/div&gt;



&lt;p&gt;The installer is not available inside the engine, so this step requires adding a few files manually. Let’s start with the javascript files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/assets/javascript/my_engine/application.js
import "controllers"

# app/assets/javascript/my_engine/controllers/index.js
// Import and register all your controllers from the importmap under controllers/*

import { application } from "controllers/application"

// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)

// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)

# app/assets/javascript/my_engine/controllers/application.js
import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Configure Stimulus development experience
application.debug = false
window.Stimulus = application

export { application }

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

&lt;/div&gt;



&lt;p&gt;Open the file &lt;code&gt;config/importmap.rb&lt;/code&gt; and replace the content with the following pins that load Stimulus and the engine’s controllers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true

pin "application", preload: true

pin_all_from MyEngine::Engine.root.join("app/assets/javascript/my_engine/controllers"), under: "controllers", to: "my_engine/controllers"

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

&lt;/div&gt;



&lt;p&gt;Instead of composing a global Importmap, we are going to set up a local configuration for the engine. Open the &lt;code&gt;lib/my_engine.rb&lt;/code&gt; file and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require "my_engine/version"
require "my_engine/engine"

require "importmap-rails"

module MyEngine
  class &amp;lt;&amp;lt; self
    attr_accessor :configuration
  end

  class Configuration
    attr_reader :importmap

    def initialize
      @importmap = Importmap::Map.new
      @importmap.draw(Engine.root.join("config/importmap.rb"))
    end
  end

  def self.init_config
    self.configuration ||= Configuration.new
  end

  def self.configure
    init_config
    yield(configuration)
  end
end

MyEngine.init_config

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

&lt;/div&gt;



&lt;p&gt;Here, a configuration class was added to the engine. It has only one setting, where it initializes a new Importmap with assets belonging to the engine. Be aware that sweepers are not set up for Importmap; this means that changes made during development with the engine will not be taken until the app is restarted. This configuration can be extended to include more engine settings if needed.&lt;/p&gt;

&lt;p&gt;Now we need to create a helper similar to &lt;code&gt;javascript_importmap_tag&lt;/code&gt; that is aware of the engine’s Importmap configuration. Open the &lt;code&gt;app/helpers/my_engine/application_helper.rb&lt;/code&gt; and add the following method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def my_engine_importmap_tags(entry_point = "application", shim: true)
  safe_join [
    javascript_inline_importmap_tag(MyEngine.configuration.importmap.to_json(resolver: self)),
    javascript_importmap_module_preload_tags(MyEngine.configuration.importmap),
    (javascript_importmap_shim_nonce_configuration_tag if shim),
    (javascript_importmap_shim_tag if shim),
    javascript_import_module_tag(entry_point)
  ].compact, "\n"
end

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

&lt;/div&gt;



&lt;p&gt;Now open the engine’s layout and replace the &lt;code&gt;javascript_importmap_tag&lt;/code&gt; with &lt;code&gt;my_engine_importmap_tags&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To test this, add a controller named &lt;code&gt;MyEngine::HomeController&lt;/code&gt; with an index action and a div that uses &lt;code&gt;engine_controller.js&lt;/code&gt; to it.&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%2Fdjl2kqfue8f16xyvp8c5.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%2Fdjl2kqfue8f16xyvp8c5.png" alt="Importmap" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are the two configuration options for Importmap with engines. Share the engine’s Stimulus controllers with the host app, or use the engine’s Stimulus controllers internally for the engine’s templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  TailwindCSS engine configuration
&lt;/h2&gt;

&lt;p&gt;The steps to install it are simple. First, let’s install it into the Dummy app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd test/dummy &amp;amp;&amp;amp; bundle add tailwindcss-rails
./bin/rails tailwindcss:install

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

&lt;/div&gt;



&lt;p&gt;For the Dummy app that comes with the engine in development mode, there are two additional steps that are not required when the host app is not the Dummy app.&lt;/p&gt;

&lt;p&gt;Ensure that the TailwindCSS task runs with Foreman or Overmind using your &lt;code&gt;Procfile.dev&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web: bin/rails server -p 3000
css: bin/rails app:tailwindcss:watch

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

&lt;/div&gt;



&lt;p&gt;Also, change the &lt;code&gt;test/dummy/config/tailwind.config.js&lt;/code&gt; file that was installed by TailwindCSS to have the right path to the Dummy app. Change the content section to include the &lt;code&gt;./test/dummy&lt;/code&gt; path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content: [
    './test/dummy/public/*.html',
    './test/dummy/app/helpers/**/*.rb',
    './test/dummy/app/javascript/**/*.js',
    './test/dummy/app/views/**/*.{erb,haml,html,slim}'
  ],

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

&lt;/div&gt;



&lt;p&gt;Restart your application, and it should work for the Dummy app.&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%2Fffvnxe7tdt9w5vq9jxq2.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%2Fffvnxe7tdt9w5vq9jxq2.png" alt="TailwindCSS" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To extend the TailwindCSS styling into the engine’s templates, there are a few steps that need to be taken first. The host app, in our case, the Dummy app, needs to override the engine’s layout to include the TailwindCSS files. Copy the file &lt;code&gt;app/layouts/my_engine/application.html.erb&lt;/code&gt; to &lt;code&gt;test/dummy/app/layouts/my_engine/application.html.erb&lt;/code&gt; and add the following line in the &lt;strong&gt;head&lt;/strong&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Navigate to an engine action, and you can confirm the general CSS reset of TailwindCSS is applied, but utility classes are ignored.&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%2F304qcddgwvhfvmv9jk6d.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%2F304qcddgwvhfvmv9jk6d.png" alt="TailwindCSS" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This happens because the file doesn’t know that it also needs to scan the engine templates for TailwindCSS classes.&lt;/p&gt;

&lt;p&gt;To fix this, we need a rake task and a generator in the host or Dummy app. Create a file &lt;code&gt;test/dummy/lib/tasks/tailwind.rake&lt;/code&gt; and add the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# frozen_string_literal: true

namespace :tailwindcss do
  desc "Generates your tailwind config file"
  task :config do
    Rails::Generators.invoke("tailwind_config", ["--force"])
  end
end

Rake::Task["tailwindcss:build"].enhance(["tailwindcss:config"])
Rake::Task["tailwindcss:watch"].enhance(["tailwindcss:config"])

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

&lt;/div&gt;



&lt;p&gt;Here we are adding a &lt;strong&gt;tailwindcss:config&lt;/strong&gt; task that invokes a generator (we are going to create this one in a moment) and enhancing TailwindCSS tasks provided by the gem by injecting this new task into the build process.&lt;/p&gt;

&lt;p&gt;For the generator, create the file &lt;code&gt;test/dummy/lib/generators/tailwind_config_generator.rb&lt;/code&gt; in the host or Dummy app and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# frozen_string_literal: true

class TailwindConfigGenerator &amp;lt; Rails::Generators::Base
  source_root File.expand_path("../templates", __FILE__ )

  def create_tailwind_config_file
    @engines_paths = MyEngine.configuration.tailwind_content

    # The second parameter for the template method is required only if the host app is the Dummy app; 
    # for an external host app, remove that parameter.
    template "config/tailwind.config.js", File.expand_path("../../../config/tailwind.config.js", __FILE__ )
  end
end

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

&lt;/div&gt;



&lt;p&gt;This generator, when executed, creates a new TailwindCSS config file from a template, but before doing that, in the &lt;code&gt;create_tailwind_config_file&lt;/code&gt; method, we can set up the engine paths to consider when scanning for TailwindCSS classes.&lt;/p&gt;

&lt;p&gt;In the code, the generator assumes that our engine configuration exposes a &lt;code&gt;tailwind_content&lt;/code&gt; accessor with an array of paths.&lt;/p&gt;

&lt;p&gt;Add an accessor to the Configuration class in file &lt;code&gt;lib/my_engine.rb&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attr_accessor :tailwind_content

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

&lt;/div&gt;



&lt;p&gt;Also add to the &lt;code&gt;initializer&lt;/code&gt; method of the same class the following paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@tailwind_content = [
  "#{MyEngine::Engine.root}/app/views/**/*",
  "#{MyEngine::Engine.root}/app/helpers/**/*",
  "#{MyEngine::Engine.root}/app/controllers/**/*",
  "#{MyEngine::Engine.root}/app/javascript/**/*.js"
]

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

&lt;/div&gt;



&lt;p&gt;Moving back to the template file in the &lt;code&gt;tailwind_config_generator&lt;/code&gt;, this is the TailwindCSS config file, but it will be created dynamically by the task to be able to inject the engine paths into it.&lt;/p&gt;

&lt;p&gt;Move the file &lt;code&gt;test/dummy/config/tailwind.config.js&lt;/code&gt; to &lt;code&gt;lib/generators/templates/config/tailwind.config.js.tt&lt;/code&gt; and modify the &lt;strong&gt;content&lt;/strong&gt; section as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content: [
    './test/dummy/public/*.html',
    './test/dummy/app/helpers/**/*.rb',
    './test/dummy/app/javascript/**/*.js',
    './test/dummy/app/views/**/*.{erb,haml,html,slim}',
    &amp;lt;%= @engines_paths.map{ |path| "'#{path}'" }.join(",\n") %&amp;gt;
  ],

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

&lt;/div&gt;



&lt;p&gt;Add the file &lt;code&gt;test/dummy/config/tailwind.config.js&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; file, since this file will be generated automatically.&lt;/p&gt;

&lt;p&gt;Restart the application, navigate the view in the host app and a view in the engine, and confirm that TailwindCSS is working for both cases.&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%2F16rxsu1zbvexjxshsfy3.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%2F16rxsu1zbvexjxshsfy3.png" alt="TailwindCSS" width="800" height="491"&gt;&lt;/a&gt;&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%2Fro7y3dp0ogyr3y1ieu93.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%2Fro7y3dp0ogyr3y1ieu93.png" alt="TailwindCSS" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This is how I have been working with the Rails engines Importmap and TailwindCSS. Most of the knowledge here came from reading the source code of the libraries and trial and error.&lt;/p&gt;

&lt;p&gt;If you are looking to work with engines and this tool set for assets, please give it a try and let me know how it goes or if there is a better, simpler way to archive the same.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>engines</category>
      <category>importmap</category>
      <category>tailwindcss</category>
    </item>
  </channel>
</rss>
