<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aleksandr Ulanov</title>
    <description>The latest articles on DEV Community by Aleksandr Ulanov (@ualeks).</description>
    <link>https://dev.to/ualeks</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F628621%2F5e172633-32f2-42ab-bbd8-a22ea5dd62ac.jpg</url>
      <title>DEV Community: Aleksandr Ulanov</title>
      <link>https://dev.to/ualeks</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ualeks"/>
    <language>en</language>
    <item>
      <title>Using Rails Service Objects</title>
      <dc:creator>Aleksandr Ulanov</dc:creator>
      <pubDate>Sat, 27 Aug 2022 16:29:00 +0000</pubDate>
      <link>https://dev.to/ualeks/using-rails-service-objects-l5p</link>
      <guid>https://dev.to/ualeks/using-rails-service-objects-l5p</guid>
      <description>&lt;p&gt;If you're developing web apps using Ruby on Rails, you probably already know that Rails is an MVC (Model-View-Controller) framework, which means that you have your Models responsible for data, Views responsible for templates and Controllers responsible for requests handling. But the bigger your app gets, the more features it has - the more business logic you will have. And here comes the question, where do you put your business logic? Obviously it's not views that should handle it. Controllers or Models? That will make them fat and unreadable pretty soon. That's where Service Objects come to the rescue. In this article we'll find out what are Service Objects and how you can use them to make your app cleaner and kepp it maintainable.&lt;/p&gt;

&lt;p&gt;Let's say you have a project for handling cab trips, we'll take a look at the particular controller action, which updates trip records. But it should not only update trips based on user input params (e.g. starting address, destination address, riders count, etc.), but it should also calculate some fields based on those params and save it to the database. So, we have a controller action like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TripsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update_trip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@trip&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_trip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;distance_and_duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_trip_distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:start_address&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                                                                 &lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:destination_address&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_trip_distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;distance: &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;duration: &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem here is that you’ve added at least ten lines to your controller, but this code does not really belong to the controller. Also if you want to update trips in another controller, for example by importing them from a csv file, you will have to repeat yourself and rewrite this code. Or you create a service object, i.e. &lt;code&gt;TripUpdateService&lt;/code&gt; and use that in any place you need to update trips.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Service Objects?
&lt;/h2&gt;

&lt;p&gt;Basically a service object is a Plain Old Ruby Object ("PORO"), a Ruby class that returns a predictable response and is designed to execute one single action. So it encapsulates a piece of business logic.&lt;/p&gt;

&lt;p&gt;The job of a service object is to encapsulate functionality, execute one service, and provide a single point of failure. Using service objects also prevents developers from having to write the same code over and over again when it’s used in different parts of the application.&lt;/p&gt;

&lt;p&gt;All service objects should have three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an initialization method&lt;/li&gt;
&lt;li&gt;a single public method&lt;/li&gt;
&lt;li&gt;return a predictable response after execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's replace our controller logic by calling a service object for trip updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TripsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;TripUpdateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@trip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update_trip&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@trip&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks much cleaner, right? Now let's take a look at how do we implement a service object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing a Service Object
&lt;/h2&gt;

&lt;p&gt;In a Rails app there are two folders which are commonly used for storing service objects: &lt;code&gt;lib/services&lt;/code&gt; and &lt;code&gt;app/services&lt;/code&gt;. Basically you can choose whichever you want, but we'll use &lt;code&gt;app/services&lt;/code&gt; for this article.&lt;/p&gt;

&lt;p&gt;So we'll add a new Ruby class (our service object) in &lt;code&gt;app/services/trip_update_service.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/trip_update_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TripUpdateService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trip&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_trip&lt;/span&gt;
    &lt;span class="n"&gt;distance_and_duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_trip_distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:start_address&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                                                                 &lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:destination_address&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_trip_distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;distance: &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;duration: &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, service object added, now you can call &lt;code&gt;TripUpdateService.new(trip, params).update_trip&lt;/code&gt; anywhere in your app, and it will work. Rails will load this object automatically because it autoloads everything under &lt;code&gt;app/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This already looks pretty clean, but we can actually make it even better. We can make service object to execute itself when called, so we can make calls to it even shorter. If we want to reuse this behavior for other service objects, we can add a new class called &lt;code&gt;BaseService&lt;/code&gt; or &lt;code&gt;ApplicationService&lt;/code&gt; and inherit from it for our &lt;code&gt;TripUpdateService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/base_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this class method named &lt;code&gt;call&lt;/code&gt; creates a new instance of the service object with arguments or block passed to it, and then calls the &lt;code&gt;call&lt;/code&gt; method on that instance.Then we need to make our service to inherit from &lt;code&gt;BaseService&lt;/code&gt; and implement &lt;code&gt;call&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/trip_update_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TripUpdateService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trip&lt;/span&gt;
    &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;distance_and_duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_trip_distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:start_address&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                                                                 &lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:destination_address&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_trip_distance_and_duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Google&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destination_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;distance: &lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;duration: &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then let's update our controller action to call the service object correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TripsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="vi"&gt;@trip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;TripUpdateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@trip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trip_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@trip&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where should you put your service objects
