Adding Interactivity to a Ruby on Rails Application with Stimulus.js
🧠 Technical Summary
This guide shows you how to manage user interactions with minimal JavaScript code using Stimulus.js in Ruby on Rails projects. The goal is to create fast, targeted interactions on server-generated HTML rather than large SPA frameworks (React, Vue). Steps: Creating the Nested Post model, configuring the controller, installing Stimulus with Webpacker and using JavaScript controllers.
🎯 What You Will Learn in This Guide
- Stimulus.js installation and integration in Rails projects,
- Creating nested resources,
- The logic of the concepts of Controller, Target and Action,
- Developing user actions with Font Awesome,
- The working principle of Turbolinks and Stimulus.
⚙️ 1. Creating a Nested Post Model
Create post model:
rails generate model Post body:text shark:references
This command associates the model
Postwith the modelSharkand adds the keyshark_id.
Set model relationship:
# ~/sharkapp/app/models/shark.rb
class Shark < ApplicationRecord
has_many :posts, dependent: :destroy
validates :name, presence: true, uniqueness: true
validates :facts, presence: true
end
dependent: :destroy, when a shark is deleted it also deletes all its posts.
🧩 2. Creating the Nested Resource Controller
Edit routes:
# ~/sharkapp/config/routes.rb
Rails.application.routes.draw do
resources :sharks do
resources :posts
end
root 'sharks#index'
end
Create PostsController:
nano app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :get_shark
def create
@post = @shark.posts.create(post_params)
end
def destroy
@post = @shark.posts.find(params[:id])
@post.destroy
end
private
def get_shark
@shark = Shark.find(params[:shark_id])
end
def post_params
params.require(:post).permit(:body, :shark_id)
end
end
This controller manages the creation and deletion of posts associated with the variable
@shark.
🧱 3. Editing Views with Widgets
Add snippets to sharks/show.html.erb:
<h2>Posts</h2>
<%= render 'sharks/posts' %>
<div data-controller="posts">
<button data-action="posts#showAll">Show Older Posts</button>
<div data-target="posts.show" style="visibility:hidden">
<%= render 'sharks/all' %>
</div>
</div>
data-controller,data-targetanddata-actionare the three basic components of Stimulus.
⚡ 4. Stimulus and Webpacker Installation
Yarn Setup
sudo apt update
sudo apt install yarn
Add Webpacker Gem
# ~/sharkapp/Gemfile
gem 'turbolinks', '~> 5'
gem 'webpacker', '~> 4.x'
Install Gem:
bundle install
bundle exec rails webpacker:install
Stimulus Setup
bundle exec rails webpacker:install:stimulus
Webpack configuration:
// ~/sharkapp/app/javascript/packs/application.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
const application = Application.start()
const context = require.context("../controllers", true, /\.js$/)
application.load(definitionsFromContext(context))
This code automatically installs Stimulus controllers.
Security policy setting:
# ~/sharkapp/config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.connect_src :self, :https, 'http://localhost:3035', 'ws://localhost:3035' if Rails.env.development?
end
🧠 5. Interaction with Stimulus Particles
New post form (_posts.html.erb):
<div data-controller="posts">
<%= form_with model: [@shark, @shark.posts.build], data: { action: "posts#addBody" } do |form| %>
<%= form.text_area :body, placeholder: "Your post here", data: { target: "posts.body" } %>
<br>
<%= form.submit %>
<% end %>
<ul data-target="posts.add"></ul>
</div>
Task listing (_all.html.erb):
<% for post in @shark.posts %>
<ul>
<li class="post">
<%= post.body %>
<%= button_tag "Görevi Kaldır", data: { controller: "posts", action: "posts#remove" } %>
<%= button_tag "Beğen", data: { controller: "posts", action: "posts#upvote" } %>
</li>
</ul>
<% end %>
Each button triggers a different action on the Stimulus controller.
💻 6. Stimulus Controller Writing
// ~/sharkapp/app/javascript/controllers/posts_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = ["body", "add", "show"]
addBody() {
let content = this.bodyTarget.value
this.addTarget.insertAdjacentHTML('beforebegin', "<li>" + content + "</li>")
}
showAll() {
this.showTarget.style.visibility = "visible"
}
upvote() {
let post = event.target.closest(".post")
post.insertAdjacentHTML('beforeend', '<i class="fa fa-check-circle"></i>')
}
remove() {
let post = event.target.closest(".post")
post.style.visibility = "hidden"
}
}
This controller manages adding, showing, liking and removing posts on the DOM.
❓ Frequently Asked Questions (FAQ)
1. Why should Stimulus be preferred over React?
It provides a lightweight and simple JavaScript layer in server-side renderings, eliminating the need for complex state management.
2. Do Stimulus and Turbolinks work together?
Yes, Turbolinks optimizes page transitions while Stimulus manages DOM interactions.
3. What do Controller, Target and Action do?
Controller: JS class, Target: element to be accessed, Action: method that manages events.
4. Why was Font Awesome used?
To visually indicate actions such as post likes.
5. Where should I put the stimulation controllers?
app/javascript/controllers folder; Webpacker detects this directory automatically.
🎯 Result
You can now create small but effective interactions with Stimulus.js in your Ruby on Rails application. This approach provides a modern user experience while maintaining the classic MVC structure.
💡 Test your Stimulus-powered Rails application on the GenixNode platform now!

