This README would normally document whatever steps are necessary to get the application up and running.
Things you may want to cover:
-
Ruby version
-
System dependencies
-
Configuration
-
Database creation
-
Database initialization
-
How to run the test suite
-
Services (job queues, cache servers, search engines, etc.)
-
Deployment instructions
-
...
Ruby on Rails 7 App to play with Hotwire & Turbo
rails new turbo-rails-app --css=sass --javascript=esbuild --database=postgresql- To install dependencies and set up db
bin/setup - Run the rails server, and the scripts that precompile the CSS and the JavaScript code with the
bin/devcommand- This runs (yarn stuff is defined in
package.json):
- This runs (yarn stuff is defined in
# Procfile.dev
web: bin/rails server -p 3000
js: yarn build --watch
css: yarn build:css --watch-
To start server
bin/dev -
Scripts live in /bin folder of the Rails app.
-
In Gemfile
gem "simple_form"
bundle install
bin/rails generate simple_form:install- Generate system tests with:
bin/rails g system_test quotes - Run with:
bin/rails test:system
Our app/assets/stylesheets/ folder will contain four elements:
- The application.sass.scss manifest file to import all our styles.
- A mixins/ folder where we'll add Sass mixins.
- Only contains one file
_media.scss- defining breakpoints of media queries
- Only contains one file
- A config/ folder where we'll add our variables and global styles.
- Contains the "look and feel" of the app
- A components/ folder where we'll add our components.
- A layouts/ folder where we'll add our layouts.
- First part of turbo installed by default in Rails 7 applications.
gem "turbo-rails"
// app/javascript/application.js
// Entry point for the build script in your package.json
import "@hotwired/turbo-rails"
import "./controllers"- Turbo Drive speeds up our Ruby on Rails applications by converting all link clicks and form submissions into AJAX requests.
- To disable Turbo Drive on a link or a form, we need to add the
data-turbo="false"data attribute on it.
<main class="container">
<div class="header">
<h1>Quotes</h1>
<%= link_to "New quote",
new_quote_path,
class: "btn btn--primary",
data: { turbo: false } %>
</div>
<%= render @quotes %>
</main>- To disable turbo drive for the whole application:
// app/javascript/application.js
import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false- Turbo Drive compares the DOM elements with data-turbo-track="reload" in the of the current HTML page and the of the response. If there are differences, Turbo Drive will reload the whole page.
// app/assets/stylesheets/components/_turbo_progress_bar.scss
.turbo-progress-bar {
background: linear-gradient(to right, var(--color-primary), var(--color-primary-rotate));
}// app/assets/stylesheets/application.sass.scss
// All the previous code
@import "components/turbo_progress_bar";- TLDR: Making all CRUD actions on quotes happen on the index page.
- Turbo Frames are independent pieces of a web page that can be appended/prepended/replaced or removed without a complete page refresh (and without writing any JS).
- The below links to
<%# app/views/quotes/index.html.erb %>
<main class="container">
<%= turbo_frame_tag "first_turbo_frame" do %>
<div class="header">
<h1>Quotes</h1>
<%= link_to "New quote", new_quote_path, class: "btn btn--primary" %>
</div>
<% end %>
<%= render @quotes %>
</main>The new "form" only.
<%# app/views/quotes/new.html.erb %>
<main class="container">
<%= link_to sanitize("← Back to quotes"), quotes_path %>
<div class="header">
<h1>New quote</h1>
</div>
<%= turbo_frame_tag "first_turbo_frame" do %>
<%= render "form", quote: @quote %>
<% end %>
</main>- Rule 1: When clicking a link within a Turbo Frame, Turbo expects the same id on the target page. It replaces the Frame's content on the source page with the Frame's content on the target page.
- Rule 2: When clicking a link within a Turbo Frame, if there is no Turbo frame with the same id on the target page, the frame disappears and we are served an error (Response has no matching element is logged in the dev tool console).
- Rule 3: A link can target another frame other than the one it is directly nested in thanks to the
data-turbo-framedata attribute. - NB: Special frame
_toprepresents the whole page.
- Use turbo frames to embed edit page in index by passing
quotevariables from _quote.html.erb to edit.
- The
turbo_frame_tagcan be passed a string to be converted to adom_id.
# If the quote is persisted and its id is 1:
dom_id(@quote) # => "quote_1"
# If the quote is a new record:
dom_id(Quote.new) # => "new_quote"- Fix show and delete to use
data: { turbo_frame: "_top"}to fix vanishing pages side effect.
- Forms in Rails 7 are now submitted with the TURBO_STREAM format.
- To delete using turbo stream:
# app/controllers/quotes_controller.rb
def destroy
@quote.destroy
respond_to do |format|
format.html { redirect_to quotes_path, notice: "Quote was successfully destroyed." }
format.turbo_stream
end
end<%# app/views/quotes/destroy.turbo_stream.erb %>
<%= turbo_stream.remove "quote_#{@quote.id}" %>
- Turbo Stream can perform the following actions:
# Remove a Turbo Frame
turbo_stream.remove
# Insert a Turbo Frame at the beginning/end of a list
turbo_stream.append
turbo_stream.prepend
# Insert a Turbo Frame before/after another Turbo Frame
turbo_stream.before
turbo_stream.after
# Replace or update the content of a Turbo Frame
turbo_stream.update
turbo_stream.replace- The turbo_stream helper expects a partial and locals as arguments to know which HTML it needs to append, prepend, replace from the DOM.
- Turbo Frames + TURBO_STREAM format allows for performing precise operations on pieces of our web pages without having to write a single line of JavaScript, therefore preserving the state of our web pages.
For a new record, the following is equivalent:
turbo_frame_tag "new_quote"
turbo_frame_tag Quote.new
turbo_frame_tag @quote
- A controller action needs to be made aware that it should support HTML and TURBO_STREAM if you are using both formats.
respond_to do |format|
format.html { redirect_to quotes_path, notice: "Quote was successfully created." }
format.turbo_stream
end- A corresponding turbo_stream view must be created.
<%# app/views/quotes/create.turbo_stream.erb %>
<%= turbo_stream.prepend "quotes", partial: "quotes/quote", locals: { quote: @quote } %>
<%= turbo_stream.update Quote.new, "" %>
Alternate syntax:
<%# app/views/quotes/create.turbo_stream.erb %>
<%= turbo_stream.prepend "quotes" do %>
<%= render partial: "quotes/quote", locals: { quote: @quote } %>
<% end %>
<%= turbo_stream.update Quote.new, "" %>
In RoR, the following are equivalent:
render partial: "quotes/quote", locals: { quote: @quote }
render @quote
So the above becomes:
<%# app/views/quotes/create.turbo_stream.erb %>
<%= turbo_stream.prepend "quotes" do %>
<%= render @quote %>
<% end %>
<%= turbo_stream.update Quote.new, "" %>
Anf can be further shortened as (since block syntax is not needed):
<%# app/views/quotes/create.turbo_stream.erb %>
<%= turbo_stream.prepend "quotes", @quote %>
<%= turbo_stream.update Quote.new, "" %>