&lt;/h2&gt;

&lt;p&gt;As we've discussed earlier two base folders for storing service objects are: &lt;code&gt;lib/services&lt;/code&gt; and &lt;code&gt;app/services&lt;/code&gt; and you can use whichever you want.&lt;br&gt;
Another good practice for storing your service objects will be storing them under different namespaces, i.e. you can have &lt;code&gt;TripUpdateService&lt;/code&gt;, &lt;code&gt;TripCreateService&lt;/code&gt;, &lt;code&gt;TripDestroyService&lt;/code&gt;, &lt;code&gt;SendTripService&lt;/code&gt;, and so on. But what will be common for all of them is that they're related to Trips. So we can put them under &lt;code&gt;app/services/trips&lt;/code&gt; folder, in other words under the &lt;code&gt;trips&lt;/code&gt; namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/trips/trip_update_service.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Trips&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TripUpdateService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/trips/send_trip_service.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Trips&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SendTripService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to use new namespace when calling those services, i.e. &lt;code&gt;Trips::TripUpdateService.call(trip, params)&lt;/code&gt;, &lt;code&gt;Trips::SendTripService.call(trip, params)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap your code in one transaction
&lt;/h2&gt;

&lt;p&gt;If your service object is going to perform multiple updates for different objects, you better wrap it in a transaction block. In this case Rails will rollback the transaction (i.e. all of the performed db changes) if any of the service object methods fail. This is a good practice because it will keep your db in consistency in case of a failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# archive route with all of its trips&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RouteArchiver&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="c1"&gt;# first archive the route&lt;/span&gt;
      &lt;span class="vi"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;archive!&lt;/span&gt;

      &lt;span class="c1"&gt;# then archive route trips&lt;/span&gt;
      &lt;span class="n"&gt;trips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TripsArchiver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;route: &lt;/span&gt;&lt;span class="vi"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# create a change log record&lt;/span&gt;
      &lt;span class="no"&gt;CreatChangelogService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;change: :archive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;object: &lt;/span&gt;&lt;span class="vi"&gt;@route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;associated: &lt;/span&gt;&lt;span class="n"&gt;trips&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# return response&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;success: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;message: &lt;/span&gt;&lt;span class="s2"&gt;"Route archived successfully"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a simple example of updating multiple records in a single transaction. If any of the updates fails with an exception (e.g. route can't be archived, changelog create fails), the transaction will be rolled back and the db will be in a consistent state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passing Data to Service Objects and Returning Response
&lt;/h2&gt;

&lt;p&gt;Basically you can pass to your service objects almost anything, depending on the operations they perform: ActiveRecord objects, hashes, arrays, strings, integers, etc. But you should always pass the minimum amount of data to your service objects. For example, if you want to update a trip, you should pass the trip object and the params hash, but you should not pass the whole &lt;code&gt;params&lt;/code&gt; hash, because it will contain a lot of unnecessary data. So you should pass only the data you need, i.e. &lt;code&gt;TripUpdateService.call(trip, trip_params)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Service Objects can perform complex operations. They can be used to modify records in the database, send emails, perform calculations or call 3d party APIs. So it's quite possible that something can go wrong during those operations. That's why it's a good practice to return a response from your service objects. You can return a boolean value, or a hash with a boolean value and some additional data. For example, if you want to update a trip, you can return a boolean value indicating whether the trip was updated successfully or not, and you can also return the trip object itself, so you can use it in your controller action.&lt;/p&gt;

&lt;p&gt;The thing you should keep in mind though, is that your response from the service object should be predictable. It should always return the same response, no matter what. So if you return a boolean value, it should always return a boolean value, and if you return a hash, it should always return a hash with the same keys. This will make your service objects more predictable and easier to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the benefits of using Service Objects?
&lt;/h2&gt;

