<?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>Forem: leastbad</title>
    <description>The latest articles on Forem by leastbad (@leastbad).</description>
    <link>https://forem.com/leastbad</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%2F125079%2Fc84874c7-55a7-4c29-b1e0-c93f1de11699.png</url>
      <title>Forem: leastbad</title>
      <link>https://forem.com/leastbad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/leastbad"/>
    <language>en</language>
    <item>
      <title>The CableReady Language Implementation Project</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Sun, 28 Nov 2021 12:42:38 +0000</pubDate>
      <link>https://forem.com/leastbad/the-cableready-language-implementation-project-4hjd</link>
      <guid>https://forem.com/leastbad/the-cableready-language-implementation-project-4hjd</guid>
      <description>&lt;p&gt;We believe that CableReady can become the universal standard tool for developers to dynamically control client browsers from the server. While the project has roots in the Ruby on Rails community, the JS client is unopinionated about how the simple JSON structure that it consumes is created.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1464595363961180162-368" src="https://platform.twitter.com/embed/Tweet.html?id=1464595363961180162"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1464595363961180162-368');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1464595363961180162&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;We would like to announce support for Python, Go, C#, Java, PHP and NodeJS server libraries in early 2022. While there's a broad set of features a server library could implement, there's a baseline that we'd like to make sure all implementations can offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;CableReady was started in 2017 by Nate Hopkins. It predates LiveView and the HTML-on-the-wire trend by 18 months. It sees roughly 15,000 downloads per week and offers 36 different &lt;a href="https://cableready.stimulusreflex.com/v/v5/reference/operations" rel="noopener noreferrer"&gt;operations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;CableReady is currently a client-side JS module and a server-side Ruby module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key concepts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;available everywhere&lt;/li&gt;
&lt;li&gt;multiple operations per payload&lt;/li&gt;
&lt;li&gt;schemaless&lt;/li&gt;
&lt;li&gt;simple JSON wire format&lt;/li&gt;
&lt;li&gt;method chaining&lt;/li&gt;
&lt;li&gt;transport agnostic&lt;/li&gt;
&lt;li&gt;extensible via custom operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Available everywhere
&lt;/h3&gt;

&lt;p&gt;Rails developers can access a &lt;code&gt;cable_ready&lt;/code&gt; singleton from just about anywhere in their application, and we believe it's a big part of the secret sauce. While every language and framework has their own idioms, we encourage implementors to remove barriers and make it easy to call CableReady anywhere it could be useful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cableready.stimulusreflex.com/v/v5/cableready-everywhere" rel="noopener noreferrer"&gt;https://cableready.stimulusreflex.com/v/v5/cableready-everywhere&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Operations and their options
&lt;/h3&gt;

&lt;p&gt;Operations are the basic atomic unit of activity in CableReady. Each operation typically has a very specific focus and often mimics the DOM JS spec for the activity in question. Operations have options passed to them which specify their exact behavior.&lt;/p&gt;

&lt;p&gt;Multiple operations can be prepared together. They will be executed in the order that they were created. Different operation types can be mixed together in one payload.&lt;/p&gt;

&lt;p&gt;The Ruby implementation offers two interfaces; the (original) primary mechanism delivers the operations to a WebSocket channel in what we refer to as a "broadcast". The other - known as "cable car" - returns a JSON string that can be sent, persisted or displayed for any purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schemaless
&lt;/h3&gt;

&lt;p&gt;CableReady operations each have their own mandatory and optional options, along with options that are provided to every operation by the library. However, arbitrary additional options can be passed to an operation and they will be forwarded to the client. This makes it easy for CableReady to form the basis of much larger projects, such as StimulusReflex.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSON wire format
&lt;/h3&gt;

&lt;p&gt;As of v5.0, the CableReady JSON wire format is an array of objects, where each object represents one operation. It's intentionally very simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;"message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Hello!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;operation&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;consoleLog&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each operation has &lt;strong&gt;camelCased&lt;/strong&gt; key/value pairs that convey options. Every operation must have an &lt;code&gt;operation&lt;/code&gt; value, or the client will raise an exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method chaining
&lt;/h3&gt;

&lt;p&gt;Developer experience is a high priority. We take pride in the readability and expressiveness offered by our server API. The basic pseudocode structure we provide looks like:&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;cable_ready&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:foo&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, the first method &lt;code&gt;cable_ready&lt;/code&gt; starts a method chain by returning &lt;code&gt;self&lt;/code&gt;, and then each operation is a method that also returns the chain started by the initial method. In this way, you can chain together as many operations as you like. Finally, we have a &lt;code&gt;broadcast&lt;/code&gt; method which takes the current chain and broadcasts it via WebSockets to the &lt;code&gt;:foo&lt;/code&gt; channel.&lt;/p&gt;

&lt;p&gt;We also have our "cable car" interface which emits JSON when &lt;code&gt;to_json&lt;/code&gt; is called. This makes it perfect for responding to Ajax fetch requests:&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;cable_car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;cable_car&lt;/code&gt; might be assembled in steps, perhaps via a control loop:&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;inspiration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cable_car&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;console_log&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;"Hello there!"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dispatch_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"fred"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;detail: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;inspiring: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&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;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;inspiration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;console_log&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;"Still here: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&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="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;inspiration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main expectation that should hold between languages is that you will start the chain with a command, add one or many operation methods, and then execute the chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transport agnostic
&lt;/h3&gt;

&lt;p&gt;CableReady started its life as a WebSocket library, but the neutral JSON format has potential far beyond just WebSocket usage. We now frequently return CableReady JSON payloads via Ajax as well. There's nothing stopping you from embedding payloads into a DOM element attribute, for example.&lt;/p&gt;

&lt;p&gt;We believe that a minimally viable CableReady server library must be able to produce compatible JSON. There's no hard requirement that it interface with WebSockets, although we do find this to be a major sweet spot and will do our best to provide support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom operations
&lt;/h3&gt;

&lt;p&gt;While CableReady ships with an impressive number of operations out of the box, users should be able to add their own operations. Admittedly, the method used to dynamically create all of the methods for each operation is the &lt;a href="https://github.com/stimulusreflex/cable_ready/blob/master/lib/cable_ready/operation_builder.rb" rel="noopener noreferrer"&gt;most sophisticated&lt;/a&gt; one in our framework, but again, we're here to help.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cableready.stimulusreflex.com/v/v5/customization#custom-operations" rel="noopener noreferrer"&gt;https://cableready.stimulusreflex.com/v/v5/customization#custom-operations&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Get involved!
&lt;/h1&gt;

&lt;p&gt;We have a wonderful community with over 1600 folks on our Discord server, helping people get started. Come join &lt;a href="https://discord.gg/stimulus-reflex" rel="noopener noreferrer"&gt;https://discord.gg/stimulus-reflex&lt;/a&gt; and drop by the #cable_ready channel with any questions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Async Redis key mutation notifications in Rails</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Sun, 27 Jun 2021 14:55:42 +0000</pubDate>
      <link>https://forem.com/leastbad/async-redis-key-mutation-notifications-in-rails-4hng</link>
      <guid>https://forem.com/leastbad/async-redis-key-mutation-notifications-in-rails-4hng</guid>
      <description>&lt;p&gt;&lt;em&gt;Update 10/7/2022&lt;/em&gt;: I prepared &lt;a href="https://github.com/leastbad/tracker"&gt;a simple Rails 7 app&lt;/a&gt; that demonstrates the functionality described in this article.&lt;/p&gt;




&lt;p&gt;I am a huge fan of &lt;a href="https://github.com/rails/kredis"&gt;Kredis&lt;/a&gt;. It allows Rails developers to see Redis as far more than just a fragment cache and "where jobs are".&lt;/p&gt;

