Services About Case Studies Blog
Let's Talk
  • Services
  • Contact
  • About
  • Case Studies
  • Blog
Hire us

All posts

Tutorials

Creating Dynamic Forms with Hotwire and Turbo Stream in Rails 7: A Complete Guide

author avatar Ariel Arzamendia. - 2024-01-07

Creating Dynamic Forms with Hotwire and Turbo Stream in Rails 7:

class Location < ApplicationRecord has_many :car_brands end

app/models/car_brand.rb

class CarBrand < ApplicationRecord belongs_to :location has_many :models end

app/models/model.rb

class Model < ApplicationRecord belongs_to :car_brand has_many :colors end

app/models/color.rb

class Color < ApplicationRecord belongs_to :model end

Notice that we also added the “rental” stimulus controller in the form in order to handle the JavaScript from the view. We also added data-target “location” and an action to the location select. We will explain that in the next steps.</p><pre><code class="language-language-ruby">#app/views/rentals/new.html.erb &lt;%= form_with url: rentals_path, method: :post, data: { controller: &quot;rental&quot; } do |form| %&gt; &lt;%= form.label :first_name %&gt; &lt;%= form.test_field :first_name, autocomplete: &quot;given_name&quot; %&gt;

&lt;%= form.label :last_name %&gt; &lt;%= form.text_field :last_name, autocomplete: &quot;family_name&quot; %&gt;

&lt;%= form.label :address %&gt; &lt;%= form.text_field :address, autocomplete: &quot;address&quot; %&gt;

&lt;%= form.label :email %&gt; &lt;%= form.email_field :email, autocomplete: &quot;email&quot; %&gt;

&lt;%= form.label :location_id, &#39;Location&#39; %&gt; &lt;%= form.collection_select :location_id, Location.all, :id, :name,

  include_blank: true,
  data: { rental_target: &quot;location&quot;, action: &quot;rental#updateCarBrands&quot; } %&gt;

&lt;div id=&quot;car_brands&quot;&gt;

&lt;%= render &#39;car_brands&#39;, car_brands: [] %&gt;

&lt;/div&gt;

&lt;div id=&quot;models&quot;&gt;

&lt;%= render &#39;models&#39;, models: [] %&gt;

&lt;/div&gt;

&lt;div id=&quot;colors&quot;&gt;

&lt;%= render &#39;colors&#39;, colors: [] %&gt;

&lt;/div&gt;

&lt;%= form.submit &quot;Rent Car&quot; %&gt; &lt;% end %&gt;

// app/javascript/controllers/rental_controller.js import { Controller } from &#39;@hotwired/stimulus&#39;; import { Turbo } from &#39;@hotwired/turbo-rails&#39;;

export default class extends Controller { static targets = [&quot;location&quot;, &quot;carBrands&quot;, &quot;models&quot;]

connect() {

console.log(&quot;Rental controller connected&quot;);

}

updateCarBrands() {

const locationId = this.locationTarget.value;
this.fetchAndUpdate(`/update_car_brands?location_id=${locationId}`);

}

updateModels() {

const brandId = this.carBrandsTarget.value;
this.fetchAndUpdate(`/update_models?brand_id=${brandId}`);

}

updateColors() {

const modelId = this.modelsTarget.value;
this.fetchAndUpdate(`/update_colors?model_id=${modelId}`);

}

fetchAndUpdate(url) {

fetch(url, {
  method: &#39;GET&#39;,
  headers: {
    Accept: &#39;text/vnd.turbo-stream.html, text/html, application/xhtml+xml&#39;,
    &#39;X-Requested-With&#39;: &#39;XMLHttpRequest&#39;,
    &#39;X-CSRF-Token&#39;: this.getMetaContent(&#39;csrf-token&#39;),
    &#39;Cache-Control&#39;: &#39;no-cache&#39;,
  },
})
  .then(response =&gt; response.ok ? response.text() : Promise.reject(&#39;Response not OK&#39;))
  .then(html =&gt; Turbo.renderStreamMessage(html))
  .catch(error =&gt; console.error(&#39;Error:&#39;, error));

}

getMetaContent(name) {

return document.querySelector(`meta[name=&quot;${name}&quot;]`).getAttribute(&#39;content&#39;);

} }

app/controllers/rentals_controller.rb

class RentalsController &lt; ApplicationController def update_car_brands

car_brands = CarBrand.where(location_id: params[:location_id])

render turbo_stream: turbo_stream.replace(&#39;car_brands&#39;, partial: &#39;car_brands&#39;, locals: { car_brands: car_brands })

end

def update_models

models = Models.where(brand_id: params[:brand_id])


render turbo_stream: turbo_stream.replace(&#39;models&#39;, partial: &#39;models&#39;, locals: { models: models })

end

def update_colors

colors = Colors.where(model_id: params[:model_id])

render turbo_stream: turbo_stream.replace(&#39;colors&#39;, partial: &#39;colors&#39;, locals: { colors: colors })

end end

For that we are building partials that will have the updated content from the turbo stream.</p><p>Notice that the car_brands and model partial have data-target and actions. These are going to be rendered in the main form. Each partial calls its own action from the stimulus controller and has its own data-target</p><pre><code class="language-language-ruby">#app/views/rentals/_car_brands.html.erb &lt;%= form_with model: Rental.new do |form| %&gt; &lt;%= form.label :car_brand_id, &#39;Car Brand&#39; %&gt; &lt;%= form.collection_select :car_brand_id, car_brands, :id, :name,

  include_blank: true,
  data: { rental_target: &quot;carBrands&quot;, action: &quot;rental#updateModels&quot; } %&gt;

&lt;% end %&gt;

app/views/rentals/_models.html.erb

&lt;%= form_with model: Rental.new do |form| %&gt; &lt;%= form.label :model_id, &#39;Model&#39; %&gt; &lt;%= form.collection_select :model_id, models, :id, :name,

  include_blank: true,
  data: { rental_target: &quot;models&quot;, action: &quot;rental#updateColors&quot; } %&gt;

&lt;% end %&gt;

#app/views/rentals/_colors.html.erb &lt;%= form_with model: Rental.new do |form| %&gt; &lt;%= form.label :color_id, &#39;Color&#39; %&gt; &lt;%= form.collection_select :color_id, colors, :id, :name,

  include_blank: true,
  data: { rental_target: &quot;colors&quot; } %&gt;

&lt;% end %&gt;

config/routes.rb

Rails.application.routes.draw do # Other routes get &#39;update_car_brands&#39;, to: &#39;rentals#update_car_brands&#39; get &#39;update_models&#39;, to: &#39;rentals#update_models&#39; get &#39;update_colors&#39;, to: &#39;rentals#update_colors&#39; end

NEWSLETTER

Go Deeper than Development

The Daring Bits newsletter cuts through the noise to bring you hardwon wisdom about fintech, insurance, and of course software development.

RSS FEED

Stay Connected Instantly

Subscribe to our RSS feed for instant notifications when we publish new content. No algorithms, no delays - just pure, unfiltered updates.

Subscribe to RSS

Perfect for:

Feedly Inoreader NewsBlur

Have a project in mind?

Get started with a free consultation

Check Availability

Company

  • Jobs
  • Blog
  • About
  • Services
  • Technology
  • Fintech

Work

  • Truth Social
  • JailCore
  • TrustCare
  • BCharitable
  • Agency Rocket
  • SDS

120 19th Street North

Suite 2005

Birmingham, AL 35203 usa flag

(877) 919-4322

All rights reserved.