&lt;p&gt;Service Objects are a great way to decouple your application logic from your controllers. You can use them to separate concerns and reuse them in different parts of your application. With this pattern you get multiple benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Clean controllers.&lt;/b&gt; Controller shouldn't handle business logic. It should be only responsible for handling requests and turning the request params, sessions, and cookies into arguments that are passed into the service object to perform the action. And then perform redirect or render according to the service response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Easier testing.&lt;/b&gt; Separation of business logic to service objects also allows you to test your service objects and your sontrollers independently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Reusable Service Objects.&lt;/b&gt; A service object can be called from app controllers, background jobs, other service objects, etc. Whenever you need to perform a similar action, you can call the service object and it will do the work for you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Separation of concerns.&lt;/b&gt; Rails controllers only see services and interact with the domain object using them. This decrease in coupling makes scalability easier, especially when you want to move from a monolith to a microservice. Your services can easily be extracted and moved to a new service with minimal modification.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Service Objects Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Name rails service objects in a way that makes it obvious what they're doing.&lt;/b&gt; The name of a service object must indicate what it does. With our trips example, we can name our service object like: &lt;code&gt;TripUpdateService&lt;/code&gt;, &lt;code&gt;TripUpdater&lt;/code&gt;, &lt;code&gt;ModifyTrip&lt;/code&gt;, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Service Object should have single public method.&lt;/b&gt; Other methods must be private and be accessible only within particular service object. You can call that single public method the way you want, just be consistent and use the same naming for all your service objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Group service objects under common namespaces.&lt;/b&gt; If you have a lot of service objects, you can group them under common namespaces. For example, if you have a lot of service objects related to trips, you can group them under &lt;code&gt;Trips&lt;/code&gt; namespace, i.e. &lt;code&gt;Trips::TripUpdateService&lt;/code&gt;, &lt;code&gt;Trips::TripDestroyService&lt;/code&gt;, &lt;code&gt;Trips::SendTripService&lt;/code&gt;, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Use syntactic sugar for calling your service objects.&lt;/b&gt; Use proc syntax in your &lt;code&gt;BaseService&lt;/code&gt; or &lt;code&gt;ApplicationService&lt;/code&gt; and inherit from it in other services. then you can use just &lt;code&gt;.call&lt;/code&gt; on your service object class name to perform an action, i.e. &lt;code&gt;TripUpdateService.call(trip, params)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Don't forget to rescue exceptions.&lt;/b&gt; When service object fails, due to exception, those exceptions should be rescued and handled properly. They shopuld not propagate up to the call stack. And if an exception can't be handled correctly within the rescue block, you should raise custom exception specific to that particular service object.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Single responsibility.&lt;/b&gt; Try keeping single responsibility for each of your service objects. If you have a service object that does too many things, you can split it into multiple service objects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Service objects are a great way to decouple your application logic from your controllers. They can be used to separate concerns and reuse them in different parts of your application. This pattern can make your application more testable and easier to maintain as you add more and more features. It also makes your application more scalable and easier to move from a monolith to a microservice. If you haven't used service objects before, you should definitely try it.&lt;br&gt;
Btw, Ruby on Rails is used for this example only, you can use same pattern with other frameworks.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Hotwire: building responsive Rails apps</title>
      <dc:creator>Aleksandr Ulanov</dc:creator>
      <pubDate>Mon, 08 Aug 2022 05:48:00 +0000</pubDate>
      <link>https://dev.to/ualeks/hotwire-building-responsive-rails-apps-5e2e</link>
      <guid>https://dev.to/ualeks/hotwire-building-responsive-rails-apps-5e2e</guid>
      <description>&lt;p&gt;If you're developing modern single page applications with Ruby on Rails, you're most likely using some fancy JS framework for your UI to be updated nicely, without page reloads. And there is really not much you can do without using them, that's kind of a standard these days... until Rails got Hotwire.&lt;br&gt;
With Hotwire you can get fast and responsive web application, but without writing ton of Javascript. Well, sounds great, but what is Hotwire? In this article we'll go through the basics of Hotwire, as well as build a sample app using it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What options are there for responsive Rails apps?
&lt;/h2&gt;

&lt;p&gt;On June 25, 2013, Rails 4 was released introducing Turbolinks. What Turbolinks are doing for "responsiveness" of Rails? Turbolinks intercept all link clicks and instead of sending regular &lt;code&gt;GET&lt;/code&gt; request it sends asynchronous Javascript request (AJAX) to fetch HTML. It then merges the &lt;code&gt;head&lt;/code&gt; tag of the fetched page and replaces the whole &lt;code&gt;body&lt;/code&gt; tag of the page, so there is no full page reload. No stylesheets or scripts reloading, which meana a faster page navigation. But it still is replacing the whole &lt;code&gt;body&lt;/code&gt;, not only changed parts of the page.&lt;/p&gt;