&lt;p&gt;Working with Kredis made me want to be able to run &lt;a href="https://cableready.stimulusreflex.com/reference/operations"&gt;arbitrary operations&lt;/a&gt; in my Rails app when specific keys are modified via specific Redis commands. &lt;a href="https://redis.io/topics/pubsub"&gt;Redis has an excellent pub/sub infrastructure&lt;/a&gt;, and all &lt;a href="https://redis.io/topics/notifications"&gt;Redis commands publish messages&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why would someone want this?
&lt;/h1&gt;

&lt;p&gt;While it's true that changes to data that occur within a typical Rails app are already well covered by model callbacks, state machines and other standard tooling, an entire world of real-time stream processing, ETL and multi-application use cases open up when you can run &lt;code&gt;redis-cli set leastbad rules&lt;/code&gt; on your terminal and pick it up in your app.&lt;/p&gt;

&lt;p&gt;Problem #1: Listening for messages blocks execution.&lt;br&gt;
Solution #1: Spin up a thread!&lt;/p&gt;

&lt;p&gt;Problem #2: Every dyno/server is going to receive the same messages, causing mayhem as developers respond to those messages with database updates. Side-effect chaos!&lt;br&gt;
Solution #2: A standalone process that can be registered as a &lt;code&gt;worker&lt;/code&gt; in &lt;code&gt;Procfile&lt;/code&gt;... &lt;em&gt;sort of like Sidekiq.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At first, I was just planning on borrowing 95% of Mike Perham's battle-hardened code. Then I realized that the Venn diagram of "people who want a Redis changeset firehose" and "Sidekiq users" is close to 100%... &lt;strong&gt;so I just bolted what I needed onto Sidekiq&lt;/strong&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Try it out!
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;What follows is the MVP of my new gem. In fact, it's not a gem, yet: it's an initializer! It has no tests and is hours old. My janky code would make poor Mike &lt;a href="https://youtu.be/W7JyjZI3LUM?t=333"&gt;bleed out&lt;/a&gt;. The goal is to see if folks actually need/want this to exist. &lt;strong&gt;I'm looking for feedback on what the ideal Rails-side API would actually look like&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your Rails app needs to be up and running with Sidekiq. Just stick this in &lt;code&gt;config/initializers/sidekiq.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="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sidekiq&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Subscriber&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Util&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
      &lt;span class="vi"&gt;@done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="vi"&gt;@thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&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;start&lt;/span&gt;
      &lt;span class="vi"&gt;@thread&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;safe_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"subscriber"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="vi"&gt;@done&lt;/span&gt;
          &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&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;conn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="c1"&gt;# https://redis.io/topics/notifications#configuration&lt;/span&gt;
            &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"notify-keyspace-events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"E$lshz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# https://redis.io/topics/notifications#events-generated-by-different-commands&lt;/span&gt;
            &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;psubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"__key*__:*"&lt;/span&gt;&lt;span class="p"&gt;)&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;on&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
              &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;psubscribe&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
                &lt;span class="vi"&gt;@firehose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Firehose&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="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pmessage&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;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
                &lt;span class="vi"&gt;@firehose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="k"&gt;end&lt;/span&gt;
              &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;punsubscribe&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
                &lt;span class="vi"&gt;@firehose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&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;end&lt;/span&gt;
        &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Subscriber exiting..."&lt;/span&gt;&lt;span class="p"&gt;)&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;terminate&lt;/span&gt;
      &lt;span class="vi"&gt;@done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@thread&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@thread&lt;/span&gt;
        &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@thread&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&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;value&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;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;CoreExtensions&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Sidekiq&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Launcher&lt;/span&gt;
      &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:subscriber&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;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="vi"&gt;@subscriber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Subscriber&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;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&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;run&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;
        &lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&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;quiet&lt;/span&gt;
        &lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;
        &lt;span class="k"&gt;super&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;stop&lt;/span&gt;
        &lt;span class="n"&gt;subscriber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;
        &lt;span class="k"&gt;super&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;end&lt;/span&gt;

&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure_server&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"sidekiq/launcher"&lt;/span&gt;
  &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CoreExtensions&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Launcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using &lt;a href="https://cableready.stimulusreflex.com"&gt;CableReady&lt;/a&gt; to send console log notifications to the Console Inspector whenever a key is updated with the Redis &lt;code&gt;SET&lt;/code&gt; command. I have a simple &lt;code&gt;AllUsers&lt;/code&gt; ActionCable Channel in play for testing. This lives in &lt;code&gt;app/lib/firehose.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Firehose&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;CableReady&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Broadcaster&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:redis&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pubsub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis_connection_for_subscriptions&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;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="c1"&gt;# https://github.com/rails/kredis#examples&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:set&lt;/span&gt;    &lt;span class="c1"&gt;# string, integer, json&lt;/span&gt;
      &lt;span class="n"&gt;cable_ready&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"all_users"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;console_log&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;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was just updated to &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;broadcast&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:rpush&lt;/span&gt;  &lt;span class="c1"&gt;# list&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:lrem&lt;/span&gt;   &lt;span class="c1"&gt;# unique_list&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:sadd&lt;/span&gt;   &lt;span class="c1"&gt;# set&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:incr&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:decr&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:incrby&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:decrby&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:exists&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:del&lt;/span&gt;
      &lt;span class="n"&gt;cable_ready&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"all_users"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;console_log&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;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; was deleted"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;
    &lt;span class="k"&gt;else&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;As Seinfeld would say, &lt;strong&gt;is this anything?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>redis</category>
    </item>
    <item>
      <title>Live previews with Rails and StimulusReflex</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Wed, 24 Mar 2021 06:40:36 +0000</pubDate>
      <link>https://forem.com/leastbad/live-previews-with-rails-and-stimulusreflex-nep</link>
      <guid>https://forem.com/leastbad/live-previews-with-rails-and-stimulusreflex-nep</guid>
      <description>&lt;p&gt;This morning I read &lt;a href="https://nts.strzibny.name/rails-stimulus-live-preview/"&gt;Live previews with Rails and Stimulus 2&lt;/a&gt;, a post which compares the experience of implementing a text preview feature using server-rendered code. The author choose to use a "pure Ajax" approach with &lt;a href="https://stimulus.hotwire.dev/"&gt;Stimulus&lt;/a&gt;, as well as what the same project would look like using &lt;a href="https://turbo.hotwire.dev/"&gt;Turbo Frames&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post will present a 3rd option: &lt;a href="https://docs.stimulusreflex.com"&gt;StimulusReflex&lt;/a&gt;. The &lt;a href="https://github.com/leastbad/stimulus_reflex_harness/tree/previews"&gt;full source&lt;/a&gt; for this example is available on Github. Don't forget to change to the &lt;strong&gt;previews&lt;/strong&gt; branch!&lt;/p&gt;




&lt;p&gt;First, we'll need a template. I'm just going to use a vanilla tag helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text_area_tag&lt;/span&gt; &lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;reflex: &lt;/span&gt;&lt;span class="s2"&gt;"input-&amp;gt;Composer#preview"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"preview"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing required to make this feature work is a Reflex declared on the textarea element. &lt;code&gt;input&lt;/code&gt; events captured will initiate a &lt;code&gt;preview&lt;/code&gt; action on the &lt;code&gt;Composer&lt;/code&gt; Reflex class. I also assigned an &lt;code&gt;id&lt;/code&gt; attribute to the &lt;code&gt;strong&lt;/code&gt; element so that we have a destination for the preview content.&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;ComposerReflex&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationReflex&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;preview&lt;/span&gt;
    &lt;span class="n"&gt;morph&lt;/span&gt; &lt;span class="s2"&gt;"#preview"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&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 &lt;code&gt;preview&lt;/code&gt; action takes the &lt;code&gt;value&lt;/code&gt; of the element which triggered the Reflex, and updates the &lt;code&gt;#preview&lt;/code&gt; element in the browser. Behind the scenes, this is done using one of CableReady's &lt;a href="https://cableready.stimulusreflex.com/reference/operations"&gt;33 operations&lt;/a&gt;, &lt;code&gt;inner_html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's it... there are no further steps. You don't need any extra templates, routes or Stimulus controllers.&lt;/p&gt;

