Inorder to see the behaviour of the stimulus controller we can turn on the debugger mode.
// app/javascript/controllers/index.js
import { Application } from "@hotwired/stimulus";
const application = Application.start();
application.debug = true;
Or in the browser console we can do something like
window.Stimulus.debug = true
ActiveModel::Model is useful when we come across a task where we want to create a model without a database table. It was useful for me while creating a multipart form feature.
Lets say we need a model Actor
with a name
class Actor
include ActiveModel::Model
attr_accessor :name
validates :name, presence: true
end
irb> actor = Actor.new(name: 'Sajan')
irb> actor.name #sajan
irb> actor.valid? #true
If we want to declare the attribute type then we can use ActiveModel::Attributes
class Actor
include ActiveModel::Model
include ActiveModel::Attributes
attribute :name, :string, default: ""
attribute :awards, array: true, default: []
end
irb> actor = Actor.new(name: 'Sajan', awards: ['Academy', 'Nobel'])
irb> actor.name #sajan
irb> actor.awards # ['Academy', 'Nobel']
irb> actor.valid? #true
When working with Turbo Frames, we can only update a single frame at a time that matches the DOM ID by directly interacting with it. However, what if we need to update any part of the page with a single request? In such cases, Turbo Stream comes to the rescue. Turbo Streams let us change any part of the page in response to updates sent over a WebSocket connection, SSE or other transport.
Turbo Streams supports various types of Actions
, these action with a target ID declares what should happen to HTML inside it.
<turbo-stream action="replace" target="message_1">
<template>
<div id="message_1">
This div will replace the existing element with the DOM ID "message_1".
</div>
</template>
</turbo-stream>
We have a turbo stream helper to write this as
<%= turbo_stream.replace "message_1", partial: 'partial_file_name', locals: {value1: 'value1'} >
Suppose we have an index
page that displays a list of users
along with a widget showing the total user count
. Additionally, each user in the list is accompanied by a delete button.
<div id="total-user-count">
Total Users: <%= @users.count %>
</div>
<ul>
<% @users.each do |user| %>
<li id="#{user}">
<%= user.name %>
<%= button_to "Delete", user_path(user), method: :delete, form: { data: { turbo_confirm: "Are you sure?" } } %>
</li>
<% end %>
</ul>
Now in our Users controller destroy
action we will have
def destroy
@user = User.find(params[:id])
respond_to do |format|
format.turbo_stream
end
end
This will look for a file called destroy.turbo_stream.erb
which will look like this
<%= turbo_stream.remove @user %>
<%= turbo_stream.update "total-user-count", User.count %>
Turbo frames are similar to react components. A portions of a of webpage can be defined as an turbo frame and after a request is made only the this portion can be replaced instead of a complete page refresh.
We can create a turbo frame by using the rails helper turbo_frame_tag
and wrapping the portion of the webpage inside it. Also the turbo_frame_tag will require an id for identifying the frame.
<%# index.html.erb %>
<div>
<h1>List of Employees</h1>
<@employees.each do |emp|>
<%= turbo_frame_tag emp do %>
<p><%= emp.full_name %>
<%= link_to "Edit Employee", edit_employee_path(employee) %>
<% end %>
<%end%>
</div>
When the link inside the turbo frame is clicked then the target page (for our case edit employee page) should also contain the matching id. We are using the employee_id
in our case which will look like employee_1
. We can use the dom_id also ` <%= turbo_frame_tag dom_id(emp) do %> ` but since emp is active record modal we dont need to add it.
After the request the content inside the turbo frame in the source page will be replaced with the target page content. In our case the list detail of the employee will be replaced with a form.
<%# edit.html.erb %>
<%= turbo_frame_tag @emp do %>
<%= render "form", emp: @emp %>
<% end %>
We can use data-turbo-frame="_top"
which will replace the whole source page with the target page content.
<%= link_to "Edit Employee", edit_employee_path(employee), data: { turbo_frame: "_top" }%>
We may have some links that can be outside of the turbo frame we define. But by using the data-turbo-frame
data attribute and giving it the same id of the turbo frame we can replace the content of it.
<%# source_page.html.erb %>
<%= link_to "Some page", some_page_path, data: { turbo_frame: "replace_here" } %>
<%= turbo_frame_tag "replace_here" do %>
...replace here
<%end%>
<%# some_page.html.erb %>
<%= turbo_frame_tag "replace_here" do %>
...replace with this
<%end%>
With rails 7.0+ Turbo Drive gets installed by default. We can see in the Gemfile as
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem 'turbo-rails'
And in the application.js
there are some default imports
// Entry point for the build script in your package.json
import "@hotwired/turbo-rails"
import "./controllers"
Before we had Turbolink
which was used to make the navigation of web applications faster by intercepting the click on links
. Now we have Turbo Drive which intercepts the link
click and also the form
submissions so that it does not completely refresh the web application when we navigate to a page or submit the form.
When we navigate to a web page through a click of a link, Turbo Drive will override the HTML request to an AJAX request and replaces only the <body>
of the page with the changes. The <head>
will remain unchanged thus we don’t need load the css bundle
, js bundle
, fonts
, and so on. It does the same thing with the form
submission as well i.e by transforming the HTML request to an AJAX request during form submission. Turbo Drive will only force a page reload if the assets have been changed which is tracked by the "data-turbo-track": "reload"
in the application.html.erb
When the navigation or form submission takes place under the action of Turbo Drive we won’t be able to see the native load progress indication in the web browser. Instead we will see a css-based progress indicator bar. In order to see it properly we can add this line in the app/javascript/application.js
file
Turbo.setProgressBarDelay(1)
Also to chanage the styles of the progress bar we can add the class as
.turbo-progress-bar {
height: 12px;
background-color: red;
}
Disabling in link
We need to add the data-attribute data-turbo="false"
<%=link_to "Click here", data: { turbo: false } %>
Disabling in form submission
<%= form_with model: @article, html: {data: { turbo: false } } do |form| %>
Form contents
<% end %>
Disabling for whole application
In application.js
file we need to add this
import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false
With rails 7.0+ import maps comes as default which is a new way of handling JavaScript assets.
Import maps let you import JavaScript modules using logical names that map to versioned/digested files – directly from the browser. So you can build modern JavaScript applications using JavaScript libraries made for ES modules (ESM) without the need for transpiling or bundling. This frees you from needing Webpack, Yarn, npm, or any other part of the JavaScript toolchain. All you need is the asset pipeline that’s already included in Rails. source
Step 1: Add gem 'bootstrap', '~> 5.1.3'
gem in the Gemfile, also uncomment the gem "sassc-rails"
and run bundle install
Step 2: Rename the file assets/stylesheets/application.css
to assets/stylesheets/application.scss
and in the file add
@import "bootstrap";
Step 3: In the terminal run rails assets:precompile
Step 4: Add this inside config/initializers/assets.rb
for precompiling the bootstrap.min.js
Rails.application.config.assets.precompile += %w( bootstrap.min.js popper.js )
Step 5: In the terminal run the command below to pin the compiled assets
bin/importmap pin bootstrap --download
Step 6: Inside config/importmap.rb
update the pinned assets as
pin "popper", to: 'popper.js', preload: true
pin "bootstrap", to: 'bootstrap.min.js', preload: true
Step 7: In file app/javascript/application.js
add bootstrap
import "popper"
import "bootstrap"