&lt;p&gt;But what if you want to reload only parts that changed? Well, you could use Rails AJAX helpers, when you mark some elements as &lt;code&gt;data-remote='true'&lt;/code&gt;, which makes those element sending AJAX &lt;code&gt;GET/POST&lt;/code&gt; requests instead of regular &lt;code&gt;GET/POST&lt;/code&gt; requests. And Rails responds with generated JS code, which then is executed by the browser to dynamically update those parts of the page.&lt;/p&gt;

&lt;p&gt;Then we can get to some JS components on the frontend (e.g. using React) to make an app feel even more responsive. So, JS component sends an AJAX request and Rails server responds to it with JSON data. Then frontend framework transforms received JSON to DOM elements and updates DOM to reflect those changes. And it works nice, the only downside is that it's mixing server side rendering and client side rendering on a page.&lt;/p&gt;

&lt;p&gt;Another, more traditional way these days, is going all in and using React, Vue or another JS framework on the frontend to use only client-side rendering, i.e. so-called Single Page Application (SPA). With this approach frontend is a separate application which sends AJAX requests to the Rails server, and Rails is being solely a JSON API. As you probably know there's a whole lot of complexity of building, maintaining and deploying two separate apps with interchangeable data.&lt;/p&gt;

&lt;p&gt;But what if you could build an SPA w/out all that complexity of building two separate apps and writing lots of Javascript code? Here's what Hotwire can help you with.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Hotwire?
&lt;/h2&gt;

&lt;p&gt;Hotwire is an alternative approach to building SPA like web apps, rendering all of the HTML server side using Rails templates, while keeping the app fast and responsive. Keeping redering on server side makes you development experience simpler and more productive. Hotwire name basically is an abbreviation for &lt;code&gt;HTML Over the Wire&lt;/code&gt;, which means sending generated HTML instead of JSON from the server to the client. It also does not require you to write much of custom javascript code. Hotwire is made of Turbo and Stimulus.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Turbo?
&lt;/h2&gt;

&lt;p&gt;Turbo gem is the heart of Hotwire. It's a set of technologies to dynamically update the page, which speeds up navigation and form submission by dividing pages into components whiuch can be partially updated utilizing Websockets as a transport. If you ever worked with websockets in Rails, you most likely know that Rails is using ActionCable to handle websockets connection, and it's included in Rails by default. And Trubo gem consists of: Turbo Drive, Turbo Frames and Turbo Streams.&lt;/p&gt;
&lt;h3&gt;
  
  
  Turbo Drive
&lt;/h3&gt;

&lt;p&gt;Turbo Drive is used to intercept links clicks (just like Turbolinks were doing it previously) and also intercept form submissions. Turbo Drive then merges the head tag of a page and replaces the body tag of the page. Same case as with Turbolinks, no full page reload, which maybe fast for some pages, but not really as responsive as the 2022 app is expected to be, so you might consider updating only some parts of the pages, not the whole body. Here's where Turboframes come in handy.&lt;/p&gt;
&lt;h3&gt;
  
  
  Turbo Frames
&lt;/h3&gt;

&lt;p&gt;You can simply make a part of a page a Turbo Frame, by simply wrapping it in a turbo-frame tag with a unique id&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"13"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/turbo-frame&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes any interactions within the frame to be scoped to that frame. Any interaction within that framne send an AJAX request to the Rails server, and server responds with an HTML only for that frame. Which allows Turbo to automatically replace only that frame on a page. And this does not require to write any Javascript.&lt;br&gt;
But what if you want to update multiple parts of the page at the same time? That's what Turbo Streams can help you with.&lt;/p&gt;
&lt;h3&gt;
  
  
  Turbo Streams
&lt;/h3&gt;

&lt;p&gt;When user interacts with an element on the page (e.g. form/link) and Turbo Drive sends an AJAX request to the server, the server responds with an HTML, consisting of Turbo Stream elements. And those are like directions for Turbo to follow in order to update affected parts of the page. Turbo Streams include seven available actions: &lt;code&gt;append, prepend, (insert) before, (insert) after, replace, update, and remove&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"append"&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"target_a"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prepend"&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"target_b"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"target_c"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="no"&gt;HTML&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;turbo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turbo Streams are using ActionCable to deliver updates to multiple clients via websockets asynchronously. Again, you're getting all of this w/out writing any of the Javascript code. But even if you need some custom Javascript for any reason (e.g. some animation, datepicker, etc.), Hotwire got you covered here with Stimulus.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Stimulus?
&lt;/h2&gt;