&lt;p&gt;If you open the Console Inspector, you'll see that &lt;strong&gt;each Reflex takes about 5ms&lt;/strong&gt;, round-trip.&lt;/p&gt;




&lt;p&gt;StimulusReflex and CableReady offer a simple but powerful RPC model for building reactive applications. The functionality of this stack eclipses Turbo in every meaningful dimension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CR has ~7x as many verbs/operations as Turbo Streams (just CRUD)&lt;/li&gt;
&lt;li&gt;CR supports adding custom operations&lt;/li&gt;
&lt;li&gt;CR is incredibly flexible in terms of who or what you send updates to&lt;/li&gt;
&lt;li&gt;CR operations can be chained and target multiple elements&lt;/li&gt;
&lt;li&gt;CR can broadcast from anywhere in your app: jobs, reflexes, controllers, rake tasks, channels&lt;/li&gt;
&lt;li&gt;CR can provide visual feedback when elements are updated&lt;/li&gt;
&lt;li&gt;SR provides a transaction wrapper around each request&lt;/li&gt;
&lt;li&gt;SR has a full life-cycle of events and callbacks on the server and client&lt;/li&gt;
&lt;li&gt;SR Reflex updates use DOM diffing, not innerHTML, so Stimulus controller state is preserved&lt;/li&gt;
&lt;li&gt;SR Reflexes can be initiated by JS or declared in markup&lt;/li&gt;
&lt;li&gt;SR offers a highly performant RPC mechanism with Nothing Morphs&lt;/li&gt;
&lt;li&gt;SR benefits from tight coupling with Stimulus&lt;/li&gt;
&lt;li&gt;SR has really powerful server and client debug tooling&lt;/li&gt;
&lt;li&gt;SR has exceptionally thorough documentation&lt;/li&gt;
&lt;li&gt;SR has incredible free support&lt;/li&gt;
&lt;li&gt;SR has a 2000+ person &lt;a href="https://discord.gg/stimulus-reflex"&gt;Discord community&lt;/a&gt; that is incredibly supportive&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I hope that you'll consider updating your post with a third option, &lt;a href="https://twitter.com/strzibnyj"&gt;Josef&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>javascript</category>
      <category>stimulus</category>
      <category>stimulusreflex</category>
    </item>
    <item>
      <title>Developer Happiness: StimulusReflex v3.4</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Fri, 18 Dec 2020 16:52:39 +0000</pubDate>
      <link>https://forem.com/leastbad/developer-happiness-stimulusreflex-v3-4-43k3</link>
      <guid>https://forem.com/leastbad/developer-happiness-stimulusreflex-v3-4-43k3</guid>
      <description>&lt;p&gt;It's red letter day: after three months of development and &lt;em&gt;nine&lt;/em&gt; pre-release candidates, StimulusReflex v3.4 has finally &lt;a href="https://rubygems.org/gems/stimulus_reflex" rel="noopener noreferrer"&gt;dropped&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The introduction of &lt;a href="https://docs.stimulusreflex.com/morph-modes#introducing-morphs" rel="noopener noreferrer"&gt;Morphs&lt;/a&gt; in September elevated StimulusReflex from a cool proof-of-concept to a promising tool for building reactive UIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svbtleusercontent.com/oZaURzYg1pBnK5AvswEzct0xspap.jpg" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsvbtleusercontent.com%2FoZaURzYg1pBnK5AvswEzct0xspap_small.jpg" alt="Reflex is faster than a reaction."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open source projects need to think creatively to stand out. &lt;strong&gt;Hiring an actor to record a fake testimonial by a beloved fictional douchebag didn't compute for some.&lt;/strong&gt; The &lt;a href="https://twitter.com/theleastbad/status/1308401495185010700" rel="noopener noreferrer"&gt;initial tweet&lt;/a&gt; was watched 9,100 times with 111 re-tweets, resulting in 27,000 impressions and 4,200 engagements. The &lt;a href="https://www.youtube.com/watch?v=utxCm3uLhIE" rel="noopener noreferrer"&gt;YouTube version&lt;/a&gt; has been viewed 5,800 times to date. This prompted a flurry of high profile articles, most notably Rails legend Obie Fernandez's epic "&lt;a href="https://medium.com/@obie/react-is-dead-long-live-reactive-rails-long-live-stimulusreflex-and-viewcomponent-cd061e2b0fe2" rel="noopener noreferrer"&gt;React is dead. Long live Reactive Rails. Long live StimulusReflex and ViewComponent&lt;/a&gt;". StimulusReflex was the featured story in &lt;a href="https://rubyweekly.com/issues/520" rel="noopener noreferrer"&gt;Ruby Weekly&lt;/a&gt;. Jason Charnes announced that &lt;a href="https://courses.jasoncharnes.com/stimulus-reflex" rel="noopener noreferrer"&gt;a course&lt;/a&gt; is in the works. Ruby Hero Ryan Bates asked Digital Ocean to &lt;a href="https://twitter.com/rbates/status/1331379415008247808" rel="noopener noreferrer"&gt;donate $5,000&lt;/a&gt; to the project. Membership on our &lt;a href="https://discord.gg/XveN625" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; has doubled, and our weekly downloads went from under 5,000 to &lt;a href="https://www.npmjs.com/package/stimulus_reflex" rel="noopener noreferrer"&gt;over 12,000&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Perhaps most importantly, &lt;strong&gt;we've crossed the rubicon from being mocked to being seen as a threat&lt;/strong&gt;. Given the thousands of hours passionately invested in building and supporting StimulusReflex and CableReady, $600 to 5x our name recognition and establish this stack as a preferable alternative to JS-based SPAs - &lt;em&gt;pretty much over night&lt;/em&gt; - seems like a solid return on investment.&lt;/p&gt;

&lt;p&gt;Knowing that we're influencing the direction of Rails itself is possibly the most gratifying upside of all. As evidenced by DHH gradually walking-back &lt;a href="https://twitter.com/patmisch/status/1252772796633137152" rel="noopener noreferrer"&gt;earlier statements&lt;/a&gt; saying the long-promised &lt;em&gt;NEW MAGIC&lt;/em&gt; is "&lt;a href="https://twitter.com/dhh/status/1225612112556281858" rel="noopener noreferrer"&gt;unrelated&lt;/a&gt;" to StimulusReflex, the narrative has progressively evolved from "&lt;a href="https://twitter.com/dhh/status/1275907797431902213" rel="noopener noreferrer"&gt;similar in intent&lt;/a&gt;" to "&lt;a href="https://twitter.com/dhh/status/1336959367879516162" rel="noopener noreferrer"&gt;an alternative to&lt;/a&gt;" StimulusReflex. We want Rails to kick ass and we're glad to be keeping one of our heroes on his toes. Granted, it's getting harder and harder for him to imply that he "discovered" these ideas in a fevered burst of primordial creative insight.&lt;/p&gt;




&lt;p&gt;When I think about what "developer happiness" means to me, it comes down to two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a magical sense of "things just work", made possible by ideas like the Principle of Least Surprise, intelligent defaults, methods that you hope will be there &lt;em&gt;and they are&lt;/em&gt;, and the defenestration of ceremony and boilerplate code&lt;/li&gt;
&lt;li&gt;the anticipation and mitigation of painful things that could otherwise reduce my happiness&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Things Just Work
&lt;/h1&gt;

