Sitemap
Jungletronics

Education & STEAM Learning by building. Practical explorations in AI, programming, electronics, robotics, IoT, and other STEAM topics — from ideas and experiments to working products. Follow and build with us.

Bookstore — Final Implementation-9

QuickHotSeries — Lightning-Fast Insights, Now in Version 9

4 min readFeb 26, 2025

--

In this episode, we’ll automatically update the entire interface using Turbo Stream.

Press enter or click to view image in full size
The Result
Press enter or click to view image in full size
If this is your first time in the series, consider starting with the introductory episode.

Let’s Get Started!

Note: if you get stuck, please see my repo.

1.Create Book

Press enter or click to view image in full size

Model:

app/models/book.rb

after_create :create_chart

private

def create_chart
broadcast_append_to('books_charts_channel', partial: '/dashboard/charts/books/chart', locals: { book: self }, target: 'books_charts')
end

2.Book & Chart Partials

app/views/dashboard/charts/books/_books.html.erb

<section class="flex flex-col w-full border">
<h3 class="font-bold text-lg">Sells By Book</h3>
<div class="flex flex-col" id='books_charts'>
<% Book.all.each do |book| %>
<%= render 'dashboard/charts/books/chart', book: book %>
<% end %>
</div>
</section>

app/views/dashboard/charts/books/_chart.html.erb

<div data-controller="book" data-book-sells="<%= latest_sells(book.id) %>" data-book-id="<%= book.id %>"  data-book-name="<%= book.name %>" id="book_<%= book.id %>" >
<canvas id="book_chart_<%= book.id %>" style="width:100%;max-width:700px"></canvas>
</div>

3.Streaming from Tag: Two Channels

app/views/dashboard/index.html.erb

<%= turbo_stream_from 'books_charts_channel' %>
<%= turbo_stream_from 'sells_charts_channel' %>

4.Real-Time Updates: Broadcasting in the Model for Book Sales

Press enter or click to view image in full size

app/models/sell.rb

after_create_commit :update_charts

private

def update_charts
broadcast_replace_to('sells_charts_channel', partial: '/dashboard/charts/days/chart', locals: {}, target: 'days_chart')
broadcast_replace_to('sells_charts_channel', partial: '/dashboard/charts/revenue/chart', locals: {}, target: 'revenue_chart')
broadcast_replace_to('sells_charts_channel', partial: '/dashboard/charts/books/chart', locals: { book: book }, target: "book_#{book.id}")
end

The after_create_commit :update_charts line sets up a callback that triggers the update_charts method after a record is successfully created in the database.

In update_charts:

  • broadcast_replace_to: This method sends updates to a specified channel (sells_charts_channel) to refresh specific chart components on the client side.
  • Partial Views: It uses three partial views to render updated charts:
  • '/dashboard/charts/days/chart': Updates the daily sales chart.
  • '/dashboard/charts/revenue/chart': Updates the revenue chart.
  • '/dashboard/charts/books/chart': Updates the chart for a specific book, using the book instance to pass its data.

This approach ensures that the charts reflect the latest data immediately after a new sell is recorded.

Get J3’s stories in your inbox

Join Medium for free to get updates from this writer.

5.Confirming all the view & chart partials & index

app/views/dashboard/charts/days/_chart.html.erb

<div data-controller="days" data-day-sells="<%= sells_per_day %>" id="days_chart">
<canvas id="daySells" style="width:100%;max-width:700px"></canvas>
</div>

app/views/dashboard/charts/days/_days.html.erb

<section class="flex flex-col w-full border">
<h3 class="font-bold text-lg">Sells Per Day</h3>

<%= render 'dashboard/charts/days/chart' %>
</section>

app/views/dashboard/charts/revenue/_chart.html.erb

<div data-controller="revenue" data-revenue-sells="<%= revenue_from_sells %>" id="revenue_chart">
<canvas id="revenueSells" style="width:100%;max-width:700px"></canvas>


<table class="my-5 w-full border-collapse border-2">
<thead>
<tr class="bg-indigo-200 text-indigo-800">
<th class="w-1/3 border-2 px-4 py-2 text-sm text-center font-semibold">ID</th>
<th class="w-1/3 border-2 px-4 py-2 text-sm text-center font-semibold">Name</th>
<th class="w-1/3 border-2 px-4 py-2 text-sm text-center font-semibold">Revenue</th>
</tr>
</thead>
<tbody>
<% Book.includes(:sells).find_each do |book| %>
<tr class="even:bg-indigo-50">
<td class="w-1/3 border-2 px-4 py-2 text-sm text-center"><%= book.id %></td>
<td class="w-1/3 border-2 px-4 py-2 text-sm text-center"><%= book.name %></td>
<td class="w-1/3 border-2 px-4 py-2 text-sm text-center font-semibold">
<%= number_to_currency(book.sells.sum(:total), unit: "USD ") %>
</td>
</tr>
<% end %>
</tbody>
</table>

<div class="mt-4 p-4 text-lg font-bold text-indigo-800 border-t-2">
Total Revenue: <%= number_to_currency(Sell.all.sum(:total), unit: "USD ") %>
</div>
</div>

app/views/dashboard/charts/revenue/_revenue.html.erb

<section class="flex flex-col w-full border">
<h3 class="font-bold text-lg">Revenue</h3>

<%= render "dashboard/charts/revenue/chart" %>
</section>

app/views/dashboard/index.html.erb

<%= turbo_stream_from 'sells_charts_channel' %>

That’s all Folks!

Press enter or click to view image in full size

Thank you!

If you enjoyed it, please hit the applause button to help others see this. Thanks!

Let me know if you need any further clarification! We’re striving to make this tutorial as clear as possible.

In the next episode, we’ll tackle timestamps. Currently, our database records days as strings, which isn’t ideal for production, right?

Note: Database Renewal

Ubuntu Terminal


docker compose restart

Vscode Terminal

rails db:drop
rails db:migrate
rails s -b 0.0.0.0 -p 3001

# Enter a new User

--

--

J3
J3

Written by J3

Computer Engineer | AI & Data Systems | IoT & Embedded Systems | Product Creator — From Idea to Product | Founder @ Jungletronics & Kidstronics

Jungletronics
Jungletronics

Published in Jungletronics

Education & STEAM Learning by building. Practical explorations in AI, programming, electronics, robotics, IoT, and other STEAM topics — from ideas and experiments to working products. Follow and build with us.