&lt;p&gt;Just like Rails which has its controllers with actions, Stimulus allows you to organize client side code in a similar way. You have a controller, which is a Javascript object, which defines actions, i.e. Javascript functions. Then you connect the controller action to interactive element on a page using HTML attributes. The action is then run in response, when DOM events are triggered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's create a sample Rails app with Hotwire
&lt;/h2&gt;

&lt;p&gt;Now having read all of the above, you might be wondering: how do I work with it? Hotwire is pretty simple to use, all that we need is a standard Rails app and Redis server.&lt;br&gt;
First you need to have Ruby 3 and Rails 7 and Redis server to be installed, I'm not going to cover installation process of those, but you can easily find any instructions you need based on your platform.&lt;/p&gt;

&lt;p&gt;So, let's setup a new Rails app (we'll be using Bootstrap as css option, just to make our app look a bit better):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails new bookstore --css bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Rails generated all the needed files, cd into the app directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd bookstore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails 7 initial app has everything we need to start using Hotwire, the Gemfile includes: Redis gem, Turbo-rails gem and Stimulus-rails.&lt;br&gt;
Make sure that you have Redis server up and running. Redis is required because it's used by ActionCable to store websockets related information.&lt;br&gt;
The default address and port for Rails to connect to Redis server is set in the &lt;code&gt;config/cable.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;development:
  adapter: &lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;
  &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="ss"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can genertate our model, controller and migration, which will be "Books" in our case of Bookstore. It'll have a string title, description of type text and likes counter as integer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="n"&gt;scaffold&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="ss"&gt;:string&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="ss"&gt;:text&lt;/span&gt; &lt;span class="n"&gt;likes&lt;/span&gt;&lt;span class="ss"&gt;:integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's fix generated migration, so we have 0 likes by default for any book we add to the db:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateBooks&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:books&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to create a database for our app, run in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;rake&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="ss"&gt;:create&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="ss"&gt;:migrate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets make books list page the root page of the app, open &lt;code&gt;config/routes.rb&lt;/code&gt; and add the missing root declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="s1"&gt;'books#index'&lt;/span&gt;
  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:books&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you should be able to run rails server with &lt;code&gt;rails server&lt;/code&gt; command or &lt;code&gt;./bin/dev&lt;/code&gt; (which will also watch css and js changes) in your terminal and, when you visit &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser, you should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lccyawmlybjac8mwlu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lccyawmlybjac8mwlu3.png" alt="Not responsive books list" width="586" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's change the Book partial &lt;code&gt;app/views/books/_book.html.erb&lt;/code&gt; to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_stream_from "book_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;" %&amp;gt;

&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"book_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
  &amp;lt;div style="background: lightblue; padding: 10px; width: 400px;"&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= book.title %&amp;gt;&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= button_to "Like (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;)", book_path(book, book: { likes: (book.likes + 1) }), method: :put %&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;br/&amp;gt;
&amp;lt;% end %&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;turbo_stream_from&lt;/code&gt; tells Hotwire to use websocket for updates of a frame identified with &lt;code&gt;:book_id&lt;/code&gt;, and &lt;code&gt;turbo_frame_tag&lt;/code&gt; identifies a frame, which can be replaced with partial on update.&lt;/p&gt;