&lt;p&gt;The recent method chaining capability of CableReady v4.4 combined with the magic &lt;code&gt;cable_ready&lt;/code&gt; method available inside of Reflex actions is a great example of things working very well. In StimulusReflex v3.3, using CableReady to replace an element for the current user looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svbtleusercontent.com/xhSiN8RxQdKrBX3BPyAnYc0xspap.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsvbtleusercontent.com%2FxhSiN8RxQdKrBX3BPyAnYc0xspap_small.png" alt="chrome_56nIIZv2s0.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the same thing in StimulusReflex v3.4. How's that for "conceptual compression"?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svbtleusercontent.com/9HLD6ALUMxH2HHd82Fpexc0xspap.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsvbtleusercontent.com%2F9HLD6ALUMxH2HHd82Fpexc0xspap_small.png" alt="chrome_VQqKSbP9BP.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As of StimulusReflex v3.4 and CableReady v4.3, every time a DOM event is created, a jQuery event with the same name and details is &lt;em&gt;also&lt;/em&gt; created - but only if the jQuery library is present and detected in the current application. We welcome the millions of jQuery users with open arms.&lt;/p&gt;

&lt;p&gt;There are two new features that are subtle but exciting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we've introduced a new &lt;a href="https://docs.stimulusreflex.com/lifecycle#client-side-reflex-callbacks" rel="noopener noreferrer"&gt;finalize&lt;/a&gt; life-cycle stage that runs &lt;em&gt;after&lt;/em&gt; all CableReady operations triggered by Morphs have finished. It's perfect for initiating animations and Turbolinks navigations&lt;/li&gt;
&lt;li&gt;there's now an optional "&lt;a href="https://docs.stimulusreflex.com/reflexes#tab-isolation" rel="noopener noreferrer"&gt;tab isolation&lt;/a&gt;" mode, which makes sure that Morphs only impact the DOM of the browser tab that initiated the Reflex&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also magically &lt;a href="https://docs.stimulusreflex.com/reflexes#signed-and-unsigned-global-id-accessors" rel="noopener noreferrer"&gt;unpack signed Global ID&lt;/a&gt;s right into ActiveRecord models. As the Great Dane often says, "look at all of the code I didn't have to write!"&lt;/p&gt;

&lt;h1&gt;
  
  
  Mitigation of Pain
&lt;/h1&gt;

&lt;p&gt;It has to be the ultimate cliche in software development that every new release is described as faster, with fewer bugs and far more robust.&lt;/p&gt;

&lt;p&gt;v3.4 isn't just faster, with fewer bugs and far more robust... it's also substantially smaller, with the client footprint shrinking to just &lt;a href="https://bundlephobia.com/result?p=stimulus_reflex@3.4.0-pre9" rel="noopener noreferrer"&gt;11kb&lt;/a&gt; including CableReady, morphdom and ActionCable.&lt;/p&gt;

&lt;p&gt;Stimulus 2 Just Works. You can use v1.1, too. Whatever you like.&lt;/p&gt;

&lt;p&gt;Significant client-side refactoring made our vastly improved &lt;a href="https://docs.stimulusreflex.com/troubleshooting#client-side-logging" rel="noopener noreferrer"&gt;logging module&lt;/a&gt;, tab isolation and the &lt;code&gt;finalize&lt;/code&gt; stage possible while ensuring that multiple concurrent Reflexes can be in-flight without corruption or side-effects. We're also far more forgiving in scenarios where the element which initiates a Reflex is replaced - a major source of confusion for newcomers in the past.&lt;/p&gt;

&lt;p&gt;It's not just the client that has been given white glove treatment, either: v3.4 introduces a brand new &lt;a href="https://docs.stimulusreflex.com/troubleshooting#stimulusreflex-logging" rel="noopener noreferrer"&gt;server-side logging&lt;/a&gt; module that is &lt;em&gt;disturbingly&lt;/em&gt; customizable without being "too much":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svbtleusercontent.com/eUaty7cud2fxtXNkCoB1Vj0xspap.png" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsvbtleusercontent.com%2FeUaty7cud2fxtXNkCoB1Vj0xspap_small.png" alt="WindowsTerminal_dQPv6fcNZM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've added an initializer, but the defaults are so intelligent, it's very likely that you'll never change them. That said, it's nice to know that if you need your Page Morphs to support &lt;a href="https://docs.stimulusreflex.com/setup#rack-middleware-support" rel="noopener noreferrer"&gt;Rack middleware&lt;/a&gt;, it's super easy.&lt;/p&gt;

&lt;p&gt;Speaking of pain: weird bugs due to mismatched gem + npm package versions are now a thing of the past. StimulusReflex will now yell loudly &lt;em&gt;and abort&lt;/em&gt; if versions don't match... unless you turn warnings off in the initializer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One more thing...&lt;/em&gt; while it's not technically part of the SR/CR stack, I released a Stimulus controller called &lt;a href="https://www.npmjs.com/package/radiolabel" rel="noopener noreferrer"&gt;radiolabel&lt;/a&gt; that gives you visual feedback on your CableReady operations as they happen in development. It's an easy way to make debugging your app faster and more explicit.&lt;/p&gt;




&lt;p&gt;The coolest and most profound &lt;a href="https://docs.stimulusreflex.com/#new-release-v-3-4-developer-happiness-edition" rel="noopener noreferrer"&gt;changes&lt;/a&gt; in v3.4 came from community contributors, sometimes making their first PRs. In particular, I'd like to call out the code and support efforts of folks like &lt;a href="https://github.com/rolandstuder" rel="noopener noreferrer"&gt;Roland Studer&lt;/a&gt;, &lt;a href="https://github.com/paramagicdev" rel="noopener noreferrer"&gt;Konnor Rogers&lt;/a&gt;, &lt;a href="https://github.com/piotrwodz" rel="noopener noreferrer"&gt;Piotr Wodz&lt;/a&gt;, &lt;a href="https://github.com/excid3" rel="noopener noreferrer"&gt;Chris Oliver&lt;/a&gt; and &lt;a href="https://github.com/existentialmutt" rel="noopener noreferrer"&gt;Rafe Rosen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Meanwhile, &lt;a href="https://github.com/joshleblanc" rel="noopener noreferrer"&gt;Josh LeBlanc&lt;/a&gt; and his &lt;a href="http://view-component-reflex-expo.grep.sh/" rel="noopener noreferrer"&gt;View Component Reflex&lt;/a&gt; might rightfully be considered our secret weapon(s).&lt;/p&gt;

&lt;p&gt;A personal thanks to all of the contributors, including my co-conspirators &lt;a href="https://github.com/hopsoft" rel="noopener noreferrer"&gt;Nate Hopkins&lt;/a&gt;, &lt;a href="https://github.com/julianrubisch" rel="noopener noreferrer"&gt;Julian Rubisch&lt;/a&gt; and &lt;a href="https://github.com/marcoroth" rel="noopener noreferrer"&gt;Marco Roth&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://svbtleusercontent.com/pfMPiGsvVmN5FxqXd3vtEU0xspap.jpg" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsvbtleusercontent.com%2FpfMPiGsvVmN5FxqXd3vtEU0xspap_small.jpg" alt="resistance.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NEW MAGIC is a wing of the #resistance, and the #resistance is kicking ass.&lt;/p&gt;

&lt;p&gt;Remember: a reflex is faster than a reaction. ❤️&lt;/p&gt;

