TODAY I LEARN

Enable Stimulus Debug Mode


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 in Rails


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

Turbo Stream in Rails 7


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 Actions

Turbo Streams supports various types of Actions, these action with a target ID declares what should happen to HTML inside it.

  • Append: Add new HTML content to the end of the target element.
  • Prepend: Add new HTML content to the beginning of the target element.
  • Replace: Replace the whole target element with new HTML content.
  • Update: Replace only the content of the target element with the given content.
  • Remove: Remove the target element.
  • Before: Inserts the new content before the target element.
  • After: Inserts the new content after the target element.

Syntax

<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 Frame in Rails 7


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.


Creating a turbo frame

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>

Replacing the portion with new content

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 %>

Replacing the whole page with new content

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%>

Turbo Drive in Rails 7


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 the Turbo Drive

  1. Disabling in link

    We need to add the data-attribute data-turbo="false"

      <%=link_to "Click here", data: { turbo: false } %>
    
  2. Disabling in form submission

      <%= form_with model: @article, html: {data: { turbo: false } } do |form| %>
         Form contents
      <% end %>
    
  3. Disabling for whole application

    In application.js file we need to add this

       import { Turbo } from "@hotwired/turbo-rails"
       Turbo.session.drive = false
    

Import Maps in Rails 7 and Adding Bootstrap 5


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


Adding Bootstrap

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.jsadd bootstrap

import "popper"
import "bootstrap"