&lt;p&gt;To tell Turbo that we want to add each new created book to the start of the books list, and update likes count on each like button click, we need to add  following callbacks to &lt;code&gt;app/models/book.rb&lt;/code&gt; file (also let's add a validation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;after_create_commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;broadcast_prepend_to&lt;/span&gt; &lt;span class="ss"&gt;:books&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;after_update_commit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;broadcast_replace_to&lt;/span&gt; &lt;span class="s2"&gt;"book_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First one tells Turbo to use &lt;code&gt;:books&lt;/code&gt; Turbo Stream for updates on create, and second one tells to use &lt;code&gt;:book_id&lt;/code&gt; to replace partial with updates.&lt;/p&gt;

&lt;p&gt;Then let's fix the ordering in our books controller and also add the new book variable assignment (so we could create a book from the root path) in &lt;code&gt;app/controllers/books_controller.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
  &lt;span class="vi"&gt;@books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should also edit &lt;code&gt;app/views/books/index.html.erb&lt;/code&gt; to add our Turbo Streams and Turbo Frames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="no"&gt;Books&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&amp;gt;

&amp;lt;%= turbo_stream_from :books %&amp;gt;

&amp;lt;%= turbo_frame_tag :book_form do %&amp;gt;
  &amp;lt;%= render 'books/&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;book: &lt;/span&gt;&lt;span class="vi"&gt;@book&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= turbo_frame_tag :books do %&amp;gt;
  &amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="vi"&gt;@books&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% end &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To avoid redirects when we create a new book or update an existing one and stay on the same main page, we need to also edit create and update actions in &lt;code&gt;app/controllers/books_controller.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
  &lt;span class="vi"&gt;@book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream: &lt;/span&gt;&lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s1"&gt;'books/form'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;book: &lt;/span&gt;&lt;span class="vi"&gt;@book&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, our bookstore app should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrp11ohsnwu8dbkf878y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrp11ohsnwu8dbkf878y.png" alt="Responsive Books List" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any time you create a new book using the form on the main page, Turbo prepends it to the books list, w/out page reloads. If you open multiple tabs in the browser - it will update all of them. "Like" buttons are also working w/out page reloads and update likes counts for the book in all of the tabs. And all of this with not a single line of Javascript code. How cool is that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This sample application is just a basic example of what you can do with Hotwire in Rails, but you can do much more complex things with Turbo and Stimulus.&lt;br&gt;
So if you want to start a new SPA with Rails, think twice if you need React, Vue or any other framework on the frontend, it might be much more productive to give Hotwire a try. There's a big chance it will make you happy.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>hotwire</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building geospatial apps is easy with Redis</title>
      <dc:creator>Aleksandr Ulanov</dc:creator>
      <pubDate>Mon, 08 Aug 2022 05:37:00 +0000</pubDate>
      <link>https://dev.to/ualeks/building-geospatial-apps-is-easy-with-redis-4g03</link>
      <guid>https://dev.to/ualeks/building-geospatial-apps-is-easy-with-redis-4g03</guid>
      <description>&lt;p&gt;Working with geospatial data is notoriously difficult, because latitude and longitude are floating point numbers and should be very precise. In addition, it would seem that latitude and longitude can be represented as a grid, but in fact they can't, simply because Earth is not flat and math is hard.&lt;/p&gt;

&lt;p&gt;For example, to determine the distance of a great circle between two points on a sphere, based on their latitude and longitude, the haversine formula is used, which looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g5hpwfxb5gkyukia0mo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2g5hpwfxb5gkyukia0mo.jpg" alt="Haversine formula" width="421" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another common task related to latitude and longitude is finding the number of points in a radius on the Earth's surface. That is, given a large ball (Earth) and you are trying to find points in a radius on this ball. But Earth, in fact, is not a perfect sphere, it is still an ellipsoid. As you might guess, the mathematical calculations for such an operation become quite complex.&lt;/p&gt;

&lt;p&gt;In this article, we'll look at how Redis can help us minimize calculations when working with geospatial data.&lt;/p&gt;

&lt;p&gt;Redis, which stands for Remote Dictionary Server, is a fast, open source key-value data store. Because of its speed, Redis is a popular choice for caching, session management, gaming, analytics, geospatial data, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get back to geospatial data. What is geohash?
&lt;/h2&gt;

&lt;p&gt;Geohash is a system representing coordinates as a string. Geohashing uses Base32 encoding to convert latitude and longitude to a string. For example, the geohash  of the Palace Square in St. Petersburg will look like this: udtscze2chgq. A variable geohash length represents a variable position accuracy, in other words, the shorter is the geohash, the less precise are the coordinates it represents. That is, a shorter geohash will represent the same geolocation, but with less accuracy. You can try encoding coordinates in geohash at &lt;a href="http://geohash.org" rel="noopener noreferrer"&gt;http://geohash.org&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Redis store geospatial data?
&lt;/h2&gt;

&lt;p&gt;Geospatial data storage is implemented in Redis using sorted lists (&lt;code&gt;ZSET&lt;/code&gt;) as the underlying data structure, but with on-the-fly encoding and decoding of location data and a new API. This means that indexing, searching and sorting by a specific location can be thrown into Redis with very few lines of code and minimal effort using the built-in commands: &lt;code&gt;GEOADD, GEODIST, GEORADIUS and GEORADIUSBYMEMBER (GEOSEARCH)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Geo Set is the basis for working with geospatial data in Redis - it is a data structure designed to manage geospatial indexes. Each Geo Set consists of one or more elements, each of which consists of a unique identifier, and a pair of coordinates - longitude and latitude.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands for working with geospatial data
&lt;/h2&gt;

&lt;p&gt;To add a new list (or a new element to an existing list) in a Redis store, use the &lt;code&gt;GEOADD&lt;/code&gt; command. For clarity, I will give examples of commands in Redis, as well as in the Ruby client for working with Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEOADD&lt;/span&gt; &lt;span class="s2"&gt;"buses"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.00020246342898&lt;/span&gt; &lt;span class="mf"&gt;40.717855101298305&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geoadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.00020246342898&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.717855101298305&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands add to the Geo Set named "buses" the coordinates of the location for bus "Bus A". If a Geo Set with this name is not yet stored in Redis, it will be created. A new entry will only be added to the index if an entry with the same name ("Bus A") is not already in the list. That is, Bus A is a unique identifier.&lt;/p&gt;

&lt;p&gt;It is also possible to add multiple records at once with a single GEOADD call, which can help reduce network and database load. Record IDs must be unique:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEOADD&lt;/span&gt; &lt;span class="s2"&gt;"buses"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.00020246342898&lt;/span&gt; &lt;span class="mf"&gt;40.717855101298305&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99472237472686&lt;/span&gt; &lt;span class="mf"&gt;40.725856700515855&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geoadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.00020246342898&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.717855101298305&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99472237472686&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;40.725856700515855&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same command is used to update the index of a record. If &lt;code&gt;GEOADD&lt;/code&gt; is called with entries already in the Geo Set, Redis simply updates the data for those entries, as soon as bus A starts moving, its location can be updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEOADD&lt;/span&gt; &lt;span class="s2"&gt;"buses"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;76.99265963484487&lt;/span&gt; &lt;span class="mf"&gt;38.87275545298483&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geoadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;76.99265963484487&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;38.87275545298483&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to adding and updating, of course entries can be removed from the index. The &lt;code&gt;ZREM&lt;/code&gt; command is provided to remove an entry from a Geo Set in Redis. &lt;code&gt;ZREM&lt;/code&gt; takes the name of the index to delete records from and the IDs of the records to be deleted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;ZREM&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zrem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bis A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The geo index can be deleted entirely and since it is stored as a Redis key, the &lt;code&gt;DEL&lt;/code&gt; command can be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;DEL&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, using &lt;code&gt;DEL&lt;/code&gt; for big lists can be a bad idea, since it can block Redis for a long time. So it might be better to always use &lt;code&gt;UNLINK&lt;/code&gt; instead of &lt;code&gt;DEL&lt;/code&gt;, i.e. 'non-blocking' delete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;UNLINK&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that Redis has a mechanism for indexes expiration, if you do not specify an expiration date for an index, then it will never be expired and will eat memory. To prevent this from happening, you need to use the &lt;code&gt;EXPIRE&lt;/code&gt; command, passing the name of the index and the number of seconds for expiration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;EXPIRE&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis uses a semi-lazy expiration mechanism, which means that the index is not expired until it is not read, if it turns out that the expiration time has passed during the reading operation, then the result is not returned, and the object itself is deleted from the storage. That is, until we request a Geo Set, it will be stored in memory indefinitely.&lt;/p&gt;

&lt;p&gt;However, Redis has a second level of expiration - it's active and random. It's a garbage collector that randomly reads different keys, and when the key is read, the standard mechanism for checking expiration occurs.&lt;/p&gt;

&lt;p&gt;Unfortunately, Redis does not have the ability to directly expire records in the index. Such a feature will have to be developed independently.&lt;/p&gt;

&lt;p&gt;What about reading and searching by geospatial data?&lt;br&gt;
There are several ways to read entries from an index. You can use the &lt;code&gt;ZRANGE&lt;/code&gt; and &lt;code&gt;ZSCAN&lt;/code&gt; commands to get started. These commands iterate over all entries in the index. For example, to return all entries in an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;ZRANGE&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With respect to geospatial data, there are two commands to get the location of an entry from an index. The first - &lt;code&gt;GEOPOS&lt;/code&gt; command returns the coordinates of the entry in the index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEOPOS&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geopos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command - &lt;code&gt;GEOHASH&lt;/code&gt; returns the coordinates of the entry encoded in the geohash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEOHASH&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geohash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the distance between two entries in an index, you can use the &lt;code&gt;GEODIST&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEODIST&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;geodist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"km"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of the command will be returned by default in meters. You can specify the required units of measurement by passing the fourth argument to the command, for example: km for kilometers, m for meters, mi - for miles, ft - for feet.&lt;/p&gt;

&lt;p&gt;To search the index, the &lt;code&gt;GEORADIUS&lt;/code&gt; and &lt;code&gt;GEORADIUSBYMEMBER&lt;/code&gt; (for Redis versions less than 6.2) or &lt;code&gt;GEOSEARCH&lt;/code&gt; (for versions older than 6.2) commands are also used.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GEORADIUS&lt;/code&gt; and &lt;code&gt;GEORADIUSBYMEMBER&lt;/code&gt; accept the parameters &lt;code&gt;WITHDIST&lt;/code&gt; (display results + distance from the specified point/record) and &lt;code&gt;WITHCOORD&lt;/code&gt; (display results + record coordinates), as well as the &lt;code&gt;ASC&lt;/code&gt; or &lt;code&gt;DESC&lt;/code&gt; sort option (sort by distance from the point):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEORADIUS&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;km&lt;/span&gt; &lt;span class="no"&gt;WITHDIST&lt;/span&gt;

&lt;span class="c1"&gt;# returns:&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"190.4424"&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"56.4413"&lt;/span&gt;

&lt;span class="no"&gt;GEORADIUS&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;km&lt;/span&gt; &lt;span class="no"&gt;WITHCOORD&lt;/span&gt;

&lt;span class="c1"&gt;# returns:&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"-74.00020246342898"&lt;/span&gt;
     &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"40.717855101298305"&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"Bus B"&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"-73.99472237472686
     2) "&lt;/span&gt;&lt;span class="mf"&gt;40.725856700515855&lt;/span&gt;&lt;span class="s2"&gt;"