</description>
      <category>rails</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>ruby</category>
    </item>
    <item>
      <title>StimulusReflex v3.3 Morphs has been released</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Tue, 22 Sep 2020 13:33:48 +0000</pubDate>
      <link>https://forem.com/leastbad/stimulusreflex-v3-3-morphs-has-been-released-o3e</link>
      <guid>https://forem.com/leastbad/stimulusreflex-v3-3-morphs-has-been-released-o3e</guid>
      <description>&lt;p&gt;TL;DR: scroll down for game-changing new features, a declaration of war and a legitimate celebrity endorsement.&lt;/p&gt;

&lt;p&gt;I have a secret: I've been developing software my entire conscious life, but StimulusReflex was my first Open Source project and I came to it out of distress, not curiousity. Instead of sensing opportunity, I was in a crush of desperation.&lt;/p&gt;

&lt;p&gt;I'm a Rails developer. I learned some React to help my partner human with her ambition to become a coder. Every day, I felt more sad and upset that she was being taught a stack that fetishized complexity. I needed to channel my grief into a solution, because complaining about how broken things had become was driving her nuts. I was on a mission.&lt;/p&gt;

&lt;p&gt;Fast-forward 18 months, 90,000 downloads, 60 releases and 500 community members later. People like it... &lt;a href="https://youtu.be/CLQ0LZSnJFE?t=7" rel="noopener noreferrer"&gt;they really like it&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;And now, the most exciting update to the original vision has arrived. We spent a year improving things incrementally, listening carefully to the problems and requests people brought to our &lt;a href="https://discord.gg/XveN625" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;. I spent a lot of time asking people &lt;em&gt;why&lt;/em&gt; they wanted to do things. Like the landscapers at Ohio State University, we didn't rush to conclusions or prescribe what we imagined people wanted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fofv7ymhf7h0hcujow1js.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fofv7ymhf7h0hcujow1js.jpg" alt="Ohio State University"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Earlier this year, regular updates paused. Coccoons were spun. Epic Github pull request conversations helped us iterate our design until &lt;a href="https://docs.stimulusreflex.com/morph-modes#introducing-morph-modes" rel="noopener noreferrer"&gt;Morphs&lt;/a&gt; took shape.&lt;/p&gt;

&lt;p&gt;Originally, a Reflex would regenerate the current page, going through most of the Rails stack as it executed the controller action and ultimately sent the entire page back to the browser, even if you just wanted to change &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt;. It worked great in spite of being a potentially heavy request, but we knew we could do better.&lt;/p&gt;

&lt;p&gt;A Selector Morph completely skips ActionDispatch. Instead, you can now render a partial or ViewComponent and send the result to the client, where it will update a surgically targeted DOM element. In fact, you can update multiple elements in one request. And boy, is it ever fast. If you think of Rails as slow, you'll have a hard time explaining the 12ms updates.&lt;/p&gt;

&lt;p&gt;Morphs can also update nothing at all. This is really handy when you want to launch an ActiveJob or trigger an external process. Later on, when that job completes, you can use &lt;a href="https://cableready.stimulusreflex.com" rel="noopener noreferrer"&gt;CableReady&lt;/a&gt; to send notifications to the client.&lt;/p&gt;

&lt;p&gt;You can see &lt;a href="https://app.lucidchart.com/documents/view/e83d2cac-d2b1-4a05-8a2f-d55ea5e40bc9/0_0" rel="noopener noreferrer"&gt;on this chart&lt;/a&gt; that we went from one codepath to three, with the original behavior still the default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjwp6zre2amk99rimzvvy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjwp6zre2amk99rimzvvy.jpg" alt="Power Rangers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Morphs might be the most obviously exciting aspect of the v3.3 release, but there's lots of other features to dig into.&lt;/p&gt;

&lt;p&gt;StimulusReflex now supports &lt;a href="https://docs.stimulusreflex.com/authentication#tokens-subscription-based" rel="noopener noreferrer"&gt;subscription-based authentication&lt;/a&gt;, which means that you can authenticate your Reflexes with JWT tokens. This is great if you're supporting mixed-device workflows or supporting clients across multiple domains.&lt;/p&gt;

&lt;p&gt;You can try out token auth by cloning and running &lt;a href="https://github.com/leastbad/stimulus_reflex_harness/tree/token_auth" rel="noopener noreferrer"&gt;this sample project&lt;/a&gt;, which itself is based on the &lt;a href="https://github.com/leastbad/stimulus_reflex_harness" rel="noopener noreferrer"&gt;StimulusReflex Harness&lt;/a&gt;, a barebones app that is perfect for testing Reflex ideas.&lt;/p&gt;

&lt;p&gt;People are finally starting to realize that &lt;a href="https://cableready.stimulusreflex.com" rel="noopener noreferrer"&gt;CableReady&lt;/a&gt; is the true secret sauce behind StimulusReflex, and is possibly the most under-appreciated powerhouse in the Rails ecosystem. How crazy is it that you can trigger client side DOM events in your ActiveRecord callback functions? Or than you can implement paginated endless scroll solutions in a few simple lines? If it was a superhero, kids would complain that it was too powerful.&lt;/p&gt;

&lt;p&gt;There's now &lt;a href="https://docs.stimulusreflex.com/lifecycle#stimulusreflex-library-events" rel="noopener noreferrer"&gt;library-level client-side lifecycle events&lt;/a&gt;: &lt;code&gt;connected&lt;/code&gt;, &lt;code&gt;disconnected&lt;/code&gt;, &lt;code&gt;rejected&lt;/code&gt; and &lt;code&gt;ready&lt;/code&gt; let you build UIs that know if there's a problem reaching the server in real-time.&lt;/p&gt;

&lt;p&gt;We've drastically improved the infrastructure around lifecycle events, callbacks and promises. Your &lt;code&gt;afterReflex&lt;/code&gt; method won't fire until after all page mutations have completed, no matter how many operations there are.&lt;/p&gt;

&lt;p&gt;Behind the scenes, every Reflex operation has a &lt;code&gt;reflexId&lt;/code&gt;. You can now provide your own, and all lifecycle events, callbacks and promises &lt;a href="https://docs.stimulusreflex.com/lifecycle#event-metadata" rel="noopener noreferrer"&gt;provide the reflexId&lt;/a&gt; they correspond to. That means you can build applications which &lt;strong&gt;treat Reflexes as transactions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tons of work has gone into removing friction from the installation, setup and integration of Reflexes into your project. We've doubled-down on Redis as a dependency, and enforce caching in your development environment. The generators have been freshened up with more helpful instructions. We have tried hard to improve debug output, warnings and error messages across the framework. And going forward, there will be a loud warning on your console if your Gem and NPM package versions are out of date.&lt;/p&gt;

&lt;p&gt;I'm personally really proud of the documentation for StimulusReflex, which has been dramatically expanded to cover Morphs, deployment, troubleshooting, form processing, &lt;a href="https://docs.stimulusreflex.com/authentication#multi-tenant-applications" rel="noopener noreferrer"&gt;support for multi-tenant apps&lt;/a&gt;, &lt;a href="https://docs.stimulusreflex.com/authentication#pundit" rel="noopener noreferrer"&gt;support for Pundit&lt;/a&gt;, &lt;a href="https://docs.stimulusreflex.com/patterns#internationalization" rel="noopener noreferrer"&gt;support for I18N&lt;/a&gt;, and lots more I'm forgetting.&lt;/p&gt;

&lt;p&gt;We're also seeing incredible work being done in the ecosystem of tools that exists alongside StimulusReflex and CableReady. &lt;a href="https://github.com/julianrubisch/futurism" rel="noopener noreferrer"&gt;Futurism&lt;/a&gt; enables powerful lazy-load capacity by using CableReady to fire just-in-time updates to your DOM like a T-shirt cannon. And &lt;a href="https://github.com/joshleblanc/view_component_reflex" rel="noopener noreferrer"&gt;ViewComponentReflex&lt;/a&gt; lets you bring StimulusReflex to your ViewComponents, allowing them to maintain their internal state between Reflex operations.&lt;/p&gt;

