This article is part of the Ruby for AI series, teaching you how to build web applications that can serve AI-powered features using Ruby on Rails.
Understanding the Request Flow
Every web request in Rails follows a predictable path. A user sends a request, Rails matches it to a route, a controller processes it, and a view renders the response. Mastering this flow is essential for building applications that can handle AI workloads, from processing form inputs to displaying model predictions.
Defining Routes
Routes live in config/routes.rb and map URLs to controller actions. Rails uses a DSL that reads like English.
# config/routes.rb
Rails.application.routes.draw do
root "predictions#index"
get "/predictions", to: "predictions#index"
get "/predictions/new", to: "predictions#new"
post "/predictions", to: "predictions#create"
# Resourceful routes generate all standard paths
resources :sentiments, only: [:index, :create]
end
Run rails routes to see all defined paths. For AI applications, you'll often need custom routes for batch processing or API endpoints.
Building Controllers
Controllers coordinate between the request and your application logic. They receive parameters, invoke models, and decide what to render.
# app/controllers/predictions_controller.rb
class PredictionsController < ApplicationController
def index
@recent_predictions = Prediction.last(10)
end
def new
@prediction = Prediction.new
end
def create
@prediction = Prediction.new(prediction_params)
# Simple AI simulation: classify text sentiment
@prediction.result = classify_sentiment(@prediction.input_text)
if @prediction.save
redirect_to predictions_path, notice: "Prediction complete!"
else
render :new, status: :unprocessable_entity
end
end
private
def prediction_params
params.require(:prediction).permit(:input_text)
end
# Rule-based sentiment classifier (no external API)
def classify_sentiment(text)
positive_words = %w[good great excellent love happy best amazing]
negative_words = %w[bad terrible awful hate worst horrible sad]
words = text.downcase.split
score = words.count { |w| positive_words.include?(w) } -
words.count { |w| negative_words.include?(w) }
case score
when 1.. then "positive"
when 0 then "neutral"
else "negative"
end
end
end
Notice how params requires explicit permitting for security. The classify_sentiment method demonstrates how to embed lightweight AI logic directly in your application.
Working with Parameters
Parameters arrive from URLs, forms, and query strings. Access them through the params hash.
# URL: /predictions?category=sentiment
params[:category] # "sentiment"
# Form submission with nested attributes
params.require(:prediction).permit(:input_text, :model_type)
# Array parameters
params[:tags] # ["ruby", "ai", "rails"]
Always use strong parameters to prevent mass assignment vulnerabilities.
Rendering Responses
Controllers can render multiple response types. Rails infers the format from the request or explicit calls.
def show
@prediction = Prediction.find(params[:id])
respond_to do |format|
format.html # renders show.html.erb
format.json { render json: @prediction }
format.text { render plain: @prediction.result }
end
end
# Explicit rendering
render :edit # renders edit view
render json: { status: "ok" }
render plain: "Done"
redirect_to root_path # 302 redirect
For AI APIs, JSON responses are common. For user-facing features, HTML views with partial updates work well.
Creating Views
Views present data using ERB templates. Keep them simple; complex logic belongs in controllers or helpers.
<!-- app/views/predictions/index.html.erb -->
<h1>Recent Sentiment Predictions</h1>
<table>
<thead>
<tr>
<th>Input</th>
<th>Result</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<% @recent_predictions.each do |prediction| %>
<tr>
<td><%= truncate(prediction.input_text, length: 50) %></td>
<td class="<%= prediction.result %>">
<%= prediction.result.capitalize %>
</td>
<td><%= time_ago_in_words(prediction.created_at) %> ago</td>
</tr>
<% end %>
</tbody>
</table>
<%= link_to "New Prediction", new_prediction_path %>
<!-- app/views/predictions/new.html.erb -->
<h1>Analyze Sentiment</h1>
<%= form_with model: @prediction do |f| %>
<% if @prediction.errors.any? %>
<div class="errors">
<% @prediction.errors.full_messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
</div>
<% end %>
<div>
<%= f.label :input_text, "Enter text to analyze:" %>
<%= f.text_area :input_text, rows: 4, required: true %>
</div>
<%= f.submit "Analyze" %>
<% end %>
A Complete Mini-Application
Here is the model to support this example:
# app/models/prediction.rb
class Prediction < ApplicationRecord
validates :input_text, presence: true, length: { maximum: 1000 }
before_save :set_processed_at, if: :result_changed?
private
def set_processed_at
self.processed_at = Time.current
end
end
# db/migrate/xxx_create_predictions.rb
class CreatePredictions < ActiveRecord::Migration[7.1]
def change
create_table :predictions do |t|
t.text :input_text, null: false
t.string :result
t.datetime :processed_at
t.timestamps
end
add_index :predictions, :result
add_index :predictions, :created_at
end
end
Key Patterns for AI Applications
Background Processing: For slow predictions, use Active Job:
def create
@prediction = Prediction.create!(prediction_params)
AnalyzeSentimentJob.perform_later(@prediction)
redirect_to @prediction, notice: "Processing..."
end
Streaming Responses: For real-time AI outputs, use Action Cable or SSE.
Caching: Cache frequent predictions:
Rails.cache.fetch("sentiment/#{text.hash}") do
classify_sentiment(text)
end
Summary
You now understand how requests flow through Rails: routes match URLs, controllers process logic, and views render responses. This foundation lets you build AI-powered features, from simple classifiers to complex pipelines. The sentiment example shows how to embed intelligence without external dependencies, perfect for prototyping and learning.
Next in Ruby for AI: connecting to real machine learning models and building APIs that serve predictions at scale.
Top comments (0)