GEORADIUS buses -73 40 200 km WITHDIST WITHCOORD

# returns:
1) 1) "&lt;/span&gt;&lt;span class="no"&gt;Bus&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt;&lt;span class="s2"&gt;"
  2) "&lt;/span&gt;&lt;span class="mf"&gt;190.4424&lt;/span&gt;&lt;span class="s2"&gt;"
  3) 1) "&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.00020246342898&lt;/span&gt;&lt;span class="s2"&gt;"
     2) "&lt;/span&gt;&lt;span class="mf"&gt;40.717855101298305&lt;/span&gt;&lt;span class="s2"&gt;"
2) 1) "&lt;/span&gt;&lt;span class="no"&gt;Bus&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;&lt;span class="s2"&gt;"
  2) "&lt;/span&gt;&lt;span class="mf"&gt;56.4413&lt;/span&gt;&lt;span class="s2"&gt;"
  3) 1) "&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;73.99472237472686&lt;/span&gt;
     &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"40.725856700515855"&lt;/span&gt;

&lt;span class="c1"&gt;# Redis example:&lt;/span&gt;
&lt;span class="no"&gt;GEORADIUSBYMEMBER&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;km&lt;/span&gt;

&lt;span class="c1"&gt;# returns:&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="no"&gt;Bus&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;