&lt;p&gt;A month ago, I soft-launched &lt;a href="https://stimulusconnect.com" rel="noopener noreferrer"&gt;StimulusConnect&lt;/a&gt;, "the release tracker and news aggregator for Stimulus and its surrounding ecosystem". I think it's already pretty awesome, and we're just getting started. My &lt;a href="https://discuss.rubyonrails.org/t/turbolinks-and-stimulus-are-developed-behind-a-wall/75124" rel="noopener noreferrer"&gt;Turbolinks and Stimulus are developed behind a wall&lt;/a&gt; post in the "May of WTFs" is now (sadly) the #1 most-liked thread on the Rails forum, which suggests that the community needs to step up. I'm going to do my part.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fohr0r5mvoxbj1fdlqrfp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fohr0r5mvoxbj1fdlqrfp.jpg" alt="Things I Hate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like I said at the beginning of this post, I came to StimulusReflex because React seemed untenable to me.&lt;/p&gt;

&lt;p&gt;As a developer, it's mind-boggling that we would give up so much power and flexibility when Rails and StimulusReflex prove that React is only &lt;a href="https://rubyonrails.org/doctrine/" rel="noopener noreferrer"&gt;one of several potential future paths for our community to take&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a citizen of the world, I don't like that &lt;a href="https://www.buzzfeednews.com/article/craigsilverman/facebook-ignore-political-manipulation-whistleblower-memo" rel="noopener noreferrer"&gt;Facebook has played a role&lt;/a&gt; - somewhere between &lt;em&gt;turned a blind eye&lt;/em&gt; and &lt;em&gt;utterly damning&lt;/em&gt; - in enabling the fascist, dystopian hellscape we're living through as a society.&lt;/p&gt;

&lt;p&gt;I don't want to piss in anyone's Life Cereal but every time you use React in one of your projects, at home or at work, you're giving tacit support to Facebook funding and ultimately steering the tools that we use to build the future of the web.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;StimulusReflex means that you still have a choice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/utxCm3uLhIE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>rails</category>
      <category>javascript</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I made a Rails ecosystem map, inspired by Laravel</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Fri, 28 Aug 2020 19:00:38 +0000</pubDate>
      <link>https://forem.com/leastbad/i-made-a-rails-ecosystem-map-inspired-by-laravel-47c5</link>
      <guid>https://forem.com/leastbad/i-made-a-rails-ecosystem-map-inspired-by-laravel-47c5</guid>
      <description>&lt;p&gt;I have to be honest and say that I don't really &lt;em&gt;get&lt;/em&gt; the appeal of Laravel.&lt;/p&gt;

&lt;p&gt;Full disclosure: I'm a long-term Rails developer. I am part of the &lt;a href="https://docs.stimulusreflex.com"&gt;StimulusReflex&lt;/a&gt; core team and I also do a lot of work with the &lt;a href="https://stimulusjs.org"&gt;Stimulus&lt;/a&gt; JS framework. It's safe to say that I love working in Ruby.&lt;/p&gt;

&lt;p&gt;When Rails first started to gain traction, I was coming from the VB/ASP world but it seemed like most folks in our community were running quickly away from either PHP, Java or Perl. It certainly seemed for a long time as though PHP - which had the advantage of being able to run self-contained if FTP'd to just about any Apache webserver - was not a technology to be envied. It presented as a culture of cargo-culting noobs who were often completely ignorant of the very basics of what they were smashing together.&lt;/p&gt;

&lt;p&gt;Then Facebook happened, and it's written in PHP. Fast-forward and in 2020 PHP is taken seriously (again?). There's even a workable Rails copycat called Laravel that has borrowed as many concepts from Rails and Django as you have fingers and toes to count. There is nothing wrong with that, although a little bit more acknowledgement would seem intellectually honest. Long live open source.&lt;/p&gt;

&lt;p&gt;Anyhow, last night someone joined the StimulusReflex &lt;a href="https://discord.gg/XveN625"&gt;Discord&lt;/a&gt; and suggested that Rails should "copy more stuff from Laravel". As it happens, I happen to think that Laravel does a much slicker job of promoting itself, and that's because Laravel is developed like a product while Rails is always going to be an abstraction from Basecamp. To that end, I decided that Rails needed a colourful grid showing the various major projects and technologies people use when building with Rails, too.&lt;/p&gt;

&lt;p&gt;It's a bit tongue-in-cheek, because the image is directly lifted from Laravel's marketing site the way Laravel lifts its features from Rails:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VHmdltIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h71qj6amuzc3eg6udjle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VHmdltIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h71qj6amuzc3eg6udjle.png" alt="Rails ecosystem"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also, notably, not the list of technologies that I would point people towards. For example, Devise, ViewComponent, StimulusReflex and CableReady, Turbolinks, Rolify, Nokogiri, Pagy, State Machines, Friendly ID, Local Time, Faker, Discard, Rouge, Redcarpet, not to mention StandardRB, Letter Opener and a half-dozen Rails frameworks like ActiveStorage, ActionText, ActionMailer and ActionMailbox are all far more interesting (and applicable) to me than pre-made Vagrant boxes and OAuth 2 servers.&lt;/p&gt;

&lt;p&gt;However, the main thing that Rails has which Laravel cannot copy is Ruby. Ruby is a language that was designed to optimize for programmer happiness. &lt;a href="https://rubyonrails.org/doctrine/"&gt;Rails is the best web framework because it is written in Ruby.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you use the best tools, you benefit from a surplus that others simply don't have.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/4PVViBjukAE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>rails</category>
      <category>laravel</category>
      <category>ruby</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Introducing stimulus-shortcut</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Thu, 23 Jul 2020 01:24:51 +0000</pubDate>
      <link>https://forem.com/leastbad/introducing-stimulus-shortcut-38he</link>
      <guid>https://forem.com/leastbad/introducing-stimulus-shortcut-38he</guid>
      <description>&lt;p&gt;Hot on the heels of &lt;a href="https://dev.to/leastbad/introducing-stimulus-hotkeys-4f98"&gt;stimulus-hotkeys&lt;/a&gt;, I am thrilled to release &lt;a href="https://www.npmjs.com/package/stimulus-shortcut"&gt;stimulus-shortcut&lt;/a&gt;, "a Stimulus controller for mapping keystrokes to element events".&lt;/p&gt;

&lt;p&gt;Based on the &lt;a href="https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz"&gt;preview version&lt;/a&gt; of Stimulus 2.0, &lt;code&gt;stimulus-hotkeys&lt;/code&gt; wraps the amazing &lt;a href="https://wangchujiang.com/hotkeys/"&gt;HotKeys.js&lt;/a&gt; library and takes advantage of the new Stimulus &lt;a href="https://github.com/stimulusjs/stimulus/pull/202"&gt;Values API&lt;/a&gt; to bind the keystrokes people type to the default events of elements on your page.&lt;/p&gt;

&lt;p&gt;Let's look at a simple example, in which the user hits the "p" key and it makes the link click itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"shortcut"&lt;/span&gt; &lt;span class="na"&gt;data-shortcut-key-value=&lt;/span&gt;&lt;span class="s"&gt;"p"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Type 'p' to activate me!&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It's a sort of sister-controller to &lt;code&gt;stimulus-hotkeys&lt;/code&gt; in that they share dependencies and together, cover both common shortcut key patterns. They can be used happily together in the same project.&lt;/p&gt;

