Welcome to this step-by-step guide on how to add AI functionality to a Rails app using the open-source BoxCars gem!
BoxCars is a new gem that allows Ruby and Rails developers to integrate AI capabilities into their applications. In this guide, we’ll show you how easy it is to add a simple text-to-command user interface to a rails app. This will let the user ask their questions, and it will use AI to respond to the request.
In this guide, we'll be using the rails-railx-tailwind starter kit as a sample app to work with. This starter app is a simple app with two models users and articles.
To get started, go ahead and click the "Use this template" button on the rails-railx-tailwind GitHub page to create a new repository with the code. Then, clone your new repository using the following command:
git clone [url of your repo]
Before proceeding, ensure you have Ruby 3.2.0 installed on your system. You can install it using RVM with the following command:rvm install ruby-3.2.0
Now, run bin/setup
to set up the app and bin/dev
to start it in dev mode.
Now, let's add BoxCar AI to your app! First, add the BoxCars and dotenv gems to your Gemfile by adding the following lines:
gem "boxcars"
gem "dotenv"
Next, run bundle install
to install the new gems.
The dotenv gem allows you to load environment variables from a .env file. To use the OpenAI LLM model, you must set the OPENAI_ACCESS_TOKEN
environment variable. You can set the environment variable without using the dotenv gem.
To demo the new functionality, we're going to add a new controller to our app by running the following command:
bin/rails generate controller boxcars
This will generate a new controller file named boxcars_controller.rb
.
In BoxCars, each BoxCar represents a tool or API that the AI can use. In this demo, we’ll keep it simple and just use the Active Record BoxCar.
require 'boxcars'
class BoxcarsController < ApplicationController
def index
session[:qa] ||= []
if params[:textcommand]
# Instantiate the Active Record BoxCar
ar_boxcar = Boxcars::ActiveRecord.new
# The user's question is passed in via the textcommand parameter
@question = params[:textcommand]
# We send the question to AI and have it run the Active Record query
@answer = ar_boxcar.run @question
# We add the question, answer, and current time into an array
# and store it in the session
@qa = [@question, @answer, Time.now ]
session[:qa].prepend @qa
end
@qalist = session[:qa]
respond_to do |format|
format.html
format.turbo_stream
end
end
end
Next, we’ll create a new view that uses Turbo to take the user’s question, send it to AI, run the Active Record BoxCar and return the results.
Create a file named index.html.erb
in the app/views/boxcars
directory and add the following code:
<div class="flex items-center justify-between">
<h1>BoxCars</h1>
<%= link_to new_article_path, class: 'btn' do %>
<%= heroicon "plus" %>
New article
<% end %>
</div>
<div class="flex items-center justify-center h-full">
<%= form_with url: boxcars_path, data: { turbo_frame: "boxcars" }, class: "w-3/5 justify-center" do |f| %>
<div class="flex items-center justify-center h-full">
<div class="flex absolute inset-0 items-center pl-3 pointer-events-none">
</div>
<%= f.text_field :textcommand, class: "search-input p-3 pl-10 text-sm", placeholder: "Ask BoxCars ..." %>
</div>
<div class="flex items-center justify-center h-full">
<%=f.submit "Submit", :class => "rounded-md bg-indigo-500 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500" %>
</div>
<% end %>
</div>
<%= turbo_frame_tag "boxcars", target: "_top" do %>
<% end %>
We’ll add the index.turb_stream.rb
file with the following info
<%= turbo_stream.replace "boxcars" do %>
<%= render "result", todo: @qa %>
<% end %>
And a _result.html.erb
file with the response
<%= turbo_frame_tag "boxcars", target: "_top" do %>
<div id="qablock" class="flex items-center justify-center">
<ul role="list" class="space-y-2 py-4 sm:space-y-4 sm:px-6 lg:px-8 w-3/5">
<% @qalist.each do |qa| %>
<li class="bg-white px-4 py-6 shadow sm:rounded-lg sm:px-6">
<div class="sm:flex sm:items-baseline sm:justify-between">
<h3 class="text-base font-medium">
<span class="text-gray-900">Question:</span>
<span class="text-gray-600"><%=qa[0]%></span>
</h3>
<p class="mt-1 whitespace-nowrap text-sm text-gray-600 sm:mt-0 sm:ml-3">
<time datetime="2021-01-28T19:24"><%= distance_of_time_in_words(Time.now, qa[2])%></time>
</p>
</div>
<div class="mt-4 space-y-6 text-sm text-gray-800">
<p> <%= qa[1] %>
</p></div>
</li>
<% end %>
</ul>
</div>
<div class="justify-center w-3/5">
</div>
<% end %>
Finally, add a new route to your routes.rb
file by adding the following line:
ruby
match 'boxcars', to: 'boxcars#index', via: [:get, :post]
In this demo, we’ve used the Active Record BoxCar, which uses OpenAI to translate the user’s text into an Active Record query. By default, the actions are read-only so no changes are made to the database. PS: It's even forgiving of typos!
Want these insights delivered? Subscribe below.