&lt;span class="c1"&gt;# Ruby example:&lt;/span&gt;
&lt;span class="no"&gt;RedisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;georadiusbymember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"buses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"km"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;GEOSEARCH&lt;/code&gt; command for new versions of Redis has a similar syntax and does the same thing. The command syntax looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Redis examples:&lt;/span&gt;
&lt;span class="no"&gt;GEOSEARCH&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="no"&gt;FROMMEMBER&lt;/span&gt; &lt;span class="s2"&gt;"Bus A"&lt;/span&gt; &lt;span class="no"&gt;BYRADIUS&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;km&lt;/span&gt; &lt;span class="no"&gt;ASC&lt;/span&gt; &lt;span class="no"&gt;WITHCOORD&lt;/span&gt; &lt;span class="no"&gt;WITHDIST&lt;/span&gt; &lt;span class="no"&gt;WITHHASH&lt;/span&gt;
&lt;span class="c1"&gt;# returns all entries in 100km radius from Bus A with coordinates, distances and geohashes&lt;/span&gt;

&lt;span class="no"&gt;GEOSEARCH&lt;/span&gt; &lt;span class="n"&gt;buses&lt;/span&gt; &lt;span class="no"&gt;FROMLONLAT&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.00020246342898&lt;/span&gt; &lt;span class="mf"&gt;40.717855101298305&lt;/span&gt;&lt;span class="s2"&gt;" BYRADIUS 200 mi DESC COUNT 2
# returns maximum 2 entries sorted from the farest to the closest within 200 miles from the center
# with given coordinates
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The simplicity of implementing location apps with geospatial data in Redis not only makes it easy to handle large amounts of geospatial data, but also allows you to implement some complex processing for the data. For example, querying for entries within a radius can help you implement searching for points of interest nearby, by giving the user only the choices closest to them. If your application uses geospatial data in any way, consider moving complex calculations to Redis, it may increase the efficiency of your application.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>geospatia</category>
      <category>geofence</category>
      <category>database</category>
    </item>
  </channel>
</rss>