&lt;p&gt;That's about it! You can learn more on the &lt;a href="https://github.com/leastbad/stimulus-shortcut"&gt;Github project page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/p-mGXLgGqkY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>stimulus</category>
      <category>hotkeys</category>
      <category>shortcuts</category>
    </item>
    <item>
      <title>Introducing stimulus-hotkeys</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Thu, 16 Jul 2020 02:15:47 +0000</pubDate>
      <link>https://forem.com/leastbad/introducing-stimulus-hotkeys-4f98</link>
      <guid>https://forem.com/leastbad/introducing-stimulus-hotkeys-4f98</guid>
      <description>&lt;p&gt;Continuing with my mission of crafting a viable ecosystem of elegant, composable Stimulus controllers, I am pleased to unveil &lt;a href="https://www.npmjs.com/package/stimulus-hotkeys"&gt;stimulus-hotkeys&lt;/a&gt;, "a Stimulus controller for mapping keystrokes to behaviors".&lt;/p&gt;

&lt;p&gt;Based on the &lt;a href="https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz"&gt;preview version&lt;/a&gt; of Stimulus 2.0, &lt;code&gt;stimulus-hotkeys&lt;/code&gt; wraps the amazing &lt;a href="https://wangchujiang.com/hotkeys/"&gt;HotKeys.js&lt;/a&gt; library and takes advantage of the new Stimulus &lt;a href="https://github.com/stimulusjs/stimulus/pull/202"&gt;Values API&lt;/a&gt; to bind the keystrokes people type to functions in your controllers.&lt;/p&gt;

&lt;p&gt;Configuration is done by providing a JSON-compliant object to the &lt;code&gt;data-hotkeys-bindings-value&lt;/code&gt; attribute. The object keys are literally the key(s) your users will press, while the values are a mapping that resembles a Stimulus action:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;selector-&amp;gt;identifier#method&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's look at a simple example, in which the user hits the "p" key and will see "PONG" on the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"hotkeys"&lt;/span&gt; 
     &lt;span class="na"&gt;data-hotkeys-bindings-value=&lt;/span&gt;&lt;span class="s"&gt;'{"p": "#foo-&amp;gt;example#ping"}'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"foo"&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// example_controller.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PONG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're new to Stimulus, there's some interesting meta topics to consider.&lt;/p&gt;

&lt;p&gt;I mentioned at the beginning of this post that this is a contribution towards an ecosystem of composable Stimulus controllers. This means that many common objectives can be completed just by adding a controller identifier to an element in your page. This might seem familiar to people who have used Bootstrap components that magically activate just by putting attributes on the right markup.&lt;/p&gt;

&lt;p&gt;The Stimulus approach is similar but improved because it's both standardized and &lt;a href="https://leastbad.com/mutation-first-development"&gt;idempotent&lt;/a&gt;; that is, you can dynamically add elements to your page after the initial load (via Ajax calls or a &lt;a href="https://docs.stimulusreflex.com"&gt;StimulusReflex&lt;/a&gt; update) and it will just pick up any controllers without you needing to initialize them.&lt;/p&gt;

&lt;p&gt;Since you can attach multiple Stimulus controllers to a single element, combining packaged controllers like &lt;code&gt;stimulus-hotkeys&lt;/code&gt; with "home-cooked" controllers that are specific to your project is easy and powerful. You can &lt;em&gt;compose&lt;/em&gt; off-the-shelf functionality with sprinkles of custom logic. For those of you who love Alpine, &lt;em&gt;this was the precise moment when you lost the war&lt;/em&gt;. I'm sorry, but it's true. Please take this package to your queen.&lt;/p&gt;

&lt;p&gt;Alright, we've reached the end! If you're still here, I have a bonus reward for you: since this is a Stimulus component and updates are &lt;strong&gt;live&lt;/strong&gt;, you can give your users the ability to dynamically create their own keyboard shortcuts by setting the &lt;code&gt;data-hotkeys-bindings-value&lt;/code&gt; attribute to a new JSON object.&lt;/p&gt;

&lt;p&gt;And when you're done, the JSON representing that user's custom shortcut mappings can be saved to a JSON column on your User model. 🤯&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wFKswPaxDtA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>stimulus</category>
      <category>hotkeys</category>
    </item>
    <item>
      <title>Introducing jquery-events-to-dom-events (and jboo)</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Tue, 12 May 2020 15:30:37 +0000</pubDate>
      <link>https://forem.com/leastbad/introducing-jquery-events-to-dom-events-and-jboo-4bll</link>
      <guid>https://forem.com/leastbad/introducing-jquery-events-to-dom-events-and-jboo-4bll</guid>
      <description>&lt;p&gt;Did you know that &lt;strong&gt;jQuery events aren't events&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;It's true - and it'll really mess up your night if you need to capture events from legacy jQuery components. Looking at you, &lt;code&gt;hidden.bs.modal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I needed a way to make &lt;code&gt;$(document).trigger('fart')&lt;/code&gt; emit a standard &lt;code&gt;$fart&lt;/code&gt; DOM event, so I wrote it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/jquery-events-to-dom-events"&gt;https://www.npmjs.com/package/jquery-events-to-dom-events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This library is short and sweet, with zero dependencies - including jQuery. It's just two functions: &lt;code&gt;delegate&lt;/code&gt; and &lt;code&gt;abnegate&lt;/code&gt;. It is &lt;a href="https://leastbad.com/mutation-first-development"&gt;Mutation-First&lt;/a&gt;; designed to work great in Stimulus and supports Turbolinks out of the box.&lt;/p&gt;

&lt;p&gt;It even has the secret ability to listen for DOM events with jQuery event listeners, but don't tell anyone.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://codepen.io/leastbad/pen/VwvQxxJ?editors=1011"&gt;&lt;strong&gt;try it now&lt;/strong&gt; on CodePen&lt;/a&gt; or even better, &lt;a href="https://github.com/leastbad/jboo"&gt;clone a sample Rails project&lt;/a&gt; to experiment in a mutation-first context with Stimulus.&lt;/p&gt;

&lt;p&gt;The Rails project is called &lt;strong&gt;jboo&lt;/strong&gt;. Don't read into the name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: it is assumed that &lt;a href="https://github.com/leastbad/jquery-events-to-dom-events#setup"&gt;jQuery is available in the global window scope&lt;/a&gt; as &lt;code&gt;$&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the most basic configuration, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;import { delegate } from 'jquery-events-to-dom-events'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;delegate(eventName)&lt;/code&gt; for every jQuery event you want to capture.&lt;/li&gt;
&lt;li&gt;Set up DOM event listeners for those events, &lt;strong&gt;prepending a $ to the event name&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's say that you want to respond to the user closing a Bootstrap modal window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;delegate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jquery-events-to-dom-events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden.bs.modal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$hidden.bs.modal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Modal closed!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That might be it. Go make a sandwich - you've earned it.&lt;/p&gt;




&lt;p&gt;You can learn more about working with jquery-events-to-dom-events on &lt;a href="https://github.com/leastbad/jquery-events-to-dom-events"&gt;the Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As always, the right music is important for establishing proper context.&lt;/p&gt;

&lt;p&gt;You don't have to listen to music, but your transpiler configuration will almost certainly fail lint checks if you are not listening to "&lt;em&gt;In Harmony New Found Freedom&lt;/em&gt;" by &lt;a href="https://en.wikipedia.org/wiki/Swirlies"&gt;The Swirlies&lt;/a&gt;, from their 1996 album "&lt;a href="https://www.youtube.com/watch?v=S1rTKIsDS8o"&gt;They Spent Their Wild Youthful Days In The Glittering World Of The Salons&lt;/a&gt;" while you integrate this library.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/idCfuK4t2vo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>events</category>
      <category>jquery</category>
    </item>
    <item>
      <title>Introducing stimulus-image-grid</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Tue, 05 May 2020 22:28:50 +0000</pubDate>
      <link>https://forem.com/leastbad/introducing-stimulus-image-grid-pc3</link>
      <guid>https://forem.com/leastbad/introducing-stimulus-image-grid-pc3</guid>
      <description>&lt;p&gt;I published my first npm package today!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/stimulus-image-grid"&gt;https://www.npmjs.com/package/stimulus-image-grid&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With only three optional parameters, this is a simple, drop-in, backend-agnostic, code-free solution that is completely free of CSS opinions. It's responsive and scales to whatever bounding container you give it. &lt;/p&gt;

&lt;p&gt;It's also performant AF: stimulus-image-grid uses the ResizeObserver so there's zero screen flicker. It's compatible with Turbolinks by design and free for personal and commercial use.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/e08sFfBYoiE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for StimulusJS
&lt;/h2&gt;

&lt;p&gt;This &lt;a href="https://stimulusjs.org/"&gt;Stimulus&lt;/a&gt; controller allows you to make any configurations for the image grid directly with data attributes in your HTML. Once registered in your Stimulus application, you can use it anywhere you like.&lt;/p&gt;

&lt;p&gt;Here is a simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"image-grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.it/350x300/EEE04A/ffffff"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.it/420x320/5cb85c/ffffff"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.it/320x380/5bc0de/ffffff"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.it/472x500/f0ad4e/ffffff"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.it/540x360/FF3167/ffffff"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Yes, that's really it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Add an import to your main JS entry point, and then register it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stimulus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ImageGrid&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stimulus-image-grid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;definitionsFromContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stimulus/webpack-helpers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../controllers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;definitionsFromContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Manually register ImageGrid as a Stimulus controller&lt;/span&gt;
&lt;span class="nx"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image-grid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ImageGrid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And you're done! Note, this package relies on the alpha preview of Stimulus v2, which is stable and available &lt;a href="https://github.com/stimulusjs/stimulus/pull/202"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Introducing Optimism: realtime remote form validation for Rails</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Fri, 28 Feb 2020 22:09:41 +0000</pubDate>
      <link>https://forem.com/leastbad/introducing-optimism-realtime-remote-form-validation-for-rails-po6</link>
      <guid>https://forem.com/leastbad/introducing-optimism-realtime-remote-form-validation-for-rails-po6</guid>
      <description>&lt;p&gt;I'm proud to announce the public release of &lt;a href="https://github.com/leastbad/optimism"&gt;Optimism&lt;/a&gt;, a new Ruby on Rails gem that makes it easy to add realtime validation error messages to your applications.&lt;/p&gt;

&lt;p&gt;When a model validation error prevents an update from succeeding, Optimism builds a list of issues that must be resolved. This list is broadcast to the browser over a websocket connection, and the live document is changed to show the necessary validation hints. No page refreshes are required and the entire process happens faster than you can blink.&lt;/p&gt;

&lt;p&gt;Optimism injects text content, CSS class updates and even DOM events into your page. It has no client-side scripting requirements beyond a dependency to the awesome &lt;a href="https://cableready.stimulusreflex.com/"&gt;CableReady&lt;/a&gt; library. It automatically handles multi-level nested forms and plays well with other technologies, such as Turbolinks.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://optimism-demo.herokuapp.com"&gt;try a live demo&lt;/a&gt; right now.&lt;/p&gt;

&lt;p&gt;Full documentation is available &lt;a href="https://optimism.leastbad.com"&gt;here&lt;/a&gt;. The project lives on Github &lt;a href="https://github.com/leastbad/optimism"&gt;here&lt;/a&gt; and you can get help via Discord &lt;a href="https://discord.gg/wKzsAYJ"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/dAP3unTl1f4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>websockets</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The best one-line Stimulus power move</title>
      <dc:creator>leastbad</dc:creator>
      <pubDate>Fri, 14 Feb 2020 13:23:52 +0000</pubDate>
      <link>https://forem.com/leastbad/the-best-one-line-stimulus-power-move-2o90</link>
      <guid>https://forem.com/leastbad/the-best-one-line-stimulus-power-move-2o90</guid>
      <description>&lt;p&gt;&lt;a href="https://stimulusjs.org"&gt;Stimulus&lt;/a&gt; is a tiny and absurdly productive JavaScript framework for developers who are looking for just the right amount of structure (lifecycle events and standard HTML) without attempting to re-invent how the web works (no template rendering or routing). It is criminally underappreciated in the JavaScript community.&lt;/p&gt;

&lt;p&gt;When using Stimulus, you write controllers in JavaScript and attach instances of those controllers to DOM elements by setting &lt;code&gt;data-controller="controller-name"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, there's no easy way to access methods in a controller from another controller, external scripts, jQuery plugins or the console... &lt;em&gt;or is there?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Before I do the big reveal, there is &lt;em&gt;technically&lt;/em&gt; a way to access another controller instance from inside of a controller. It's an undocumented method so there's no guarantee that it won't disapper someday, but the real clue that this isn't intended to be used is the laughably long name: &lt;code&gt;this.application.getControllerForElementAndIdentifier(element, controller)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Controllers have access to the global Stimulus application scope, which has &lt;code&gt;getControllerForElementAndIdentifier&lt;/code&gt; as a member function. If you have a reference to the element with the controller attached and the name of the controller, you can get a reference to any controller on your page. Still, this doesn't offer any solutions to developers working outside of a Stimulus controller.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Here's what we should all do instead.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your controller's &lt;code&gt;connect()&lt;/code&gt; method, add this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.element[this.identifier] = this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! This hangs a reference to the Stimulus controller instance off the DOM element that has the same name as the controller itself. Now, if you can get a reference to the element, you can access &lt;code&gt;element.controllerName&lt;/code&gt; anywhere you need it.&lt;/p&gt;

&lt;p&gt;What's cool about this trick is that since Stimulus calls &lt;code&gt;connect()&lt;/code&gt; every time an instance is created, you can be confident that your elements will always have a direct reference to their parent, even if they are attached to elements that are dynamically inserted by something like &lt;a href="https://github.com/patrick-steele-idem/morphdom"&gt;morphdom&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;this.identifier&lt;/code&gt; can be replaced with any camelCase string as you desire.&lt;/p&gt;




&lt;p&gt;I'll provide a basic example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// test_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
  connect () {
    this.element[this.identifier] = this
  }

  name () {
    this.element.innerHTML = `I am ${this.element.dataset.name}.`
  }
}

// index.html
&amp;lt;div id="person" data-controller="test" data-name="Steve"&amp;gt;&amp;lt;/div&amp;gt;

// run this in your console
document.querySelector('#person').test.name()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything goes according to plan, the &lt;code&gt;div&lt;/code&gt; should now say: &lt;em&gt;I am Steve.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;If you want to automatically camelCase the name of your Stimulus controller, "one-line" becomes dubious, but it can still be done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    this.element[
      (str =&amp;gt; {
        return str
          .split('--')
          .slice(-1)[0]
          .split(/[-_]/)
          .map(w =&amp;gt; w.replace(/./, m =&amp;gt; m.toUpperCase()))
          .join('')
          .replace(/^\w/, c =&amp;gt; c.toLowerCase())
      })(this.identifier)
    ] = this
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I broke the statement up into multiple lines to help illustrate the acrobatics required to pull this off. It can still be expressed on a single line if you choose. However, the devil hides in clever code.&lt;/p&gt;




&lt;p&gt;The only caveat I can think of is that you should exercise common sense and not expose any controller instances that you wouldn't want people to access. Even though there's no visible proof that an element has a variable on it in the inspector, you shouldn't assume that it's locked down.&lt;/p&gt;

&lt;p&gt;If you're working in FinTech, you might need to skip this technique. Everyone else should be doing this by default.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>stimulus</category>
    </item>
  </channel>
</rss>
