<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><link href="https://michaeljherold.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://michaeljherold.com/" rel="alternate" type="text/html" /><updated>2021-03-22T09:00:00-05:00</updated><id>https://michaeljherold.com/feed.xml</id><title type="html">Michael Herold | Articles</title><subtitle>Writing about the art and craft of software and leadership.</subtitle><author><name>Michael Herold</name><email>hello@michaeljherold.com</email><uri>https://michaeljherold.com</uri></author><entry><title type="html">Extending ActiveRecord with custom types</title><link href="https://michaeljherold.com/articles/extending-activerecord-with-custom-types/" rel="alternate" type="text/html" title="Extending ActiveRecord with custom types" /><published>2021-03-22T09:00:00-05:00</published><updated>2021-03-22T09:00:00-05:00</updated><id>repo://articles.collection/_articles/extending-activerecord-with-custom-types.md</id><category term="activerecord" /><category term="ruby-on-rails" /><content type="html" xml:base="https://michaeljherold.com/articles/extending-activerecord-with-custom-types/">&lt;p&gt;In &lt;a href=&quot;/articles/activerecord-attributes-api/&quot;&gt;the last article&lt;/a&gt;, we saw how to use the ActiveRecord Attributes API to make Ruby on Rails models more communicative. The two explicit benefits of doing so from that article were a more discoverable data model and a uniform way of defining different types of attributes. The article also hinted at using richer data types to model attributes. That is the topic of this article.&lt;/p&gt;

&lt;p&gt;The Attributes API is extensible and allows you to define clean interfaces for using Ruby objects as attribute types. Instead of relying on primitives, you can create your domain logic as Ruby objects that ActiveRecord then coerces form data into. This is a powerful technique that allows you to limit the &lt;a href=&quot;https://refactoring.guru/smells/primitive-obsession&quot;&gt;Primitive Obsession&lt;/a&gt; code smell in your projects.&lt;/p&gt;

&lt;p&gt;This article details how to create your own ActiveRecord type classes. Once we have a type class, we’ll register it to easily use it within a model, though we’ll also cover how to use it without registering it. Then, we’ll go over a concrete example. Lastly, we’ll talk through some rules of thumb for creating custom type classes.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;type-classes&quot;&gt;Type classes&lt;/h2&gt;

&lt;p&gt;To create a type class, you only need to define a plain old Ruby class that meets a four method interface:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#cast(value)&lt;/code&gt; performs a type cast on input, like when using the setter methods from accepting a form submission.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#serialize(value)&lt;/code&gt; casts the value from the Ruby type to the type needed by the database.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#deserialize(value)&lt;/code&gt; casts the value from the database into the Ruby type.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#type&lt;/code&gt; returns a Symbol corresponding to the Symbol used to register it with the type registry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In some cases, you can use private helper methods to run similar transformations for these interface methods. For example, if you end up serializing the value into a String-like field, your &lt;code class=&quot;&quot;&gt;#cast&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;#deserialize&lt;/code&gt; methods might be able to share code.&lt;/p&gt;

&lt;p&gt;If you need to be able to configure your type, you will also want to define an &lt;code class=&quot;&quot;&gt;#initialize&lt;/code&gt; method to set the instance variables you will need as configuration.&lt;/p&gt;

&lt;p&gt;While you can use any class that conforms to this interface, sometimes it makes sense to inherit from a built-in base class from ActiveRecord.&lt;/p&gt;

&lt;h3 id=&quot;built-in-base-classes&quot;&gt;Built-in base classes&lt;/h3&gt;

&lt;p&gt;ActiveRecord comes with base classes that can help you create type classes for certain patterns. For example, if you have an immutable value type, inheriting from &lt;code class=&quot;&quot;&gt;ActiveRecord::Type::Value&lt;/code&gt; might make it easier to implement your value type.&lt;/p&gt;

&lt;p&gt;Likewise, when you want to add special behavior on top of an existing one, it can help to look at the built-ins. ActiveRecord types are a superset of ActiveModel types. This means that you will need to look at both the &lt;code class=&quot;&quot;&gt;ActiveRecord::Type&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;ActiveModel::Type&lt;/code&gt; modules to get the list of built-in types.&lt;/p&gt;

&lt;h2 id=&quot;registering-a-type-class&quot;&gt;Registering a type class&lt;/h2&gt;

&lt;p&gt;Once you have created your type, the easiest way to make it usable from your model is to register it in the &lt;code class=&quot;&quot;&gt;ActiveRecord::Type&lt;/code&gt; registry. Doing so looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:my_identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyClass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With the above registration, you can use your new type class with the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:my_attribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:my_identifier&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;configurable-type-declarations&quot;&gt;Configurable type declarations&lt;/h3&gt;

&lt;p&gt;For configurable types, you can pass arguments on the &lt;code class=&quot;&quot;&gt;attribute&lt;/code&gt; declaration to forward them to the type class instance. For example, given a class called &lt;code class=&quot;&quot;&gt;MyClass&lt;/code&gt; with an initializer like &lt;code class=&quot;&quot;&gt;#initialize(limit:, precision:)&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:my_attribute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:my_identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;precision: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Positional arguments work as well, like with a normal method call.&lt;/p&gt;

&lt;h2 id=&quot;using-a-type-class-without-registering-it&quot;&gt;Using a type class without registering it&lt;/h2&gt;

&lt;p&gt;While registering the type makes it easier to use, you don’t &lt;em&gt;have&lt;/em&gt; to register it to use it. To use an unregistered type, pass an instance of the class to the &lt;code class=&quot;&quot;&gt;attribute&lt;/code&gt; class method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For configurable classes, pass the arguments to the constructor:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;limit: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;precision: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now that you know how to create, register, and use a class, let’s look at an example.&lt;/p&gt;

&lt;h2 id=&quot;example-globalid&quot;&gt;Example: GlobalID&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/rails/globalid&quot;&gt;GlobalID&lt;/a&gt; is a default Rails gem that enables you to store a reference to an object using a URI specific to your application. In cases where you have multiple Rails applications — or Rails applications that talk to non-Rails applications — they can be handy for loading information from other services. The behavior of a GlobalID is what allows ActiveJob to transparently load records from the database.&lt;/p&gt;

&lt;p&gt;Sometimes it makes sense to store a reference to an object in the database as a GlobalID. You &lt;em&gt;could&lt;/em&gt; store the GlobalID as a simple string, but that makes for a clumsy way to look up the reference. Instead, let’s create an ActiveRecord type that accepts a string value but transforms it into a GlobalID object.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ActiveRecord&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Type&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GlobalID&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;presence&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;type&lt;/span&gt;
        &lt;span class=&quot;ss&quot;&gt;:global_id&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

      &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cast_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_gid&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond_to?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:to_gid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GlobalID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:global_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GlobalID&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On line 3, you’ll see that the type inherits from the String type. This makes it so you can ignore a lot of the details of the other methods since this is a specialization of a String column in your database.&lt;/p&gt;

&lt;p&gt;On lines 4-6, you’ll see the definition of the &lt;code class=&quot;&quot;&gt;#serialize&lt;/code&gt; method. At this point, we know that the value we’re dealing with is a nilable GlobalID. For this method on this specific type, we need to ensure that we transform the value into a nilable String. Converting the GlobalID to a String happens with &lt;code class=&quot;&quot;&gt;#to_s&lt;/code&gt;. However, this means it will place an empty string when the value is nil. To prevent that, &lt;code class=&quot;&quot;&gt;#presence&lt;/code&gt; outputs the value when it’s present and otherwise outputs &lt;code class=&quot;&quot;&gt;nil&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On lines 10-14 is a specialization of &lt;code class=&quot;&quot;&gt;ActiveRecord::Type::Value&lt;/code&gt;’s &lt;code class=&quot;&quot;&gt;#cast_value&lt;/code&gt; helper. It takes a value that can be an instance of an ActiveRecord model, a String, or a GlobalID. For an ActiveRecord model, it converts the model to a GlobalID. Then, it delegates to &lt;code class=&quot;&quot;&gt;ActiveRecord::Type::String&lt;/code&gt; to cast the GlobalID or String to a frozen string. And lastly, it reconverts the String into a GlobalID. This is an inefficient, but effective, workflow that we can later optimize, if necessary.&lt;/p&gt;

&lt;p&gt;Lastly, on line 17, you’ll see the registration of the type as &lt;code class=&quot;&quot;&gt;:global_id&lt;/code&gt; to make it easily usable from your models.&lt;/p&gt;

&lt;p&gt;By leaning on the built-in class, this is all the change we need to have a fully functional GlobalID type! To use it, you can do the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:related_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:global_id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;related_to: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;related_to&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &apos;gid://myapp/User/1&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;MyModel&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;related_to: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;gid://otherapp/Foo/123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;related_to&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;locate&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; loads the record from otherapp using GlobalID&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;related_to: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;gid://otherapp/Foo/123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;MyModel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;related_to: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GlobalID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;gid://otherapp/Foo/123&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The database stores the GlobalID value object as a simple string, but when ActiveRecord loads the record the string becomes a GlobalID with all of its rich behavior. An, instead of being a one-off for this class, you can now reuse this behavior in other models as well!&lt;/p&gt;

&lt;p&gt;After seeing an example, hopefully your creativity is flowing, so let’s talk about some rules of thumb for making custom type classes.&lt;/p&gt;

&lt;h2 id=&quot;rules-of-thumb&quot;&gt;Rules of thumb&lt;/h2&gt;

&lt;p&gt;I have two main rules of thumb for creating ActiveRecord type classes. First, make your types robust, not strict. Second, try to use value objects for your types as much as possible.&lt;/p&gt;

&lt;h3 id=&quot;robustness&quot;&gt;Robustness&lt;/h3&gt;

&lt;p&gt;When creating types in other cases, generally you want to be strict so that you don’t end up introducing invalid data into your system. However, with the Attributes API, it’s best to follow the &lt;a href=&quot;https://en.wikipedia.org/wiki/Robustness%5Fprinciple&quot;&gt;robustness principle&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Be conservative in what you do, be liberal in what you accept.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s tempting to have your type classes raise an error whenever the value that you receive is invalid for the attribute. However, in cases where you are either adding typing to an existing application or where multiple writers are writing to the database from which you read, it’s better to take a gentler approach. Consider converting the value to &lt;code class=&quot;&quot;&gt;nil&lt;/code&gt; and then adding a validation for the attribute. Or even better, encode the invalid data as an &lt;a href=&quot;https://www.rubytapas.com/2016/08/02/episode-431-exceptional-value/&quot;&gt;exceptional value&lt;/a&gt; with a validation.&lt;/p&gt;

&lt;p&gt;By taking the gentler route, you prevent the case where bad data in the database makes it so you can’t even inspect the object without resorting to &lt;code class=&quot;&quot;&gt;ActiveRecord::Connection#execute&lt;/code&gt; or a direct database connection. Since the casting of the value raises an error, any time bad record with an invalid datum loads it will raise the exception. This is indiscriminate and can happen in a customer-facing controller, an API endpoint, or a diagnostic terminal.&lt;/p&gt;

&lt;h3 id=&quot;value-objects&quot;&gt;Value objects&lt;/h3&gt;

&lt;p&gt;The best type classes use &lt;a href=&quot;https://www.martinfowler.com/bliki/ValueObject.html&quot;&gt;value objects&lt;/a&gt; for their Ruby values. Value objects are immutable and have identity based on the values that they wrap, rather than identity of their own. Most of the prior art that you can reference uses value objects, so it’s easier to reference examples for them. Mutability also adds an additional set of methods that you have to either &lt;code class=&quot;&quot;&gt;include&lt;/code&gt; or define.&lt;/p&gt;

&lt;p&gt;When you are unable to use a value object, consider inheriting from the JSON (or, if you’re using PostgreSQL, JSONB type). It is already a mutable type class and will handle the changes you need for mutability out of the box for you.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this article, we covered creating your own custom type classes for ActiveRecord. We then found how to register them to make them easier to use, but also talked about using them without registering them. Then we went through an example with a type that models GlobalIDs and discussed two rules of thumb for creating your own type classes.&lt;/p&gt;

&lt;p&gt;In the next article, we’ll see how to use type modifiers to make your type classes even more extensible. That article will show two examples of custom type classes, so if you’re interested in learning more about this technique, make sure check it out next week!&lt;/p&gt;

&lt;p&gt;Have you created your own custom ActiveRecord types? What types did you model? Did you run into any issues?&lt;/p&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/woodtype-846089_1280.jpg" /><media:content medium="image" url="https://michaeljherold.com/images/woodtype-846089_1280.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Use the Attributes API to make your Rails models more communicative</title><link href="https://michaeljherold.com/articles/activerecord-attributes-api/" rel="alternate" type="text/html" title="Use the Attributes API to make your Rails models more communicative" /><published>2021-03-15T09:00:00-05:00</published><updated>2021-03-15T09:00:00-05:00</updated><id>repo://articles.collection/_articles/activerecord-attributes-api.md</id><category term="activerecord" /><category term="ruby-on-rails" /><content type="html" xml:base="https://michaeljherold.com/articles/activerecord-attributes-api/">&lt;p&gt;ActiveRecord is the powerful object-relational mapper at the heart of &lt;a href=&quot;https://rubyonrails.org&quot;&gt;Ruby on Rails&lt;/a&gt;. By default, it gives you tools to quickly and easily create new database tables and map them to domain models. It follows the ethos of “convention over configuration” that David Heinemeier Hansson coined with the release of Rails. As such, with little application code, you get a powerful, database-backed model that Just Works™.&lt;/p&gt;

&lt;p&gt;However, few lines of code do not, necessarily, mean that the lines that are there are easy to understand. Does this model have a &lt;code class=&quot;&quot;&gt;name&lt;/code&gt; or a &lt;code class=&quot;&quot;&gt;description&lt;/code&gt;? How is that &lt;code class=&quot;&quot;&gt;expiry&lt;/code&gt; field encoded, as a Date or a Time? What fields exist on this table, again? These are examples of common questions when working in a Ruby on Rails application.&lt;/p&gt;

&lt;p&gt;To alleviate some of these issues, you can use the Attributes API when defining your models to make them more explicit. You can also use richer data types for your fields to ease working with subsets of your models. This article will share the history of the Attributes API, then show some concrete examples of using it.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;what-is-the-attributes-api&quot;&gt;What is the Attributes API?&lt;/h2&gt;

&lt;p&gt;Starting in Rails 4.2, then-maintainer of ActiveRecord, &lt;a href=&quot;https://github.com/sgrif&quot;&gt;Sage Griffin&lt;/a&gt;, added an API to separate the concerns of the columns of the underlying database from the attributes that ActiveRecord sets on its record models. This helped to make the internals of ActiveRecord cleaner and easier to modify. As with many decoupling scenarios, this had the added benefit of allowing independent changes to the two systems and, thus, opening a public API for use by Rails application developers.&lt;/p&gt;

&lt;p&gt;Before this change, the ActiveRecord directly set and modified the attributes of its models based on columns mapped from the database. The original design coupled these two things in the interest of making it easy to work with Rails; it was a good solution for the time and served its purpose well. However, it was also a limiting design in that, if you needed to color outside the lines of the framework, it was difficult to do.&lt;/p&gt;

&lt;p&gt;As an illustration, consider this problem. You have a ticket model that contains a state machine controlling the transitions between the &lt;code class=&quot;&quot;&gt;reserved&lt;/code&gt;, &lt;code class=&quot;&quot;&gt;issued&lt;/code&gt;, &lt;code class=&quot;&quot;&gt;redeemed&lt;/code&gt;, and &lt;code class=&quot;&quot;&gt;voided&lt;/code&gt; states. You &lt;code class=&quot;&quot;&gt;reserve&lt;/code&gt; a ticket while the customer purchases it. After the purchase, you &lt;code class=&quot;&quot;&gt;issue&lt;/code&gt; the ticket. Once the customer arrives at the event, you &lt;code class=&quot;&quot;&gt;redeem&lt;/code&gt; it. And if for any reason, you cancel the ticket, you consider it &lt;code class=&quot;&quot;&gt;voided&lt;/code&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/images/ticket-model.svg&quot; /&gt;
  &lt;figcaption&gt;&lt;p&gt;Figure 1: The state machine for the ticket example.&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The important parts of this example are the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;There are event names for transitioning between different states (e.g. &lt;code class=&quot;&quot;&gt;purchase&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Some states cannot transition to other states (e.g. &lt;code class=&quot;&quot;&gt;reserved&lt;/code&gt; cannot transition to &lt;code class=&quot;&quot;&gt;redeemed&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This state machine is an example of logic that you should encapsulate in a “plain old Ruby object” and use as a collaborator in your model. Without the Attributes API, you might do the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Ticket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# field: state&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;state&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;TicketState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This isn’t harmful in itself, but it’s a symptom of missing functionality in the framework. A trickier bit to work around, and the source of why API exists, involves domain objects and SQL queries. For example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TicketState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;issued&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Ticket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;state: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This SQL query requires you to handle the serialization of the &lt;code class=&quot;&quot;&gt;TicketState&lt;/code&gt; class to the database. Your &lt;code class=&quot;&quot;&gt;TicketState&lt;/code&gt; class shouldn’t know anything about databases; it’s only a state machine! Alternatively, you might attempt to always remember to write this query as &lt;code class=&quot;&quot;&gt;Ticket.where(state: state.state)&lt;/code&gt;. If you’re anything like me, you will forget this at the worst possible time: in the middle of an emergency.&lt;/p&gt;

&lt;p&gt;The Attributes API gives you a place to encode all of this information and make it easy to use rich domain models as fields in your ActiveRecord models.&lt;/p&gt;

&lt;h2 id=&quot;using-the-attributes-api&quot;&gt;Using the Attributes API&lt;/h2&gt;

&lt;p&gt;There are two pieces to the Attributes API that you need to know about to use it:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;ActiveRecord::Type&lt;/code&gt; classes for defining the types of data in your models.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;attribute&lt;/code&gt; declarations in your model files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There’s a lot of functionality within those two concepts, so let’s dig in.&lt;/p&gt;

&lt;h3 id=&quot;activerecord-types&quot;&gt;ActiveRecord types&lt;/h3&gt;

&lt;p&gt;Within the Rails code base, type classes live in three different modules. &lt;code class=&quot;&quot;&gt;ActiveRecord::Type&lt;/code&gt; classes inherit from &lt;code class=&quot;&quot;&gt;ActiveModel::Type&lt;/code&gt; that implement similar behavior for use in ActiveModel and, sometimes, are only aliases to constant in ActiveModel. PostgreSQL-specific type classes exist in the &lt;code class=&quot;&quot;&gt;ActiveRecord::ConnectionAdapters::PostgreSQL::OID&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;The contract for these classes consists of a slim, four methods&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#cast(value)&lt;/code&gt; performs a type cast on input, like when using the setter methods from accepting a form submission.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#serialize(value)&lt;/code&gt; casts the value from the Ruby type to the type needed by the database.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#deserialize(value)&lt;/code&gt; casts the value from the database into the Ruby type.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;#type&lt;/code&gt; is a Symbol corresponding to the Symbol used to register it with the type registry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unless you’re defining your own types — and if you are, stay tuned for my next article — you won’t ever use these methods directly. However, it’s helpful to know the interface in case you see some unexpected behavior and need to diagnose the issue.&lt;/p&gt;

&lt;h3 id=&quot;attribute-declarations&quot;&gt;Attribute declarations&lt;/h3&gt;

&lt;p&gt;Within your models, you can use the &lt;code class=&quot;&quot;&gt;attribute&lt;/code&gt; class method to declare an attribute with a type and any additional information about it. For example, these are all valid examples:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyModel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# A simple, documenting example&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:hosted_on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:date&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Have a default? Set it here! (Overrides database defaults.)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:likes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Is your default something that needs recalculated?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:expiry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;default: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_now&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# For the JSONB type in PostgreSQL, like a document model&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:metadata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:jsonb&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Have a PostgreSQL array field? Mark it like so:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;array: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# How about a PostgreSQL date range?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:valid_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;range: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Attributes don’t need a database column associated with them. If you have some datum that you need to accept but not persist, you can create an attribute for it, set validations for it, and even use dirty tracking if you want!&lt;/p&gt;

&lt;p&gt;Sadly, there is no API for listing the registered types. In Rails 6.1, you can run the following, but it’s not guaranteed to work in the future:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Type&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registry&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:@registrations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;__send__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The built-in types for all databases mirror the types you can use in your migrations, though with some different names (e.g. &lt;code class=&quot;&quot;&gt;bigint&lt;/code&gt; in migrations vs. &lt;code class=&quot;&quot;&gt;big_integer&lt;/code&gt; in attributes). Gems and database adapters may also add extra options so if you’re doing something exotic, it’s worth a look under the hood to see what’s available.&lt;/p&gt;

&lt;h2 id=&quot;benefits-of-declaring-your-attributes&quot;&gt;Benefits of declaring your attributes&lt;/h2&gt;

&lt;p&gt;There are three benefits to declaring your attributes in your models. First, you make the data model more discoverable when you onboard a new person into your project. Second, you use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Uniform%5Faccess%5Fprinciple&quot;&gt;uniform access principle&lt;/a&gt; to define different types of attributes for your model. And lastly, you enable richer behavior for your fields when it’s appropriate.&lt;/p&gt;

&lt;p&gt;Convention over configuration means there’s little ceremony to working in Rails, which allows you to move quickly. But it comes at the cost of discoverability for future you and your teammates. When you end up with a large model, it’s easy to forget the name of a value or what type it is. Adding an attribute declaration, particularly when you have a standard ordering to the paragraphs of code in your models, makes it easy to reference in the file when you’re working with it.&lt;/p&gt;

&lt;p&gt;Have you ever had to define a virtual attribute on a model? For example, you might have a confirmation system where you have your customers check a box, but don’t persist that to the database. If so, you probably defined that attribute using standard methods. That’s reasonable, but the Attributes API allows you to define that attribute just like a database-backed one so that all of the model’s attributes exist in the same paragraph of code. That makes it easier to see the shape of the class and what it does. This is an application of the uniform access principle, in a way, because it groups like things under the same interface.&lt;/p&gt;

&lt;p&gt;Like we saw with the ticket example above, we sometimes need to have complex collaborators as part of our models. In the ticket’s case, we wanted a state machine to manage the state of the class but had to resort to using a wrapping method. Instead, what if we could define our own type to handle that behavior:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Ticket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:ticket_state&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is a teaser of my next article, so check in next week to see how to do this!&lt;/p&gt;

&lt;p&gt;Everything isn’t sunshine and rainbows though; there are detriments to using the Attributes API as well.&lt;/p&gt;

&lt;h2 id=&quot;detriments-of-the-api&quot;&gt;Detriments of the API&lt;/h2&gt;

&lt;p&gt;Like with anything in life, there are trade-offs to using the Attributes API. The two big ones are the introduction of a new tool and boilerplate.&lt;/p&gt;

&lt;p&gt;There’s a common saying about projects where you only have so much “innovation budget” that you can spend on a project and hope that it succeeds. This guiding principle helps you to control the impulse to always use the hot, new technology or process on every project. Making decisions based on blog posts is one of the things you have to consider in this budget! There is overhead to using this technique. It’s relatively new to Rails, so you will likely have to teach it to your colleagues. Because it’s new, there will be friction for people using it for the first time. And because it’s something that you had to teach, you should also document the decision as well so that future you and your colleagues know why you made that decision.&lt;/p&gt;

&lt;p&gt;The second detriment comes down to requiring boilerplate, which is not the Rails Way. You already have the information in the schema, so why duplicate it in the model when Rails can discover it for you? You also can make standard methods for the class, so why do it this way? Do you always use the Attributes API or only sometimes? The decision to use something that can seem like boilerplate is fraught. I get that. But I believe that the trade-off is worth it.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The Attributes API allows you to customize the way that Rails casts data into fields, both database-backed and virtual. The API is a public part of Rails so has certain compatibility guarantees around it. It’s powerful and extensible. And it solves many problems using a small amount of code, both at the application level and the framework level.&lt;/p&gt;

&lt;p&gt;There are trade-offs to using the Attributes API. I believe the costs are worth it, particularly for complex problem sets. But if you have a limited amount of teaching ability in your team, it might be something to pause and think about before you adopt it.&lt;/p&gt;

&lt;p&gt;This article was just a primer on how to use it. Make sure to read my next article, where I will show you how to customize it!&lt;/p&gt;

&lt;p&gt;Have you used the Attributes API in a project? What was your experience?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;I &lt;em&gt;think&lt;/em&gt; the &lt;code class=&quot;&quot;&gt;#type&lt;/code&gt; method is necessary. It’s marked with a “no doc” comment so that indicates that it’s not, but I have never seen a type without it. If I’m wrong, let me know! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/ruby-on-rails.png" /><media:content medium="image" url="https://michaeljherold.com/images/ruby-on-rails.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Solving the right problem</title><link href="https://michaeljherold.com/articles/solving-the-right-problem/" rel="alternate" type="text/html" title="Solving the right problem" /><published>2021-03-08T09:00:00-06:00</published><updated>2021-03-08T09:00:00-06:00</updated><id>repo://articles.collection/_articles/solving-the-right-problem.md</id><category term="design" /><content type="html" xml:base="https://michaeljherold.com/articles/solving-the-right-problem/">&lt;p&gt;Recently, I &lt;a href=&quot;https://github.com/michaelherold/benchmark-memory/issues/12&quot;&gt;received a bug report&lt;/a&gt; on one of my open source projects. The bug report came from my co-maintainer on &lt;a href=&quot;https://github.com/hashie/hashie&quot;&gt;Hashie&lt;/a&gt;, &lt;a href=&quot;https://www.dblock.org/&quot;&gt;dB&lt;/a&gt;, who I hold as an exemplary asker of questions. I quickly read through the issue and the background information on his issue and quickly sent him a reply that his suggestion sounded reasonable and that I would accept a contribution adding that behavior. I was attempting to be more like him and act as a facilitator, which is an attribute that I admire in him. However, I did a poor job of this task.&lt;/p&gt;

&lt;p&gt;Notice what I said earlier: “I &lt;em&gt;quickly&lt;/em&gt; read through the question and background information.” Any time you find yourself quickly doing something a non-rote task, alarm bells should go off in your head. When it comes to complex tasks or understanding, &lt;em&gt;quick&lt;/em&gt; is the enemy of &lt;em&gt;right&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Quick begets shortcuts. Shortcuts in understanding. Shortcuts in listening. Shortcuts in thinking.&lt;/p&gt;

&lt;p&gt;Quick begets guesses. Guesses about meaning. Guesses about intent.&lt;/p&gt;

&lt;p&gt;And quick begets mistakes.&lt;/p&gt;

&lt;p&gt;This is a story about my mistake.&lt;/p&gt;

&lt;h2 id=&quot;the-report&quot;&gt;The report&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/michaelherold/benchmark-memory/issues/9&quot;&gt;original report&lt;/a&gt; shares a confusing situation from the &lt;a href=&quot;https://github.com/ruby-grape/grape&quot;&gt;Grape framework&lt;/a&gt;. To debug a memory leak, a Grape programmer &lt;a href=&quot;https://github.com/ruby-grape/grape/pull/2102#issuecomment-693823956&quot;&gt;wrote a benchmark&lt;/a&gt; using my gem. The benchmark is not easy to understand. It tests switching from an Array to a Set as the backend to store some inheritable properties for an API endpoint.&lt;/p&gt;

&lt;p&gt;Here is the benchmark they wrote, along with the output from running it, as written by &lt;a href=&quot;https://github.com/jylamont&quot;&gt;James Lamont&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;vg&quot;&gt;$LOAD_PATH&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;unshift&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;..&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;lib&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;grape&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;benchmark/memory&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;VotingApi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Grape&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STDOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;helpers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logger&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;votes&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostApi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Grape&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mount&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommentAPI&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Grape&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;API&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mount&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MockRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/votes&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;method: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;PostApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;CommentAPI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;setup_ar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:@setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;setup_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:@setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_set&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;memory&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;calls&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;using Array&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:@setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setup_ar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;calls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PostApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;using Set&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;VotingApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;instance_variable_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:@setup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;setup_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;calls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PostApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compare!&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;Calculating -------------------------------------
         using Array    21.261M memsize (   274.472k retained)
                       178.001k objects (     2.012k retained)
                        18.000  strings (     0.000  retained)
           using Set    21.261M memsize (     2.472k retained)
                       178.001k objects (    12.000  retained)
                        18.000  strings (     0.000  retained)

Comparison:
         using Array:   21261040 allocated
           using Set:   21261040 allocated - same
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;how-i-heard-it-and-my-mistake&quot;&gt;How I heard it and my mistake&lt;/h2&gt;

&lt;p&gt;When &lt;em&gt;quickly&lt;/em&gt; reading through this bug report and reading the benchmark, this certainly sounds like a bug.&lt;/p&gt;

&lt;p&gt;The output here shows that both approaches use the same ~21MiB of memory. This intuitively makes no sense since the set should prevent duplicates.&lt;/p&gt;

&lt;p&gt;The question that came from the bug report was, in essence, “should we run &lt;code class=&quot;&quot;&gt;GC.start&lt;/code&gt; after the task runs?” After a quick read, &lt;em&gt;of course,&lt;/em&gt; we should run the garbage collector! Otherwise, how would we know what items survive through a collection?&lt;/p&gt;

&lt;p&gt;And there — right there in that last sentence — is my mistake. I answered with a quick, “Yeah, that makes sense. Go ahead and send a change!”&lt;/p&gt;

&lt;p&gt;Surprise! My quick response was wrong; it doesn’t make sense. &lt;a href=&quot;https://github.com/SamSaffron/memory%5Fprofiler&quot;&gt;The memory profiler gem&lt;/a&gt; already does that. In fact, it runs multiple collections to encourage the garbage collector to fully clean unlinked memory. Without doing so, it wouldn’t be able to report the statistics that it does.&lt;/p&gt;

&lt;p&gt;So we now know my mistake, but what was the correct thing to do?&lt;/p&gt;

&lt;h2 id=&quot;reading-it-again&quot;&gt;Reading it again&lt;/h2&gt;

&lt;p&gt;To find the correct path, let’s summarize the situation.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Grape stores some configuration that appears to cause a memory leak.&lt;/li&gt;
  &lt;li&gt;James wrote a benchmark that showed the memory leak using my gem.&lt;/li&gt;
  &lt;li&gt;The output was confusing so dB filed an issue with my gem.&lt;/li&gt;
  &lt;li&gt;I read the benchmark and the explanation for the confusion.&lt;/li&gt;
  &lt;li&gt;The confusion centered around someone using the benchmark &lt;em&gt;with the comparison&lt;/em&gt; to show the presence of a memory leak.&lt;/li&gt;
  &lt;li&gt;Memory leaks have to do with &lt;em&gt;retaining&lt;/em&gt; objects, not necessarily with &lt;em&gt;allocating&lt;/em&gt; them, and the comparison is about allocations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After rereading and summarizing, I saw my mistake and decided upon two directions to approach the problem.&lt;/p&gt;

&lt;h2 id=&quot;a-better-solution&quot;&gt;A better solution?&lt;/h2&gt;

&lt;p&gt;While there isn’t a bug in the behavior of the gem, there &lt;em&gt;is&lt;/em&gt; a bug in the experience of using the gem. If I created the gem with the intent of using it to diagnose memory leaks, perhaps I would have nailed the experience of doing that with its DSL. However, I designed the gem to measure the space complexity of algorithms. That is a similar, yet distinct, job. Thus, I didn’t design the interface for that purpose and, when used for it, the gem confused the benchmarker.&lt;/p&gt;

&lt;p&gt;Making a quick decision here, I could take the path of redesigning the DSL to make it so you can easily do the job of tracking memory leaks. This needn’t be a complicated change. And it can be backward-compatible as well. This is a task that I know how to do and I’m confident in doing it.&lt;/p&gt;

&lt;p&gt;But is it what I should do first?&lt;/p&gt;

&lt;h2 id=&quot;the-right-choice&quot;&gt;The right choice&lt;/h2&gt;

&lt;p&gt;Even the summarized version of events is complex. There is more than one conversation that I was not a part of. There are multiple stakeholders in the problem. And it’s unclear if I missed or misunderstood anything in any of the conversations.&lt;/p&gt;

&lt;p&gt;Thus, the &lt;em&gt;right choice&lt;/em&gt; here is to &lt;em&gt;ask whether I have it correct&lt;/em&gt;. The only way to know whether I understand the problem correctly is to state how I understand it and check that for correctness. Only once I have done that and know that I understand the job to be done can I be sure that I am solving the right problem.&lt;/p&gt;

&lt;p&gt;So, I &lt;a href=&quot;https://github.com/michaelherold/benchmark-memory/issues/12#issuecomment-792369721&quot;&gt;did that&lt;/a&gt; and am currently waiting on an answer. The great thing now is that, even if my understanding isn’t complete, I now know that people use my gem for a purpose other than the one I designed it for: proving memory leaks. Since I know that is something that people need, I can spend some time thinking about how to make that process better. I can even &lt;em&gt;take my time&lt;/em&gt; instead of being &lt;em&gt;quick&lt;/em&gt; about it.&lt;/p&gt;

&lt;h2 id=&quot;what-to-do-next-time&quot;&gt;What to do next time&lt;/h2&gt;

&lt;p&gt;Maintaining my open source libraries is something that I do in my free time. I do it for enjoyment and to improve my skills. Free time, however, is a precious commodity at this point in my life. This makes me want to handle things quickly when I’m going through the issues on my repositories.&lt;/p&gt;

&lt;p&gt;Next time, I will think about this experience and the mistake that I made. Most bug reports are technical and not easy to quickly understand. I need a process that allows me to feel like I am quickly handling issues even when I’m taking my time. These articles are one way of doing that. Another would be to write a development diary about the task so that I can reference it later.&lt;/p&gt;

&lt;p&gt;I think the process that I outlined in this article is a reasonable approach for next time. When I feel myself doing something quickly on an open source project, I should write down my understanding of the problem and then check it with the person who filed the issue. Only once I have done that should I move forward with a change.&lt;/p&gt;

&lt;p&gt;Save time by spending it wisely. It’s a finite resource, after all.&lt;/p&gt;

&lt;p&gt;What kinds of mistakes have you made because you were acting quickly? Have you created a process to catch yourself when you’re doing this?&lt;/p&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/daniela-holzer-u_3rD02dmkw-unsplash.jpg" /><media:content medium="image" url="https://michaeljherold.com/images/daniela-holzer-u_3rD02dmkw-unsplash.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Using ox-hugo without duplicating content in the repository</title><link href="https://michaeljherold.com/articles/using-ox-hugo-without-duplication/" rel="alternate" type="text/html" title="Using ox-hugo without duplicating content in the repository" /><published>2021-03-01T09:00:00-06:00</published><updated>2021-03-01T09:00:00-06:00</updated><id>repo://articles.collection/_articles/using-ox-hugo-without-duplication.md</id><category term="emacs" /><category term="hugo" /><category term="org-mode" /><content type="html" xml:base="https://michaeljherold.com/articles/using-ox-hugo-without-duplication/">&lt;p&gt;My new blogging setup mashes up &lt;a href=&quot;https://orgmode.org/&quot;&gt;Org mode&lt;/a&gt; with &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; using the &lt;a href=&quot;https://ox-hugo.scripter.co/&quot;&gt;ox-hugo&lt;/a&gt; library. This is part of my attempt to bring more of my work product into the single format of Org mode. Hugo has rudimentary support for Org mode through &lt;a href=&quot;https://github.com/niklasfasching/go-org&quot;&gt;go-org&lt;/a&gt;, but it admittedly only tries to cover the &lt;a href=&quot;https://en.wikipedia.org/wiki/Pareto%5Fprinciple&quot;&gt;80/20&lt;/a&gt;  use case of Org mode. Since I intend to use Org mode for everything, the chances are high that I will end up with something incompatible, thus requiring me to work around the problem. I didn’t want to do that, so I stuck with the Hugo export library.&lt;/p&gt;

&lt;p&gt;Every &lt;a href=&quot;https://zzamboni.org/post/my-blogging-setup-with-emacs-org-mode-ox-hugo-hugo-gitlab-and-netlify/&quot;&gt;example&lt;/a&gt; that I &lt;a href=&quot;https://www.shanesveller.com/blog/2018/02/13/blogging-with-org-mode-and-ox-hugo/&quot;&gt;saw&lt;/a&gt; of a &lt;a href=&quot;https://willschenk.com/articles/2019/using%5Forg%5Fmode%5Fin%5Fhugo/&quot;&gt;site&lt;/a&gt; that &lt;a href=&quot;https://github.com/jethrokuan/braindump&quot;&gt;uses&lt;/a&gt; &lt;code class=&quot;&quot;&gt;ox-hugo&lt;/code&gt; ends up duplicating the Org mode documents are Markdown in the repository. This felt noisy and redundant to me so I wanted to find a way to have Netlify render the Org document to its constituent Hugo pages as part of the deploy process. This article documents the process that I now use and shares the rendering script that I wrote to process the file as part of a deployment.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;project&quot;&gt;Project&lt;/h2&gt;

&lt;p&gt;The layout of my project looks like the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;michaeljherold.com/
├── blog.org
├── config.toml
├── Makefile
├── netlify.toml
├── script/
│   └── publish
├── static/
└── themes/
    └── v1/
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;&quot;&gt;blog.org&lt;/code&gt; file holds my Org mode blog pages in “one post per Org subtree” mode. This allows me to have the entire site live in a single file, which I think is pretty cool. As time goes on and I write more, perhaps I will choose to split it into multiple files but for now, I like how this works.&lt;/p&gt;

&lt;p&gt;For Hugo, we have &lt;code class=&quot;&quot;&gt;config.toml&lt;/code&gt;, &lt;code class=&quot;&quot;&gt;static/&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;themes/&lt;/code&gt;. Nothing is exciting in any of those files since I render the site with Hugo-flavored Markdown.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;&quot;&gt;netlify.toml&lt;/code&gt; file contains my Netlify-specific configuration. This includes some redirects and commands for making production deployments, deploy previews, and branch-based deployments. Again, there’s not a lot that is interesting here.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;&quot;&gt;Makefile&lt;/code&gt; is a simple affair that we will discuss in two sections.&lt;/p&gt;

&lt;p&gt;The meat of this article lives in the &lt;code class=&quot;&quot;&gt;script/publish&lt;/code&gt; file, which we will talk about next.&lt;/p&gt;

&lt;p&gt;Hopefully, other than the complete lack of a &lt;code class=&quot;&quot;&gt;content/&lt;/code&gt; folder, none of this is surprising for you if you’re familiar with Hugo. I’m striving to have as little magic as possible as part of my publication routine so that there are fewer things for me to tweak instead of sitting down and writing.&lt;/p&gt;

&lt;h2 id=&quot;publish-script&quot;&gt;Publish script&lt;/h2&gt;

&lt;p&gt;The solution to the problem of duplicating content in your Git repository lives in the &lt;code class=&quot;&quot;&gt;script/publish&lt;/code&gt; file. This is an Emacs Lisp-in-shell script that I run during my Netlify deployment to generate all of the Markdown files for the pages on the site.&lt;/p&gt;

&lt;p&gt;I will break the script down section-by-section here and explain what I’m doing in each section.&lt;/p&gt;

&lt;h3 id=&quot;run-me-a-shell-script-please&quot;&gt;Run me a shell script, please&lt;/h3&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;
:&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# -*- mode: emacs-lisp; lexical-binding: t -*-&lt;/span&gt;
:&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; emacs &lt;span class=&quot;nt&quot;&gt;--no-site-file&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--script&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;__EXITCODE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$?&lt;/span&gt;
:&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;__EXITCODE&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I borrowed the setup for this section from Henrik Lissner’s &lt;a href=&quot;https://github.com/hlissner/doom-emacs/blob/88e18fc7c1d36bb2fe842d43ece0903556d2c5ca/bin/doom&quot;&gt;fantastic Doom Emacs scripts&lt;/a&gt; because I thought it was so ingenious. First, we set the shebang that makes the script run via a shell command. Then comes the “wow!” moment. To explain, first a little background.&lt;/p&gt;

&lt;p&gt;Historically, POSIX shells didn’t have the concepts of &lt;code class=&quot;&quot;&gt;true&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;false&lt;/code&gt;&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. They used &lt;code class=&quot;&quot;&gt;:&lt;/code&gt; to mean something equivalent to &lt;code class=&quot;&quot;&gt;true&lt;/code&gt;. So we could rewrite the &lt;code class=&quot;&quot;&gt;:;&lt;/code&gt; on each of these lines to &lt;code class=&quot;&quot;&gt;true;&lt;/code&gt;, which is a no-op. You might be asking, “why would you do that?” The reason requires a little bit of Emacs Lisp knowledge: the &lt;code class=&quot;&quot;&gt;;&lt;/code&gt; character denotes a comment and the &lt;code class=&quot;&quot;&gt;:&lt;/code&gt; denotes a keyword. In this case, it’s the keyword &lt;code class=&quot;&quot;&gt;:&lt;/code&gt;, which in Emacs is effectively a no-op since it’s only a reference to the keyword.&lt;/p&gt;

&lt;p&gt;Combine all of this together and you get the following effect: each of these lines runs in a POSIX shell like Bash but does &lt;em&gt;not&lt;/em&gt; run anything in Emacs. How cool is that?&lt;/p&gt;

&lt;p&gt;So what do these lines do?&lt;/p&gt;

&lt;p&gt;The first line sets the POSIX “consequence of shell errors” to be that any command that fails in the script exits the script with the same exit code as the failing command. It also uses &lt;a href=&quot;https://www.gnu.org/software/emacs/manual/html%5Fnode/emacs/Specifying-File-Variables.html&quot;&gt;Emacs file variables&lt;/a&gt; to tell Emacs to read the file as if it were an Emacs Lisp file and use lexical bindings for all its variables.&lt;/p&gt;

&lt;p&gt;The second line runs &lt;span class=&quot;underline&quot;&gt;this file&lt;/span&gt; (via &lt;code class=&quot;&quot;&gt;--script &quot;$0&quot;&lt;/code&gt;) in Emacs without using a blank profile (via &lt;code class=&quot;&quot;&gt;--no-site-file&lt;/code&gt;), passing along any arguments you give to the script into Emacs (via &lt;code class=&quot;&quot;&gt;-- &quot;$@&quot;&lt;/code&gt;). If Emacs exits with a non-zero exit code, it saves that exit code in the &lt;code class=&quot;&quot;&gt;__EXITCODE&lt;/code&gt; variable&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The third line then exits the script with the exit code from the last line or, if we didn’t set it, a default of zero, or success. That means the shell will never execute the rest of the script, so we’re free to do whatever we want there! Again, Henrik’s pattern here is impressive.&lt;/p&gt;

&lt;p&gt;Okay, with the entirety of the shell script covered, let’s move on to some Emacs Lisp!&lt;/p&gt;

&lt;h3 id=&quot;just-kidding-actually-run-some-emacs-code&quot;&gt;Just kidding, actually run some Emacs code&lt;/h3&gt;

&lt;p&gt;The first section of Emacs Lisp is boring because it just sets up some boilerplate. But it’s necessary, so here we go:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot; data-lang=&quot;elisp&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defvar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;bootstrap-version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defvar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;straight-base-dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defvar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;straight-fix-org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defvar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;straight-vc-git-default-clone-depth&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;defvar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;publish--straight-repos-dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;setq&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;gc-cons-threshold&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;83886080&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;; 80MiB&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;straight-base-dir&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expand-file-name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;../..&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;load-file-name&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;buffer-file-name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;straight-fix-org&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;straight-vc-git-default-clone-depth&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;publish--straight-repos-dir&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expand-file-name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;straight/repos/&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;straight-base-dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The first paragraph defines some variables that we later use to hush some warnings in the byte compiler. The first four are for setting up &lt;a href=&quot;https://github.com/raxod502/straight.el&quot;&gt;the Straight package manager&lt;/a&gt;, which I’ll talk about shortly, and the last one is a variable for this script.&lt;/p&gt;

&lt;p&gt;The second paragraph sets values for several variables, as so:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;gc-cons-threshold&lt;/code&gt; controls the threshold that Emacs must hit before it allows the garbage collector to run. By default, Emacs uses a very low threshold of 800KB. This causes many garbage collection cycles, which slows down the process of running the script. You can see in the comment that I set it to 80MiB, which is a good deal larger than the default but still conservative enough that I don’t worry that Netlify will run out of memory in the build container.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;straight-base-dir&lt;/code&gt; sets the location to which Straight will install itself, relative to the current file. This is the project’s root directory, confusingly. Straight then adds a &lt;code class=&quot;&quot;&gt;straight/&lt;/code&gt; folder to that directory for its files.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;straight-fix-org&lt;/code&gt; sets a Boolean variable for Straight to run a “fix” on the Org mode package during download. Org mode doesn’t store its &lt;code class=&quot;&quot;&gt;org-version&lt;/code&gt; variable in the source repository and instead waits till they release a package to set it. Since Straight pulls from a Git repository, we must set this variable or Org will not build correctly&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;straight-vc-git-default-clone-depth&lt;/code&gt; sets the depth to which Straight will check out the Git repository for each package when it downloads them. Since we’re only using this to build our Markdown files and not use it daily, we can safely limit this to a single commit to speed up the build process on Netlify.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;publish--straight-repos-dir&lt;/code&gt; contains the directory in which we want to store the repositories for the packages we download. We set this to be &lt;code class=&quot;&quot;&gt;straight/repos/&lt;/code&gt; in the root of the project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now that we have all of that setup, we can run the commands to bootstrap and install Straight. I decided to go with Straight here because it’s declarative and flexible. Right now, I only need Org mode and &lt;code class=&quot;&quot;&gt;ox-hugo&lt;/code&gt;, but using Straight allows me to quickly and easily add a new package or a monkey-patch to either of the packages that I currently use. It also uses a declarative syntax which helps to make sure we do the correct thing every time.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot; data-lang=&quot;elisp&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bootstrap-file&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expand-file-name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;straight/repos/straight.el/bootstrap.el&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;straight-base-dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bootstrap-version&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;file-exists-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;bootstrap-file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;with-current-buffer&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url-retrieve-synchronously&lt;/span&gt;
         &lt;span class=&quot;s&quot;&gt;&quot;https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el&quot;&lt;/span&gt;
         &lt;span class=&quot;ss&quot;&gt;&apos;silent&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;inhibit-cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;goto-char&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;point-max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;eval-print-last-sexp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;bootstrap-file&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;&apos;nomessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This bootstrap block comes almost verbatim from the Straight documentation. We install the bootstrap file in the Straight directory that we configured above and load it without displaying any messages. Please note that this, at the end of the day, downloads and executes a script from the Internet. Many people use Straight so I’m confident in my use case to do this, but consider it if you’re doing something where you are hosting sensitive data. I am not, so I am comfortable doing this.&lt;/p&gt;

&lt;p&gt;Once we have downloaded and bootstrapped Straight, we can install the packages we need:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot; data-lang=&quot;elisp&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;straight-use-package&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;org-mode&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:type&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;git&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;:host&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;github&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;:repo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;emacs-straight/org-mode&quot;&lt;/span&gt;
            &lt;span class=&quot;ss&quot;&gt;:files&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*.el&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;lisp/*.el&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;contrib/lisp/*.el&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;straight-use-package&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ox-hugo&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:type&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;git&lt;/span&gt;
           &lt;span class=&quot;ss&quot;&gt;:host&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;github&lt;/span&gt;
           &lt;span class=&quot;ss&quot;&gt;:repo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;kaushalmodi/ox-hugo&quot;&lt;/span&gt;
           &lt;span class=&quot;ss&quot;&gt;:nonrecursive&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As previously stated, we are only going to use two packages, Org mode and &lt;code class=&quot;&quot;&gt;ox-hugo&lt;/code&gt;. The &lt;code class=&quot;&quot;&gt;:files&lt;/code&gt; declaration on Org mode tells Straight to only create symbolic links for those particular files. The &lt;code class=&quot;&quot;&gt;:nonrecursive&lt;/code&gt; declaration on &lt;code class=&quot;&quot;&gt;ox-hugo&lt;/code&gt; tells Straight not to pull the submodules in the repository, which helps the download complete faster.&lt;/p&gt;

&lt;p&gt;Now that we have our dependencies, we can run the actual script to publish the Org subtrees as Markdown files:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-elisp&quot; data-lang=&quot;elisp&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;with-current-buffer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;find-file-noselect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;expand-file-name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;blog.org&quot;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;org-next-visible-heading&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;equal&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;org-entry-get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;CUSTOM_ID&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;toc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;inhibit-message&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;org-cut-subtree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Publishing...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;org-hugo-export-wim-to-md&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This block opens the &lt;code class=&quot;&quot;&gt;blog.org&lt;/code&gt; file at the root of the project. It removes my table of contents that I keep in the file because it prevents &lt;code class=&quot;&quot;&gt;ox-hugo&lt;/code&gt; from rendering the file. To do this, it skips to the first heading in the file, checks to make sure it has the &lt;code class=&quot;&quot;&gt;CUSTOM_ID&lt;/code&gt; property that I set on it called &lt;code class=&quot;&quot;&gt;toc&lt;/code&gt; for “table of contents,” and, if so, cuts — or removes — the subtree from the file. Then, we echo “Publishing …” to standard out and use the &lt;code class=&quot;&quot;&gt;org-hugo-export-wim-to-md&lt;/code&gt; function to export the whole file to its constituent Markdown files.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Et voilà!&lt;/em&gt; We now have all of our Markdown files in the &lt;code class=&quot;&quot;&gt;content/&lt;/code&gt; directoy, just as Hugo expects.&lt;/p&gt;

&lt;h2 id=&quot;makefile&quot;&gt;Makefile&lt;/h2&gt;

&lt;p&gt;To coordinate the use of our publish script, I find it useful to create &lt;code class=&quot;&quot;&gt;Makefile&lt;/code&gt; so that we can write a simple command in our &lt;code class=&quot;&quot;&gt;netlify.toml&lt;/code&gt; instead of a complex list of commands. Here is the content of the &lt;code class=&quot;&quot;&gt;Makefile&lt;/code&gt; we need:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-makefile&quot; data-lang=&quot;makefile&quot;&gt;&lt;span class=&quot;nl&quot;&gt;.PHONY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;md production preview clean&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;md&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	./script/publish

&lt;span class=&quot;nl&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;md&lt;/span&gt;
	hugo

&lt;span class=&quot;nl&quot;&gt;preview&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;md&lt;/span&gt;
	hugo &lt;span class=&quot;nt&quot;&gt;-FD&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; ./content ./public ./resources
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We have four &lt;a href=&quot;https://www.gnu.org/software/make/manual/html%5Fnode/Phony-Targets.html&quot;&gt;phony targets&lt;/a&gt; that handle different pieces of the site. The &lt;code class=&quot;&quot;&gt;md&lt;/code&gt; target runs our publish script to create our Markdown files. We list it as a dependency for the &lt;code class=&quot;&quot;&gt;production&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;preview&lt;/code&gt; targets, which generate the published and published, future, and draft pages of our Markdown, respectively. And to help clean it all up, we have the &lt;code class=&quot;&quot;&gt;clean&lt;/code&gt; target that removes everything we built.&lt;/p&gt;

&lt;p&gt;With these targets, we can then fill out the relevant portions of our Netlify configuration.&lt;/p&gt;

&lt;h2 id=&quot;netlify-dot-toml&quot;&gt;&lt;code class=&quot;&quot;&gt;netlify.toml&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;For this article, we only need a simple, three-section Netlify configuration. You can see it as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;nn&quot;&gt;[context.production]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;make production&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[context.deploy-preview]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;make preview&quot;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[context.branch-deploy]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;make preview&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;For production deployments, we only want to build published pages, so we use &lt;code class=&quot;&quot;&gt;make production&lt;/code&gt; to generate the site. For previews, it’s handy to see future and draft pages as well, so we use &lt;code class=&quot;&quot;&gt;make preview&lt;/code&gt; for those environments.&lt;/p&gt;

&lt;p&gt;And that’s it! That’s all we need.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Because I am still a novice-to-journeyman Emacs Lisp programmer, it took me a while to figure out how to do the programmatic render of my Org mode file into Markdown. But, once I figured it out, the solution ended up being very simple and, I believe, future-proof.&lt;/p&gt;

&lt;p&gt;This allows me to keep the size of my site repository down, which pays dividends going forward. I still have to worry about the size of assets in the repository, so that might be something I will look into in the future. But at least I don’t have two copies of the page contents that could only lead to confusion. Even better is the fact that it only uses software that is readily available in Netlify’s build images.&lt;/p&gt;

&lt;p&gt;Did I make any mistakes? What do you think of Org mode? Have you ever written a website in it?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;At least according to &lt;a href=&quot;https://stackoverflow.com/a/3224910/37436&quot;&gt;this StackOverflow answer&lt;/a&gt;. I haven’t been able to track down a primary source for this quirk. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;As I write this, I think I found an unnecessary feature here. Given that I just explained that &lt;code class=&quot;&quot;&gt;set -e&lt;/code&gt; will cause the command to exit with an error code, I think this is not needed and that the next line can just be &lt;code class=&quot;&quot;&gt;exit 0&lt;/code&gt;, but I will have to try it out to double-check. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;There’s nothing like writing an article for you to catch errors in your scripts. I looked this variable up and the Straight maintainer &lt;a href=&quot;https://github.com/raxod502/straight.el/commit/cd42db63bcbadd0ee42e9ca39c06a7503e9f71b4&quot;&gt;marked it as obsolete&lt;/a&gt; on 2020-10-23. I don’t think it’s necessary any more, but again, I have to double-check. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/org-mode-unicorn.png" /><media:content medium="image" url="https://michaeljherold.com/images/org-mode-unicorn.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Provisioning a Valheim server with Terraform</title><link href="https://michaeljherold.com/articles/provisioning-a-valheim-server-with-terraform/" rel="alternate" type="text/html" title="Provisioning a Valheim server with Terraform" /><published>2021-02-22T09:00:00-06:00</published><updated>2021-02-22T09:00:00-06:00</updated><id>repo://articles.collection/_articles/provisioning-a-valheim-server-with-terraform.md</id><category term="devops" /><content type="html" xml:base="https://michaeljherold.com/articles/provisioning-a-valheim-server-with-terraform/">&lt;p&gt;My friends and I decided we were going to give &lt;a href=&quot;https://www.valheimgame.com/&quot;&gt;Valheim&lt;/a&gt;, an early-access survival game, a try. Since many of us are parents now and we’re in a socially distancing world, colocating for an evening of fun was out of the question. I decided to use the event as an excuse to practice my DevOps skills.&lt;/p&gt;

&lt;p&gt;I use Terraform at work for a variety of things, but I don’t write enough of it to be completely comfortable. As such, I decided to use Terraform to provision a dedicated server for us. This article documents how I went about it using DigitalOcean and Terraform.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;create-an-ssh-key&quot;&gt;Create an SSH key&lt;/h2&gt;

&lt;p&gt;When provisioning cloud servers, SSH is your best friend. When you need to perform any maintenance on the server, you will need SSH to connect to it. Since this is a single-purpose machine that we’ll be provisioning, let’s create a single-purpose SSH key to go with it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;ssh-keygen &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; ed25519 &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; ~/.ssh/id_valheim &lt;span class=&quot;nt&quot;&gt;-P&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This command generates &lt;a href=&quot;https://en.wikipedia.org/wiki/EdDSA&quot;&gt;an elliptic curve SSH key&lt;/a&gt; that we will use to connect to the server. It uses an empty passphrase since its only purpose is to connect to this throwaway server. We will use the DigitalOcean API to upload this key to them for use with our server.&lt;/p&gt;

&lt;h2 id=&quot;configure-the-terraform-provider&quot;&gt;Configure the Terraform provider&lt;/h2&gt;

&lt;p&gt;First, you will need to &lt;a href=&quot;https://cloud.digitalocean.com/account/api/tokens&quot;&gt;create an API token&lt;/a&gt; on DigitalOcean. You’ll want to keep this in a safe place because once you generate it you will not be able to see the token again. Once you have it, you’ll want to set it as an environment variable. I use &lt;a href=&quot;https://direnv.net/&quot;&gt;direnv&lt;/a&gt; for this purpose and, as such, put the environment variable in a &lt;code class=&quot;&quot;&gt;.envrc&lt;/code&gt; file in my project. That looks like the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DIGITALOCEAN_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mytokenthatigotfromdigitalocean&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once you have that environment variable set, you can use &lt;a href=&quot;https://registry.terraform.io/providers/digitalocean/digitalocean/latest&quot;&gt;the DigitalOcean Terraform provider&lt;/a&gt;. To configure Terraform to use that provider, you will need to specify it as a required provider&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;terraform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;required_providers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;digitalocean&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean/digitalocean&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 2.5&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And then declare it as a provider:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;provider&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# export DIGITALOCEAN_TOKEN&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I like to add that comment in the provider block to state where its credentials come from; it’s not necessary. For those of you following at home, you can place all of the configuration in a single &lt;code class=&quot;&quot;&gt;.tf&lt;/code&gt; file or design your own scheme for organizing your blocks.&lt;/p&gt;

&lt;p&gt;To install the provider, initialize Terraform with:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;set-up-the-ssh-key&quot;&gt;Set up the SSH key&lt;/h2&gt;

&lt;p&gt;The first resource we need to create is the SSH key. This is because DigitalOcean creates the server from an image that uses &lt;a href=&quot;https://cloud-init.io/&quot;&gt;cloud-init&lt;/a&gt; to set default SSH keys for the root user. Without it, the other commands won’t work.&lt;/p&gt;

&lt;p&gt;To create the SSH key resource, we specify it like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean_ssh_key&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;public_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.ssh/id_valheim.pub&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This takes the file on our local machine located at &lt;code class=&quot;&quot;&gt;~/.ssh/id_valheim.pub&lt;/code&gt; and uploads it as an SSH key to DigitalOcean. If you chose to name your key something different in the previous step, use that filename here. The name shows up in DigitalOcean’s security settings to help you identify it later. Pick something memorable.&lt;/p&gt;

&lt;h2 id=&quot;create-the-server&quot;&gt;Create the server&lt;/h2&gt;

&lt;p&gt;Once we have the provider set up, we can write a recipe to provision the server. First, we have to set up the SSH key that we generated in the last step&lt;/p&gt;

&lt;p&gt;For our purposes, a bit of anecdotal research showed that others successfully run a four-person server on a 2 CPU / 4 GiB server. This is a standard server size from DigitalOcean called &lt;code class=&quot;&quot;&gt;s-2vcpu-4b&lt;/code&gt;. With that knowledge, we can write the following resource:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean_droplet&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ubuntu-20-04-x64&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;region&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;nyc1&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;monitoring&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;s-2vcpu-4gb&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;game&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;ssh_keys&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digitalocean_ssh_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;valheim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fingerprint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The image here is the latest Ubuntu LTS at the time of this writing. The name helps to identify your droplet in DigitalOcean’s cloud console. In my case, three of the four of us were playing from Ohio so I picked the New York City 1 data center (or region). Setting the monitoring flag to true enables DigitalOcean’s new agent-based resource monitoring that is more fine-grained than the default. I prefer it to their older metrics. The tags are a way to keep your droplets organized in the cloud console.&lt;/p&gt;

&lt;p&gt;The most important field here is the &lt;code class=&quot;&quot;&gt;ssh_keys&lt;/code&gt; field. This takes the SSH key that we uploaded in the previous resource and links it to the droplet so that we can log into the droplet via SSH. Without it, DigitalOcean will email you a root password, but that is incompatible with the rest of the Terraform configuration.&lt;/p&gt;

&lt;h2 id=&quot;configure-the-server&quot;&gt;Configure the server&lt;/h2&gt;

&lt;p&gt;I very much label this section as &lt;em&gt;caveat lector&lt;/em&gt;, reader beware. Because I am treating this server as a “create it and delete it later” server, I am using a Terraform provisioner to provision the box. As stated &lt;a href=&quot;https://www.terraform.io/docs/language/resources/provisioners/syntax.html&quot;&gt;in the documentation&lt;/a&gt;, Hashicorp only intends you to use these provisioners as a last resort. If this were for a mission-critical system, I would probably use Packer to build an image and deploy it instead. But that’s a little too heavy for my current task, so here we are.&lt;/p&gt;

&lt;p&gt;There are three steps that we will perform. First, we’ll create some necessary directories and attempt to temporarily disable the automated updates for Ubuntu Server. Next, we’ll upload some configuration files to the server. Lastly, we’ll install Docker and start the Valheim service.&lt;/p&gt;

&lt;h3 id=&quot;create-necessary-directories&quot;&gt;Create necessary directories&lt;/h3&gt;

&lt;p&gt;Running simple scripts in the Terraform provisioner is simple. The &lt;code class=&quot;&quot;&gt;remote-exec&lt;/code&gt; provisioner generates a script file and runs it with the shell. We’ll use this functionality for the first step:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean_droplet&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;provisioner&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;remote-exec&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;sed -i &apos;s/1/0/g&apos; /etc/apt/apt.conf.d/20auto-upgrades&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;mkdir -p /opt/valheim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;mkdir -p /etc/sysconfig&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ssh&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;root&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipv4_address&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;private_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.ssh/id_valheim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The first line is a hack that attempts to beat a race condition between the provisioning scripts and the Ubuntu automatic updates service. If the automatic update occurs just before any of our calls to Apt, we won’t be able to obtain the lock on the repositories and package caches and our scripts will fail. So we use &lt;code class=&quot;&quot;&gt;sed&lt;/code&gt; to disable the automatic upgrades service, then create two directories.&lt;/p&gt;

&lt;p&gt;The first directory, &lt;code class=&quot;&quot;&gt;/opt/valheim&lt;/code&gt;, will contain our game data. The second contains an environment file for the Systemd unit we will use to manage the server process.&lt;/p&gt;

&lt;p&gt;The connection block configures the SSH connection that we use to run the provisioner. This will be in each of the provisioner blocks that follow, as I don’t think there is a way to share connection information between provisioner blocks without something like &lt;a href=&quot;https://terragrunt.gruntwork.io/&quot;&gt;Terragrunt&lt;/a&gt;. The block says “create an SSH connect using the root user at the IPv4 address that we just created and use the &lt;code class=&quot;&quot;&gt;id_valheim&lt;/code&gt; key.”&lt;/p&gt;

&lt;p&gt;Now that we have the basics, we can upload a Systemd service unit and its accompanying environment file. This takes two blocks, one for each file.&lt;/p&gt;

&lt;h3 id=&quot;upload-systemd-unit&quot;&gt;Upload Systemd unit&lt;/h3&gt;

&lt;p&gt;To upload the Systemd unit that manages the Valheim server, we can add this provisioner to the droplet resource:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean_droplet&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;provisioner&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;file&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;conf/valheim-server.service&quot;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/etc/systemd/system/valheim-server.service&quot;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ssh&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;root&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipv4_address&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;private_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.ssh/id_valheim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The file provisioner takes a local file (or content string) and uploads it as a file to the resource. In this case, it takes the Systemd unit below, which we store in &lt;code class=&quot;&quot;&gt;conf/valheim-server.service&lt;/code&gt; at the root of our Terraform project.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-systemd&quot; data-lang=&quot;systemd&quot;&gt;&lt;span class=&quot;k&quot;&gt;[Unit]&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;Valheim Server
&lt;span class=&quot;nt&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;docker.service
&lt;span class=&quot;nt&quot;&gt;Requires&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;docker.service

&lt;span class=&quot;k&quot;&gt;[Service]&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;TimeoutStartSec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;0
&lt;span class=&quot;nt&quot;&gt;EnvironmentFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;-/etc/sysconfig/valheim-server
&lt;span class=&quot;nt&quot;&gt;ExecStartPre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;-/usr/bin/docker stop %n
&lt;span class=&quot;nt&quot;&gt;ExecStartPre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;-/usr/bin/docker rm %n
&lt;span class=&quot;nt&quot;&gt;ExecStartPre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;/usr/bin/docker pull lloesche/valheim-server
&lt;span class=&quot;nt&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;/usr/bin/docker run &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          --name %n &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          --rm &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -v /opt/valheim:/config:Z &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -p 2456-2458:2456-2458/udp &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -e SERVER_NAME &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -e SERVER_PORT &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -e WORLD_NAME &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -e SERVER_PASS &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          -e SERVER_PUBLIC &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          --dns $DNS_1 &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          --dns $DNS_2 &lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;          lloesche/valheim-server
&lt;span class=&quot;nt&quot;&gt;ExecStop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;/usr/bin/docker stop %n
&lt;span class=&quot;nt&quot;&gt;Restart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;always
&lt;span class=&quot;nt&quot;&gt;RestartSec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;10s

&lt;span class=&quot;k&quot;&gt;[Install]&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;WantedBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is the default Systemd unit for &lt;a href=&quot;https://github.com/lloesche/valheim-server-docker&quot;&gt;the Docker container&lt;/a&gt; we use to run the server. It reads environment variables from an environment file that we’ll upload next and starts the Docker container with them. Running through line-by-line:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-v /opt/valheim:/config:Z&lt;/code&gt; mounts our game data directory as a volume as an unshared bind-mount within the container&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-p 2456-2458:2456-2458/udp&lt;/code&gt; forwards the default game ports from the host to the container&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-e SERVER_NAME&lt;/code&gt; will configure the name of the server within Valheim&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-e SERVER_PORT&lt;/code&gt; sets the first port that Valheim listens on&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-e WORLD_NAME&lt;/code&gt; sets the name of the world file for your identification&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-e SERVER_PASS&lt;/code&gt; sets the password to enter the server, which must be 5+ characters according to the manual&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;-e SERVER_PUBLIC&lt;/code&gt; is a boolean flag for whether the server should show up in the public server list&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;&quot;&gt;-e --dns&lt;/code&gt; lines set the DNS servers that the container should use&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;lloesche/valheim-server&lt;/code&gt; is the name of the container image to run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rest of the unit file attempts to ensure the container is the only copy running and tells the unit to load the environment file that we upload next.&lt;/p&gt;

&lt;h3 id=&quot;upload-environment-file&quot;&gt;Upload environment file&lt;/h3&gt;

&lt;p&gt;The second file provisioner uploads the environment file that configures the Systemd unit:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean_droplet&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;provisioner&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;file&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;templatefile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;conf/valheim-server.conf.tpl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;server_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;server_pass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;server_pass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;world_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;world_name&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/etc/sysconfig/valheim-server&quot;&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ssh&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;root&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipv4_address&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;private_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.ssh/id_valheim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This reads a configuration file template (shown below) from the &lt;code class=&quot;&quot;&gt;conf/valheim-server.conf.tpl&lt;/code&gt; file at the root of your Terraform project and inserts some Terraform variables. To enable the variables, we need three variable blocks in our Terraform file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;variable&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;server_name&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;variable&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;server_pass&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;variable&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;world_name&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can set these as environment variables with the pattern &lt;code class=&quot;&quot;&gt;TF_VAR_&amp;lt;variable&amp;gt;&lt;/code&gt;, which I recommend doing in your &lt;code class=&quot;&quot;&gt;.envrc&lt;/code&gt; file if you use &lt;code class=&quot;&quot;&gt;direnv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The template itself looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-conf&quot; data-lang=&quot;conf&quot;&gt;&lt;span class=&quot;n&quot;&gt;DNS_1&lt;/span&gt;=&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;DNS_2&lt;/span&gt;=&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;8&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;.&lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;SERVER_NAME&lt;/span&gt;=&lt;span class=&quot;s2&quot;&gt;&quot;${server_name}&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;SERVER_PASS&lt;/span&gt;=&lt;span class=&quot;s2&quot;&gt;&quot;${server_pass}&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;SERVER_PORT&lt;/span&gt;=&lt;span class=&quot;m&quot;&gt;2456&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;SERVER_PUBLIC&lt;/span&gt;=&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;WORLD_NAME&lt;/span&gt;=&lt;span class=&quot;s2&quot;&gt;&quot;${world_name}&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;These are the environment variables that you see referenced above in the System unit.&lt;/p&gt;

&lt;p&gt;Now that we have all of that on the server, it’s time to install Docker and start the service!&lt;/p&gt;

&lt;h3 id=&quot;install-docker-and-start-valheim&quot;&gt;Install Docker and start Valheim&lt;/h3&gt;

&lt;p&gt;This section is the longest part of provisioning the server because it does several things. First, Docker is not available from the default Ubuntu repositories so we have to add its repository and configuration, which takes a few commands. Then, we need to install Docker. Finally, we enable the Valheim service and re-enable the automatic upgrade system.&lt;/p&gt;

&lt;p&gt;We do this with the following provisioner block:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-terraform&quot; data-lang=&quot;terraform&quot;&gt;&lt;span class=&quot;k&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;digitalocean_droplet&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valheim&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;provisioner&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;remote-exec&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;apt-get update &amp;amp;&amp;amp; DEBIAN_FRONTEND=noninteractive apt-get install -yq apt-transport-https ca-certificates curl gnupg-agent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;echo &apos;deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable&apos; &amp;gt; /etc/apt/sources.list.d/docker.list&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;apt-get update &amp;amp;&amp;amp; DEBIAN_FRONTEND=noninteractive apt-get install -yq docker-ce docker-ce-cli containerd.io&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;systemctl daemon-reload&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;systemctl enable --now valheim-server.service&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;sed -i &apos;s/0/1/g&apos; /etc/apt/apt.conf.d/20auto-upgrades&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;connection&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ssh&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;root&quot;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ipv4_address&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;private_key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/.ssh/id_valheim&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dissecting this line-by-line, we see the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;apt-get update [...] gnupg-agent&lt;/code&gt; installs the prerequisites for adding the Docker repository in a headless way so we aren’t prompted for input&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;curl [...] | apt-key add -&lt;/code&gt; adds the Docker GPG key to the Apt keychain&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;echo [...] &amp;gt; /etc/apt/sources.list.d/docker.list&lt;/code&gt; adds the Docker repository to Apt&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;apt-get update [...] containerd.io&lt;/code&gt; installs Docker, again in a headless way&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;systemctl daemon-reload&lt;/code&gt; ensures that Systemd has a reference to our new Docker service&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;systemctl enable --now valheim-server.service&lt;/code&gt; enables the Valheim server, which makes it start up upon reboot, and starts the server now&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;&quot;&gt;sed [...]&lt;/code&gt; re-enables Ubuntu’s automatic updates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once this provisioner finishes, Terraform finalizes its actions. We’ll have a Valheim server running in approximately four minutes! To connect, put in the IP address of your droplet with the port &lt;code class=&quot;&quot;&gt;:2457&lt;/code&gt;, confusingly.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;One of the ways that I improved my skills as both a developer and systems operator was through running private services for my friends and me, like video game servers. This article shared a pared-down version of the DevOps setup I used for setting up a dedicated Valheim server. Using a single local tool, Terraform, we were able to go from nothing to a fully functioning server running on DigitalOcean.&lt;/p&gt;

&lt;p&gt;Granted, this simple setup skips a larger step in provisioning and uses a hack to try to beat the race against Ubuntu’s auto-updates. Converting those steps to a repeatable process was beyond the scope of what I wanted to do, but there are many ways to do so with a variety of different tools. As an exercise, see what you can come up with!&lt;/p&gt;

&lt;p&gt;If you’re interested in seeing my full setup using CloudFlare for DNS and DigitalOcean firewalls, you can find it &lt;a href=&quot;https://github.com/michaelherold/homelab&quot;&gt;on GitHub&lt;/a&gt;. Also, you can &lt;a href=&quot;https://m.do.co/c/2819a9be7e31&quot;&gt;sign up for DigitalOcean&lt;/a&gt; and receive a $100 credit for 60 days so you can test this for free&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Do you ever use your leisure time for things like this? Have you ever used a cloud provider to host a game server?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;As of this writing, I am using Terraform v0.14. It and its prior version, v0.13, &lt;a href=&quot;https://www.terraform.io/upgrade-guides/0-13.html&quot;&gt;require different&lt;/a&gt; &lt;a href=&quot;https://www.terraform.io/upgrade-guides/0-14.html&quot;&gt;provider setup&lt;/a&gt; than prior versions. To check which version you’re using, run &lt;code class=&quot;&quot;&gt;terraform -version&lt;/code&gt;. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Full disclosure: that link is a referral link. If you sign up and spend $25, I will receive a $25 credit. If you’d prefer not to participate in that referral, &lt;a href=&quot;https://cloud.digitalocean.com/registrations/new&quot;&gt;here is a link without referral&lt;/a&gt;. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/logo-valheim.png" /><media:content medium="image" url="https://michaeljherold.com/images/logo-valheim.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Duplicate cookies in Ruby on Rails</title><link href="https://michaeljherold.com/articles/duplicate-cookies-in-ruby-on-rails/" rel="alternate" type="text/html" title="Duplicate cookies in Ruby on Rails" /><published>2021-02-15T09:00:00-06:00</published><updated>2021-02-15T09:00:00-06:00</updated><id>repo://articles.collection/_articles/duplicate-cookies-in-ruby-on-rails.md</id><category term="ruby-on-rails" /><content type="html" xml:base="https://michaeljherold.com/articles/duplicate-cookies-in-ruby-on-rails/">&lt;p&gt;Ruby on Rails has an &lt;a href=&quot;https://guides.rubyonrails.org/action%5Fcontroller%5Foverview.html#cookies&quot;&gt;easy-to-use cookie store&lt;/a&gt; for managing state between requests. It has affordances for storing clear-text values, tamper-proof signed  values, and encrypted values.  I previously showed how you can make good use of encrypted values &lt;a href=&quot;/articles/implementing-account-impersonation-in-actioncable/&quot;&gt;for handling ActionCable authentication&lt;/a&gt;. However, there is a case where you might end up with duplicate cookies in Ruby on Rails applications.&lt;/p&gt;

&lt;p&gt;This article discusses cookies, shared cookies, and how the use of the Ruby on Rails cookie store may lead to apparently duplicate cookies. From that, we’ll end with three rules of thumb for using cookies and avoiding the issue.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;basic-cookie-use&quot;&gt;Basic cookie use&lt;/h2&gt;

&lt;p&gt;The Rails cookie store exposes a &lt;code class=&quot;&quot;&gt;#cookies&lt;/code&gt; method that returns a Hash-like object on your controllers. For standard, visitor-editable cookies, the bracket-set operator is your friend:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:plain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This results in a cookie that can be both read and edited on a client device:&lt;/p&gt;

&lt;p&gt;A signed cookie works much the same from your application code. To create a signed cookie, send the &lt;code class=&quot;&quot;&gt;#signed&lt;/code&gt; message with a bracket-set operator, like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;signed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:signed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This sets the value of the cookie to a signed blob of the form &lt;code class=&quot;&quot;&gt;&amp;lt;payload&amp;gt;--&amp;lt;digest&amp;gt;&lt;/code&gt;. The payload is a base 64-encoded JSON document that has a base 64-encoded message containing the value set via your assignment. The digest is, as of Rails 6.1, an HMAC-SHA1 digest using your application’s &lt;code class=&quot;&quot;&gt;secret_key_base&lt;/code&gt; as the key.&lt;/p&gt;

&lt;p&gt;For the curious, the cookie value looks like this in the header:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;eyJfcmFpbHMiOnsibWVzc2FnZSI6IkluWmhiSFZsSWc9PSIsImV4cCI6bnVsbCwicHVyIjoiY29va2llLnNpZ25lZCJ9fQ%3D%3D--20b6c117746e8b08ad4349ce4594e89bd47ef3c5
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lastly, an encrypted cookie follows the same pattern. It’s easy to access, only needing a change to &lt;code class=&quot;&quot;&gt;#encrypted&lt;/code&gt; instead of  &lt;code class=&quot;&quot;&gt;#signed&lt;/code&gt; with the bracket-set operator:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;signed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rails translates the cookie using a pattern similar to that of the signed cookie. It’s a blob of the form &lt;code class=&quot;&quot;&gt;&amp;lt;payload&amp;gt;--&amp;lt;iv&amp;gt;--&amp;lt;auth_tag&amp;gt;&lt;/code&gt;. Breaking that down …&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The payload is a base 64-encoded, encrypted form of the JSON document from the signed implementation.&lt;/li&gt;
  &lt;li&gt;The initialization vector (IV) the base 64-encoded value of the seed to the encryption algorithm, which we need to be able to later decrypt the value.&lt;/li&gt;
  &lt;li&gt;The authentication tag is a base 64-encoded value that you can use to verify both the encrypted and plain text values of the payload. It, along with its delimiting dashes, is only present if you are using &lt;a href=&quot;https://en.wikipedia.org/wiki/Authenticated%5Fencryption#Authenticated%5Fencryption%5Fwith%5Fassociated%5Fdata%5F.28AEAD.29&quot;&gt;an “authenticated encryption with associated data” (AEAD) algorithm&lt;/a&gt;, which Rails 6.1 does by default.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whew, that’s a lot to take in! For the (morbidly?) curious, here’s the cookie in header form:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;x8s62U2CFfX0TgjuAcyX2eLi4PTLzSdWkjvHeYJ5zh8QO22fFj%2F%2BvM%2Fsk%2FV1u1lTKowWKrEe3YpQcYJ2yqRGUiiNMMWUqT9qcA%3D%3D--kwVMgQ4wOkYWXdQS--%2FcJ8VB6zVAsJ5aShfNGHaA%3D%3D
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now that we’ve seen the three different strategies, we can talk about trade-offs.&lt;/p&gt;

&lt;h2 id=&quot;trade-offs-between-methods&quot;&gt;Trade-offs between methods&lt;/h2&gt;

&lt;p&gt;To talk about trade-offs, it will be handy to see each of the values side-by-side:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;value
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;eyJfcmFpbHMiOnsibWVzc2FnZSI6IkluWmhiSFZsSWc9PSIsImV4cCI6bnVsbCwicHVyIjoiY29va2llLnNpZ25lZCJ9fQ%3D%3D--20b6c117746e8b08ad4349ce4594e89bd47ef3c5
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;&quot;&gt;x8s62U2CFfX0TgjuAcyX2eLi4PTLzSdWkjvHeYJ5zh8QO22fFj%2F%2BvM%2Fsk%2FV1u1lTKowWKrEe3YpQcYJ2yqRGUiiNMMWUqT9qcA%3D%3D--kwVMgQ4wOkYWXdQS--%2FcJ8VB6zVAsJ5aShfNGHaA%3D%3D
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;All three of these cookies contain the value &lt;code class=&quot;&quot;&gt;&quot;value&quot;&lt;/code&gt;. The plain cookie is of size 10, the signed cookie is size 148, and the encrypted one is size 171&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. That shows that there is, at a minimum, a space trade-off when using each of these strategies. Since cookies cannot be larger than ~4,096 bytes&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;sup&gt;, &lt;/sup&gt;&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, the trade-off for signing and encrypting can add enough overhead to make problems for you.&lt;/p&gt;

&lt;p&gt;However, you would not want to put your authentication cookie in plaintext for your visitors to monkey with! Imagine the trouble you could get in if they were able to masquerade as your account by changing a cookie value. Signed cookies prevent that from being an issue, but can still leak internal information.&lt;/p&gt;

&lt;p&gt;For extremely important data, like said authentication cookies, use encrypted values and keep the value small.&lt;/p&gt;

&lt;h2 id=&quot;sharing-them-across-multiple-domains&quot;&gt;Sharing them across multiple domains&lt;/h2&gt;

&lt;p&gt;Sometimes, you need to be able to access a cookie on more than one domain. Perhaps you want to be able to show your customer’s avatar on your marketing page, but it runs on a WordPress website instead of your Rails application. Or maybe you run a web application firewall in front of your application and it doesn’t support the WebSockets that you need for a feature (I’m looking at you Sucuri Firewall.)&lt;/p&gt;

&lt;p&gt;Regardless of your use case, when you need to share a cookie between domains, you must change the syntax you use for setting cookies. It’s not a large change, but there is an extra level of memory needed for using this method.&lt;/p&gt;

&lt;p&gt;First, a look at what the new form looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# old&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;value: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;domain: :all&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That &lt;code class=&quot;&quot;&gt;:all&lt;/code&gt; is a wildcard specifier meaning &lt;code class=&quot;&quot;&gt;*.&amp;lt;my_domain&amp;gt;&lt;/code&gt;, or any subdomain of the primary top-level domain. So if you’re running an app at &lt;code class=&quot;&quot;&gt;app.cool.io&lt;/code&gt; and your marketing site runs on &lt;code class=&quot;&quot;&gt;www.cool.io&lt;/code&gt;, the &lt;code class=&quot;&quot;&gt;:all&lt;/code&gt; option will work for you.&lt;/p&gt;

&lt;p&gt;Otherwise, you can specify the domain(s) to which the cookie will apply.&lt;/p&gt;

&lt;h2 id=&quot;duplicate-cookies&quot;&gt;Duplicate cookies?&lt;/h2&gt;

&lt;p&gt;Consider the following scenario. In one place of your application, you set a shared cookie as follows:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;value: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;domain: :all&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In another spot, you nil out the value to remove it from the cookie jar, but you do it like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When you make several subsequent requests to the application, calling these lines in any number of orders and counts, what would you expect the behavior to be?&lt;/p&gt;

&lt;p&gt;In some cases&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;, you might end up with a cookie set for both &lt;code class=&quot;&quot;&gt;mysite.com&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;.mysite.com&lt;/code&gt;! That can lead to some unexpected behavior if you’re not aware it’s a possibility because the cookie that “wins” the fight is the last one to appear in your request headers.&lt;/p&gt;

&lt;p&gt;When this cookie relates to authentication, &lt;a href=&quot;/articles/implementing-account-impersonation-in-actioncable/&quot;&gt;like handling ActionCable authentication&lt;/a&gt;, it can lead to exasperating issues where every other request might come through as unauthenticated.&lt;/p&gt;

&lt;h2 id=&quot;rules-of-thumb&quot;&gt;Rules of thumb&lt;/h2&gt;

&lt;p&gt;With all of these learnings, we can describe three rules of thumb for using cookies in your Ruby on Rails application:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Use the level of cookie that makes sense for the domain. When you handle authentication and authorization, prefer at a minimum, signed cookies. When it doesn’t &lt;em&gt;truly&lt;/em&gt; matter if someone changes the value, prefer plain cookies.&lt;/li&gt;
  &lt;li&gt;Store smaller, ideally primitive, values in a cookie to keep the size down. If you need a rich structure, consider storing an identifier to a record in your database.&lt;/li&gt;
  &lt;li&gt;When you need to share a cookie between domains, ensure that you &lt;em&gt;always&lt;/em&gt; use the same &lt;code class=&quot;&quot;&gt;domain:&lt;/code&gt; parameter when setting the cookie. In fact, there’s a line &lt;a href=&quot;https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html&quot;&gt;in the &lt;code class=&quot;&quot;&gt;ActionDispatch::Cookies&lt;/code&gt; documentation&lt;/a&gt; about that, but it’s easy to miss.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Applying the third rule can take many forms, but once you do, you will see the problem of duplicate cookies disappear.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The cookie store in Ruby on Rails is very easy to work with but has a few sharp edges. Because it’s easy to use, it’s easy to make mistakes. Knowing when and how to use the different types of cookies will help you avoid one class of error.&lt;/p&gt;

&lt;p&gt;But even knowing those basics still leaves a spot for error. When you need to share cookies between domains, there’s a possibility that you end up with duplicate cookies in your app.&lt;/p&gt;

&lt;p&gt;Apply your discipline to the use of cookies and you’ll see your errors vanish before you.&lt;/p&gt;

&lt;p&gt;Have you ever experienced a duplicate cookie in your Ruby on Rails application? How about those tricky authentication cookies - have you ever made a mistake there or discovered one in another app?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;It’s slightly important to note here that the values for the signed and encrypted cookies are inflated by 1 and 4, respectively. Cookie sizes are the size of the cookie name plus the size of the value. Since I used the cookie names “plain,” “signed,” and “encrypted,” the names are of varying size. Keep that in mind when you’re picking names for your cookies! &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Interestingly, &lt;a href=&quot;https://tools.ietf.org/html/rfc6265#section-6.1&quot;&gt;the spec, also known as RFC6265&lt;/a&gt;, does not specify a maximum size. It’s entirely up to browser vendors to implement, though they all agree that 4KiB is the maximum length they’ll support. Some browsers have overhead though, so it’s wise not to approach this value. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Please, consider the effect of large cookies. For every request and every response, the browser transmits the cookie. As such, if you have a 4,000-byte cookie you’re tacking on an extra &lt;code class=&quot;&quot;&gt;8KiB&lt;/code&gt; of data per page for someone using a mobile device. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;I haven’t been able to put my finger on when this happens, but it feels like either a race condition or an order-dependent condition. From anecdotal reloading of a page that non-deterministically sets the value of a cookie to the current domain or to &lt;code class=&quot;&quot;&gt;:all&lt;/code&gt;, I end up seeing both cookies set after a few reloads. If I track down the exact behavior, I’ll update this post. Do you know the cause? If so, please let me know! &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/cookies-5600123_1280.jpg" /><media:content medium="image" url="https://michaeljherold.com/images/cookies-5600123_1280.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">3 Ways to Make Arel a Joy</title><link href="https://michaeljherold.com/articles/3-ways-to-make-arel-more-enjoyable/" rel="alternate" type="text/html" title="3 Ways to Make Arel a Joy" /><published>2021-02-08T09:00:00-06:00</published><updated>2021-02-08T09:00:00-06:00</updated><id>repo://articles.collection/_articles/3-ways-to-make-arel-more-enjoyable.md</id><category term="activerecord" /><category term="ruby-on-rails" /><content type="html" xml:base="https://michaeljherold.com/articles/3-ways-to-make-arel-more-enjoyable/">&lt;p&gt;Arel, the SQL syntax tree constructor library backing ActiveRecord, allows you to express your SQL queries without the use of fragments. ActiveRecord uses it internally to represent a variety of queries. It does this transparently using the traditional &lt;code class=&quot;&quot;&gt;where(key: value)&lt;/code&gt; syntax that we’ve seen since the early days of Rails.&lt;/p&gt;

&lt;p&gt;In cases where you only need to write simple query predicates, the traditional syntax is what you should reach for, full stop. However, when you need to write a more complex query like &lt;a href=&quot;/articles/grouped-pagination-in-activerecord/&quot;&gt;grouped pagination&lt;/a&gt; or &lt;a href=&quot;/articles/null-based-ordering-in-activerecord/&quot;&gt;null-based ordering&lt;/a&gt;, I recommend reaching for Arel.&lt;/p&gt;

&lt;p&gt;However, because Arel considers itself a “framework framework,” it’s not a truly enjoyable to use out-of-the-box. This article shares three ways to make Arel a joy to use.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;table-accessor&quot;&gt;Table accessor&lt;/h2&gt;

&lt;p&gt;Arel’s status as a private API makes it so there are not easy-to-use affordances for it by default. So, it stands to reason, the first step we should take is to make it easier to use Arel queries. That first step is a simple one-line method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationRecord&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;field&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With this change, you can use &lt;code class=&quot;&quot;&gt;Model[:field]&lt;/code&gt; to access the Arel attribute for any field or alias in your models. That allows you to make writing even simple queries less error-prone.&lt;/p&gt;

&lt;p&gt;To show what I mean, take the following &lt;a href=&quot;https://github.com/rubygems/rubygems.org/blob/f53eda471eb558dc5c88089e49d1cb4e629cd0fa/app/models/ownership.rb#L24-L26&quot;&gt;example from RubyGems.org&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_owner_handle!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;users: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;handle: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;users: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;One thing that, no matter how long I write Rails applications, I make a mistake on the first try is switching between the singular and plural forms of the model versus the table. To paraphrase &lt;a href=&quot;https://nosycrow.com/product/theres-a-bear-on-my-chair/&quot;&gt;one of my daughter’s favorite books&lt;/a&gt; …&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I understand that tables are plural.
I &lt;em&gt;know&lt;/em&gt; that models are singular.
I &lt;em&gt;know&lt;/em&gt; all that.
I am aware.&lt;/p&gt;

  &lt;p&gt;But &lt;em&gt;still&lt;/em&gt;
I cannot
&lt;em&gt;remember&lt;/em&gt;
&lt;em&gt;in a&lt;/em&gt;
&lt;em&gt;query&lt;/em&gt;.&lt;/p&gt;

  &lt;p&gt;– Apologies to &lt;a href=&quot;http://rosscollins.net/&quot;&gt;Mr. Collins&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Inevitably, I will read the joins clause (&lt;code class=&quot;&quot;&gt;joins(:user)&lt;/code&gt;) and intuitively write the predicate as &lt;code class=&quot;&quot;&gt;user: { handle: handle }&lt;/code&gt; even though I &lt;em&gt;know&lt;/em&gt; that it’s SQL that we’re writing in the Hash-based query.&lt;/p&gt;

&lt;p&gt;It’s &lt;em&gt;not&lt;/em&gt; a big deal.&lt;/p&gt;

&lt;p&gt;It’s &lt;em&gt;really&lt;/em&gt; isn’t.&lt;/p&gt;

&lt;p&gt;But it’s just enough of a paper cut that it can cause me to lose my focus when my test unexpectedly blinks red.&lt;/p&gt;

&lt;p&gt;As such, I’ve taken to writing these types of query like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by_owner_handle!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is unambiguous. I’m writing more Ruby and less Hash-as-SQL. I don’t have to think about it and the way the Arel attribute works, it feels like I’m accessing a Hash so I have the muscle memory for it. No more paper cuts!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As an aside, because this is Ruby, there’s &lt;a href=&quot;https://www.artima.com/intv/ruby.html#part3&quot;&gt;more than one way to do the same thing&lt;/a&gt; and I might instead use &lt;a href=&quot;https://guides.rubyonrails.org/active%5Frecord%5Fquerying.html#merging-of-scopes&quot;&gt;the &lt;code class=&quot;&quot;&gt;merge&lt;/code&gt; method&lt;/a&gt;. But that’s because this is a simple example; in general, the principle still holds.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;operator-aliasing&quot;&gt;Operator aliasing&lt;/h2&gt;

&lt;p&gt;Another paper cut that I experience that makes using Arel not quite joyful is remembering the names of the operators. Is it &lt;code class=&quot;&quot;&gt;gteq&lt;/code&gt; or &lt;code class=&quot;&quot;&gt;gteql&lt;/code&gt;? Likewise, is it &lt;code class=&quot;&quot;&gt;eq&lt;/code&gt; or &lt;code class=&quot;&quot;&gt;eql&lt;/code&gt; … or was it &lt;code class=&quot;&quot;&gt;equal&lt;/code&gt;? What about &lt;code class=&quot;&quot;&gt;neq&lt;/code&gt; vs &lt;code class=&quot;&quot;&gt;not_eq&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Abbreviations (or lack thereof) for these operators &lt;a href=&quot;https://docs.python.org/3/reference/datamodel.html#object.%5F%5Feq%5F%5F&quot;&gt;change&lt;/a&gt; across &lt;a href=&quot;https://golang.org/pkg/bytes/#Equal&quot;&gt;languages&lt;/a&gt; and, &lt;a href=&quot;https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node74.html&quot;&gt;in some cases&lt;/a&gt;, include all of them for different uses! If you find yourself hopping between languages with any sort of regularity, it can be difficult to write this correctly on the first try:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gteq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To remove some ambiguity, you can use the superpowers of Ruby to define operators that you won’t mistake:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Arel::Predications&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;alias_method&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:gt&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;alias_method&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:gteq&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;alias_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:lt&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;alias_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:&amp;lt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:lteq&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That change allows you to write this with a maths-like syntax:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Or you might choose to do something heretical and alias &lt;code class=&quot;&quot;&gt;gt&lt;/code&gt; as &lt;code class=&quot;&quot;&gt;after&lt;/code&gt; so you can write the following, thus saving some mental overhead when performing date maths:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;after&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Just remember, &lt;a href=&quot;https://en.wikipedia.org/wiki/With%5Fgreat%5Fpower%5Fcomes%5Fgreat%5Fresponsibility&quot;&gt;with great power&lt;/a&gt; and all that.&lt;/p&gt;

&lt;h2 id=&quot;extending-arel-and-activerecord&quot;&gt;Extending Arel and ActiveRecord&lt;/h2&gt;

&lt;p&gt;Sometimes the SQL standard moves more slowly than the industry does, leading to &lt;a href=&quot;https://www.postgresql.eu/events/pgconfeu2019/sessions/session/2555/slides/221/jsonpath-pgconfeu-2019.pdf&quot;&gt;databases innovating ahead of the standard&lt;/a&gt;. That innovation is great because it helps raise the tide for everyone, but it can lead to &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING&quot;&gt;opaque, hard-to-remember syntax&lt;/a&gt; that only works for a single database type.&lt;/p&gt;

&lt;p&gt;For example, I can never remember &lt;a href=&quot;https://www.postgresql.org/docs/13/functions-array.html&quot;&gt;any PostgreSQL array functions&lt;/a&gt;. Which set of symbols means “contains”? Gotta look it up.&lt;/p&gt;

&lt;p&gt;To help with this, you can define custom Arel nodes like the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arel::Nodes::Contains&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;InfixOperation&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:&apos;@&amp;gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This encodes the knowledge of symbol translate to “contains” in the &lt;code class=&quot;&quot;&gt;Contains&lt;/code&gt; class, which is much easier to remember. Since this is a custom Arel node, you must let your database-specific visitor know how to handle it, which you can do this like this for PostgreSQL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arel::Visitors::PostgreSQL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Visitors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ToSql&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;alias_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:visit_Arel_Nodes_Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;:visit_Arel_Nodes_InfixOperation&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This change tells the visitor that the Contains node is just an Infix Operation (which is any operation of the form &lt;code class=&quot;&quot;&gt;arg1 op arg2&lt;/code&gt;, like &lt;code class=&quot;&quot;&gt;1 + 2&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Once you have this change, you can use your custom Arel node in a query like the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build_quoted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ruby&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This query translates to &lt;code class=&quot;&quot;&gt;SELECT * FROM &quot;posts&quot; WHERE &quot;posts&quot;.&quot;tags&quot; @&amp;gt; &apos;ruby&apos;;&lt;/code&gt; to look for all posts that have a “ruby” tag. The bit here that you need to know is the &lt;code class=&quot;&quot;&gt;build_quoted&lt;/code&gt; method, which is responsible for wrapping — or “quoting” — arguments for use as parameters in SQL queries.&lt;/p&gt;

&lt;p&gt;You can, of course, abstract this one level higher to ActiveRecord, which I recommend doing. But that’s another post for another day.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Arel is a powerful way to compose your SQL queries. However, the Rails core team considers it a private API so it’s not a smooth experience to use it out-of-the-box.&lt;/p&gt;

&lt;p&gt;But this is Ruby! With a little bit of love, Arel can be a joy to use. It can save you from silly mistakes, make your queries more expressive, and be easily extended once you know how it works. I encourage you to give it a try to see if it helps you in your day-to-day work.&lt;/p&gt;

&lt;p&gt;Do you use Arel in your project? If so, have you run into any issues with it? What’s your favorite thing to do with it?&lt;/p&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/question-mark-1872665_1280.jpg" /><media:content medium="image" url="https://michaeljherold.com/images/question-mark-1872665_1280.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Null-based ordering in ActiveRecord</title><link href="https://michaeljherold.com/articles/null-based-ordering-in-activerecord/" rel="alternate" type="text/html" title="Null-based ordering in ActiveRecord" /><published>2021-02-01T09:00:00-06:00</published><updated>2021-02-01T09:00:00-06:00</updated><id>repo://articles.collection/_articles/null-based-ordering-in-activerecord.md</id><category term="activerecord" /><category term="ruby-on-rails" /><content type="html" xml:base="https://michaeljherold.com/articles/null-based-ordering-in-activerecord/">&lt;p&gt;When designing your domain model for an application, the natural solution might end up using a &lt;code class=&quot;&quot;&gt;nil&lt;/code&gt; to model the absence of a value. In these cases, you might need to reach for null-based ordering within your database. When you’re writing a Ruby on Rails application, you’re most likely going to use ActiveRecord as the interface to your database. While ActiveRecord supports most queries out-of-the-box, sometimes you have to go off the beaten path.&lt;/p&gt;

&lt;p&gt;Null-based ordering is one of those situations. Since ActiveRecord’s ordering works based on simple &lt;code class=&quot;&quot;&gt;key: :direction&lt;/code&gt; pairs, it is hard to perform null-based ordering outside of using the built-in behavior of your database. This article outlines two ways you can structure this type of null-based ordering, along with the caveats of using them.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;a-simple-model&quot;&gt;A simple model&lt;/h2&gt;

&lt;p&gt;For this article, consider the following model. You want to deliver notifications to your customers. Each notification has a header and a body. You deliver the notification to an account via a “delivery” and the owner of the account receives the delivery and reads it at two separate times. When showing notifications to the owner of an account, you want to show unread notifications first in reverse order of delivery, then show the read notifications in the order that the owner read them.&lt;/p&gt;

&lt;p&gt;Let’s run through a &lt;a href=&quot;https://en.wikipedia.org/wiki/Class-responsibility-collaboration%5Fcard&quot;&gt;Class-Responsibility-Collaborator card&lt;/a&gt; exercise to define your model. Looking at this short description, we see three nouns that are candidates for models: &lt;code class=&quot;&quot;&gt;Account&lt;/code&gt;, &lt;code class=&quot;&quot;&gt;Notification&lt;/code&gt;, and &lt;code class=&quot;&quot;&gt;Delivery&lt;/code&gt;. We also see four properties: a &lt;code class=&quot;&quot;&gt;header&lt;/code&gt;, a &lt;code class=&quot;&quot;&gt;body&lt;/code&gt;, a &lt;code class=&quot;&quot;&gt;delivered at&lt;/code&gt; time, and a &lt;code class=&quot;&quot;&gt;read at&lt;/code&gt; time. We also see two responsibilities, “show unread notifications first” and “show read notifications in the order the owner read them.”&lt;/p&gt;

&lt;p&gt;We can translate these findings to the following model:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Account&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Notifications::Delivery&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:notifications&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;through: :deliveries&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notification&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Notifications::Delivery&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;has_many&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:accounts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;through: :deliveries&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notifications::Delivery&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;alias_attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:delivered_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:created_at&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attribute&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:read_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:account&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;belongs_to&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:notification&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This simple model uses a join model to represent the &lt;em&gt;act&lt;/em&gt; of delivering a notification. By modeling an act, the join model makes the model more flexible by allowing us to easily deliver notifications to multiple accounts. It also separates the two concerns — the content of the notification and the act of delivering it — so that they can be independently modified.&lt;/p&gt;

&lt;p&gt;Now that we have a model, let’s think about how to satisfy the goal of the brief.&lt;/p&gt;

&lt;h2 id=&quot;default-null-ordering&quot;&gt;Default null ordering&lt;/h2&gt;

&lt;p&gt;By default, SQL considers &lt;code class=&quot;&quot;&gt;NULL&lt;/code&gt; to be a value less than any present value. When you order by a nullable field, an &lt;code class=&quot;&quot;&gt;ORDER BY read_at ASC&lt;/code&gt; places all &lt;code class=&quot;&quot;&gt;NULL&lt;/code&gt; values at the end of the results, whereas an &lt;code class=&quot;&quot;&gt;ORDER BY read_at DESC&lt;/code&gt; clause places them at the beginning.&lt;/p&gt;

&lt;p&gt;With that knowledge, it’s straightforward on how to place the unread notifications at the start of the list:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;&quot;notifications_deliveries&quot;.&quot;read_at&quot; DESC&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I find SQL fragments to be frustrating to write because typos are less obvious and you can’t use Rails’ aliased attributes so we could refactor this to use &lt;code class=&quot;&quot;&gt;ActiveRecord::Relation#merge&lt;/code&gt; to write each clause in the natural sense:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;read_at: :desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we have the unread messages at the beginning of the list … but the most recently read last. That doesn’t quite match our brief. What we want is for the unread notifications to be listed first, but the read ones to be listed in ascending order. What can we do?&lt;/p&gt;

&lt;h2 id=&quot;null-based-ordering-in-sql-2003&quot;&gt;Null-based ordering in SQL:2003&lt;/h2&gt;

&lt;p&gt;The 2003 version of the ISO SQL added a &lt;a href=&quot;https://github.com/ronsavage/SQL/blob/a67e7eaefae89ed761fa4dcbc5431ec9a235a6c8/sql-2003-2.bnf#L3160&quot;&gt;null ordering clause&lt;/a&gt; that allows you to specify &lt;code class=&quot;&quot;&gt;NULLS FIRST&lt;/code&gt; or &lt;code class=&quot;&quot;&gt;NULLS LAST&lt;/code&gt; for a nullable field. This changes the default interpretation of null for the purposes of sorting. Using this logic, we could change the query to:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;&quot;notifications_deliveries&quot;.&quot;read_at&quot; ASC NULLS FIRST&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This gets the notifications in the order we want, except for when there are multiple unread messages. Let’s fix that:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;joins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;&quot;notifications_deliveries&quot;.&quot;read_at&quot; ASC NULLS FIRST&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;&quot;notifications_deliveries&quot;.&quot;created_at&quot; DESC&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The first &lt;code class=&quot;&quot;&gt;ORDER BY&lt;/code&gt; clause handles “unread notifications first […] read notifications in the order in which the owner read them.” And the second handles “unread notifications in order of delivery” because the database will only apply it to break ties where &lt;code class=&quot;&quot;&gt;read_at&lt;/code&gt; is the same value (usually only for unread notifications).&lt;/p&gt;

&lt;h2 id=&quot;refactoring-to-arel&quot;&gt;Refactoring to Arel&lt;/h2&gt;

&lt;p&gt;Since ActiveRecord does not, by default, support &lt;code class=&quot;&quot;&gt;NULLS FIRST&lt;/code&gt; we resorted to two SQL fragments. But as I said earlier, I find SQL fragments to be difficult to write on the first go-around and hard to debug when they result in error. To fix this, we can use the Arel internal querying grammar&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. A refactor might looks something like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notifications::Delivery&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in_display_order&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;asc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;nulls_first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:delivered_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;desc&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With this, we have encapsulated the logic in a chainable method on the relation using fully-qualified field names, so we can go back to using &lt;code class=&quot;&quot;&gt;#merge&lt;/code&gt; to run the full query:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:deliveries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Delivery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in_display_order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are two extra things that I leave as exercises for the reader:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We are not scoping this query to a single account. How would you make sure you’re only showing deliveries to a particular account?&lt;/li&gt;
  &lt;li&gt;This is a lot to type and a lot to change if we want to show notifications in this order in multiple places. What could you add or refactor to make it easier to change this logic in the future? Hint: think about how can we hide the implementation detail of deliveries from a module that only wants to know about &lt;code class=&quot;&quot;&gt;Notification&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;database-support&quot;&gt;Database support&lt;/h2&gt;

&lt;p&gt;Since ActiveRecord is a generic object-relational mapper over several different databases, you have to think about whether your database supports this syntax before you try to use this in your own application. Let’s take a look at database support for null-based ordering.&lt;/p&gt;

&lt;h3 id=&quot;postgresql&quot;&gt;PostgreSQL&lt;/h3&gt;

&lt;p&gt;PostgreSQL has supported null-based ordering since &lt;a href=&quot;https://www.postgresql.org/docs/8.3/queries-order.html&quot;&gt;v8.3&lt;/a&gt;. Since this version went end-of-life in 2013, you are most likely on a version newer than that so go ahead!&lt;/p&gt;

&lt;h3 id=&quot;mysql&quot;&gt;MySQL&lt;/h3&gt;

&lt;p&gt;As of v8.0.23, released on 2021-01-18, MySQL does not support this query&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. You can work around the problem by using some quirks of MySQL’s treatments of null.&lt;/p&gt;

&lt;p&gt;To order null values first in a query, we can rely on the fact that &lt;code class=&quot;&quot;&gt;IS NULL&lt;/code&gt; returns a &lt;code class=&quot;&quot;&gt;1&lt;/code&gt; for a null value and a &lt;code class=&quot;&quot;&gt;0&lt;/code&gt; otherwise. Using that knowledge, we can split the predicate into two:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notifications::Delivery&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in_display_order&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not_eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;asc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;arel_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:delivered_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;desc&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’m not really sure how this will interact with indexes since I haven’t used MySQL in production since before the MariaDB split. Since the predicate checks for a null value, I &lt;em&gt;think&lt;/em&gt; it will use indexes accordingly, but check before you roll it out to production.&lt;/p&gt;

&lt;p&gt;If you use this and have different findings, please let me know!&lt;/p&gt;

&lt;h3 id=&quot;sqlite&quot;&gt;SQLite&lt;/h3&gt;

&lt;p&gt;SQLite has supported null-based ordering since &lt;a href=&quot;https://www.sqlite.org/changes.html&quot;&gt;v3.30.0&lt;/a&gt;, released on 2019-10-30. As of this writing, Debian Stable (Buster) is only on v3.27.2 but Debian Testing and Unstable (Buster) are both on v3.34.1. Ubuntu 20.04, the latest LTS release, is on v3.31.1. And for the Docker users, Alpine is on v3.34.1.&lt;/p&gt;

&lt;p&gt;However, as of Rails 6.1.1, the SQLite adapter does not support the Arel version of the query and the SQL fragment version requires a wrap of the &lt;code class=&quot;&quot;&gt;NULLS FIRST&lt;/code&gt; ordering clause in &lt;code class=&quot;&quot;&gt;Arel.sql&lt;/code&gt; to prevent an &lt;code class=&quot;&quot;&gt;ActiveRecord::UnknownAttributeReference&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With those caveats, a modified version looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notifications::Delivery&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;in_display_order&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;Arel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&quot;notifications_deliveries&quot;.&quot;read_at&quot; ASC NULLS FIRST&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;&quot;notifications_deliveries&quot;.&quot;created_at&quot; DESC&apos;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Null-based ordering allows you to have fine-grained control over the ordering of nullable fields within your database queries. For cases where you need to handle null values differently, using the SQL:2003 null ordering predicates can help you make your sorting logic easier-to-read and understand while also helping you keep querying logic in the database rather than Ruby.&lt;/p&gt;

&lt;p&gt;PostgreSQL has excellent support for null-based ordering, both at the database level and the ActiveRecord level. MySQL does not support it, but there are reasonable workarounds. And SQLite requires a small workaround due to not supporting the query at the ActiveRecord level.&lt;/p&gt;

&lt;p&gt;Using Arel makes it so you can even use your domain logic when you use &lt;code class=&quot;&quot;&gt;alias_attribute&lt;/code&gt;, which is an added cherry on top. Even though it’s a private API, it’s stable enough that I feel fine using it with tests around it for regression coverage during Rails upgrades.&lt;/p&gt;

&lt;p&gt;Have you ever used null-based ordering in your Rails application? What were you querying for? If not, has there ever been a case where you needed to work around the problem?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;It’s important to note that the Rails core team considers Arel to be a private API. I use it extensively and it has been stable for my use cases, but I recommend writing tests around any query in which you use it to make sure that Rails upgrades don’t break your queries. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;I know MySQL is still very popular, but I don’t know why. Do you work somewhere that uses MySQL? I’d love to hear why! &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/zero-1207806_1280.jpg" /><media:content medium="image" url="https://michaeljherold.com/images/zero-1207806_1280.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Memoization in Ruby using three different patterns</title><link href="https://michaeljherold.com/articles/memoization-in-ruby/" rel="alternate" type="text/html" title="Memoization in Ruby using three different patterns" /><published>2021-01-25T09:00:00-06:00</published><updated>2021-01-25T09:00:00-06:00</updated><id>repo://articles.collection/_articles/memoization-in-ruby.md</id><category term="ruby" /><content type="html" xml:base="https://michaeljherold.com/articles/memoization-in-ruby/">&lt;p&gt;Memoization is the process of performing an expensive calculation — a lookup from a database, a long-running function, etc. — and then caching its value to prevent the expensive action from running again. In computer science jargon, it trades space complexity for time complexity; that is, instead of releasing the calculated value for the garbage collector, it stores the value, thus increasing the space requirements of the object, to save on the time it takes to calculate the value more than once.&lt;/p&gt;

&lt;p&gt;There are myriad ways to perform memoization in Ruby. The most common means is to use the &lt;code class=&quot;&quot;&gt;||=&lt;/code&gt; operator to conditionally set an instance variable upon first running the method. In most cases, this works well. However, there are certain cases where it does not do what you expect it to. This article discusses the semantics and gotchas of memoization in Ruby and shares different patterns you can use to ensure the correct behavior happens every time you choose to memoize a method.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;hello-my-old-friend&quot;&gt;Hello, &lt;code class=&quot;&quot;&gt;||=&lt;/code&gt;, my old friend&lt;/h2&gt;

&lt;p&gt;One of the first things you see when source-diving or working in Ruby on a team is the use of the &lt;code class=&quot;&quot;&gt;||=&lt;/code&gt; operator to save method results as instance variables. Using a simple Rails controller as an example, this pattern looks like the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once you have seen this pattern and understood it at the surface level, it’s easy to apply. The algorithm looks something like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a method that performs some expensive calculation.&lt;/li&gt;
  &lt;li&gt;When you see that you use the method more than once, apply &lt;code class=&quot;&quot;&gt;@&amp;lt;method_name&amp;gt; ||=&lt;/code&gt; on the left-hand side of the method’s body.&lt;/li&gt;
  &lt;li&gt;Move on to your next task.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But what is really happening here? Let’s break down the semantics of the operator. Were we to expand that line, it would look like the following&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After seeing the expanded form written out, we can step through its semantics. Due to operator precedence and the everything-is-an-expression nature of Ruby (e.g. there is an implicit &lt;code class=&quot;&quot;&gt;return&lt;/code&gt; on the last statement of a block or method), there are two steps to this algorithm:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;If &lt;code class=&quot;&quot;&gt;@_current_user&lt;/code&gt; is truthy, return the value from the method.&lt;/li&gt;
  &lt;li&gt;Otherwise, set &lt;code class=&quot;&quot;&gt;@_current_user&lt;/code&gt; to the value returned by &lt;code class=&quot;&quot;&gt;User.find_by(id: session[:user_id)&lt;/code&gt; and return the value from the method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple enough, right? Use the value if it’s there, otherwise calculate it. That accomplishes our goal for much of the problem space, but writing out the algorithm in English helps to illustrate that it doesn’t fully memoize the answer for all types of work that we might do.&lt;/p&gt;

&lt;h2 id=&quot;when-is-not-enough&quot;&gt;When is &lt;code class=&quot;&quot;&gt;||=&lt;/code&gt; not enough?&lt;/h2&gt;

&lt;p&gt;Think about the English algorithm again. Do you see the problem? To illustrate the point, it will help to veer into Boolean algebra for a moment.&lt;/p&gt;

&lt;p&gt;In traditional Boolean algebra, there are two values: &lt;em&gt;true&lt;/em&gt; and &lt;em&gt;false&lt;/em&gt;. Computers fully run on Boolean algebra as all binary coding and logic run on this branch of algebra. That’s all well and good for the electrons working their way through the silicon of your computer processor, but we are programmers, not electrical engineers.&lt;/p&gt;

&lt;p&gt;Computer programming is the practice of telling a computer what to do. However, it’s not &lt;em&gt;only&lt;/em&gt; that. It is also the practice of codifying your understanding of a problem such that &lt;em&gt;other humans&lt;/em&gt; can tell what the computer will do. Because the designers of the languages that we program mean for your code to be understandable by both machine and human&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, we often take shortcuts to reduce the amount of mental clutter in our programs. One such shortcut is the introduction of the concepts of &lt;em&gt;truthiness&lt;/em&gt; and &lt;em&gt;falsiness&lt;/em&gt;, with their itinerant adjectives &lt;em&gt;truthy&lt;/em&gt; and &lt;em&gt;falsy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When we say something is &lt;em&gt;truthy&lt;/em&gt;, we mean that when we pass the value to an &lt;code class=&quot;&quot;&gt;if&lt;/code&gt; statement, the program will take the success branch rather than the failure one. The opposite is true of &lt;em&gt;falsy&lt;/em&gt; values. In Ruby, everything is truthy except for an instance of &lt;code class=&quot;&quot;&gt;NilClass&lt;/code&gt; (i.e. the value &lt;code class=&quot;&quot;&gt;nil&lt;/code&gt;) or an instance of &lt;code class=&quot;&quot;&gt;FalseClass&lt;/code&gt; (i.e. the value &lt;code class=&quot;&quot;&gt;false&lt;/code&gt;). This is one of the reasons why Rails introduced the &lt;code class=&quot;&quot;&gt;#present?&lt;/code&gt; and &lt;code class=&quot;&quot;&gt;#blank?&lt;/code&gt; methods and why you see them littered throughout the code base of Rails applications.&lt;/p&gt;

&lt;p&gt;Now that we know what we mean when we say truthy, the problem becomes clear: in our method, when we set &lt;code class=&quot;&quot;&gt;@_current_user&lt;/code&gt; to something falsy, the method will run the calculation again … and again and again, if you call it repeatedly. With that knowledge, how can we ameliorate this problem?&lt;/p&gt;

&lt;h2 id=&quot;a-simple-amelioration&quot;&gt;A simple amelioration&lt;/h2&gt;

&lt;p&gt;The semantics of variables in programming languages is a topic that is hotly debated in language design circles. When should a variable be considered in scope? When is it defined? Do you hoist the definition to the top of the scope or not consider it until the line that declares the variable? Exciting stuff, for those who like that type of thing.&lt;/p&gt;

&lt;p&gt;In Ruby, we define a variable — whether it’s a class, instance, or local variable — by putting its name on the left-hand side of an assignment operator&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; for the first time. Once we define a variable, we gain access to the keyword-cum-method &lt;code class=&quot;&quot;&gt;#defined?&lt;/code&gt; for checking whether we defined it previously … even if it is falsy.&lt;/p&gt;

&lt;p&gt;Now, with knowledge of &lt;code class=&quot;&quot;&gt;#defined?&lt;/code&gt;, we can design a new memoization strategy that helps protect our nilable &lt;code class=&quot;&quot;&gt;User.find_by&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt;

    &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The first time you call the method, the instance variable &lt;code class=&quot;&quot;&gt;@_current_user&lt;/code&gt; will not be defined so the method body runs, setting &lt;code class=&quot;&quot;&gt;@_current_user&lt;/code&gt; to the &lt;code class=&quot;&quot;&gt;User&lt;/code&gt; or &lt;code class=&quot;&quot;&gt;nil&lt;/code&gt;. The next time you call it, &lt;code class=&quot;&quot;&gt;@_current_user&lt;/code&gt; is defined so it returns via the guard clause. Peachy!&lt;/p&gt;

&lt;h3 id=&quot;a-gotcha-in-rails-controllers&quot;&gt;A gotcha in Rails controllers&lt;/h3&gt;

&lt;p&gt;Since Rails controllers share their instance variables and methods with the view context, you must exercise your discipline with helper methods and instance variables. Don’t access your instance variables from your view; declare them as helper methods in your controllers. This is the reason why I prefix my instance variables with an underscore in my Rails examples. It helps to communicate that we should not access the instance variable and instead use a defined helper method.&lt;/p&gt;

&lt;p&gt;The danger is that when you access the instance variable from the view, there is a chance that you might inadvertently define it. If by accident, you define the instance variable backing your &lt;code class=&quot;&quot;&gt;#current_user&lt;/code&gt; method in the view, you might break the method for every other caller.&lt;/p&gt;

&lt;p&gt;As such, I recommend instituting a policy in your application of not accessing instance variables in views, whether they are partials or not.&lt;/p&gt;

&lt;h2 id=&quot;memoizing-methods-with-parameters&quot;&gt;Memoizing methods with parameters&lt;/h2&gt;

&lt;p&gt;Sometimes, we write methods that take parameters and perform expensive actions based on the parameters sent to the method. In those cases, we often still want to make the same trade-off in space for time that we do for singular variables. How can we cache the results of a parameterized method?&lt;/p&gt;

&lt;p&gt;Writing out our meaning in English can, again, help:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;If we have called the method &lt;code class=&quot;&quot;&gt;#expensive&lt;/code&gt; with the argument &lt;code class=&quot;&quot;&gt;&quot;foo&quot;&lt;/code&gt; before, return the value that we cached last time.&lt;/li&gt;
  &lt;li&gt;Otherwise, calculate the value and cache the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Single-parameter methods are the easiest here because the argument is easy to visualize as a cache key. When you want to cache something, you want a fast lookup (ideally &lt;code class=&quot;&quot;&gt;O(1)&lt;/code&gt;) from a list of values. Expressed in that manner, is there a data structure that comes to mind?&lt;/p&gt;

&lt;p&gt;Consider the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;expensive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;defined?&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt;

  &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_expensive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Hashes have &lt;code class=&quot;&quot;&gt;O(1)&lt;/code&gt; lookup cost, so they make a natural fit for the cache storage. Once we make that decision, we can leap to this implementation. Let’s break it down:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The guard clause runs fetches from the cache and uses the fall-through when the value isn’t there, as long as the cache was previously defined.&lt;/li&gt;
  &lt;li&gt;The storage takes the form of a Hash with a &lt;code class=&quot;&quot;&gt;#default_proc&lt;/code&gt;. The block takes two arguments: the first is the hash itself and the second is the key that you’re trying to access. By setting the value of the key within the storage hash inside the &lt;code class=&quot;&quot;&gt;#default_proc&lt;/code&gt;, we ensure that a call calculates the value and caches it.&lt;/li&gt;
  &lt;li&gt;The call to &lt;code class=&quot;&quot;&gt;@_expensive_memo[arg]&lt;/code&gt; ensures that we get the calculated value from the first time we run the &lt;code class=&quot;&quot;&gt;#expensive&lt;/code&gt; method instead of the cache store.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern works as long as &lt;code class=&quot;&quot;&gt;arg&lt;/code&gt; has a stable &lt;code class=&quot;&quot;&gt;#hash&lt;/code&gt; method. This is true for built-ins like String and the Numeric classes, ActiveRecord objects, and Structs, but is &lt;em&gt;not&lt;/em&gt; true by default for plain old Ruby objects that you implement yourself, so keep that in mind if you need to pass rich objects to your memoized method.&lt;/p&gt;

&lt;h3 id=&quot;extending-for-multiple-arguments&quot;&gt;Extending for multiple arguments&lt;/h3&gt;

&lt;p&gt;What if you have multiple arguments that you need to pass to the method? You can pass an Array of objects as a Hash key, so it’s a small tweak to get that to work:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;expensive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner_arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner_arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_expensive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner_arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In this example, we are destructuring the cache key into its constituent parts and then recombining them back into an array when setting the key. You could also use the &lt;code class=&quot;&quot;&gt;*&lt;/code&gt; or &lt;code class=&quot;&quot;&gt;**&lt;/code&gt; operators if you choose to break out the calculation part into its own method; it depends on how communicative you want the signature of the memoized method to be. That could look like the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;expensive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner_args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_expensive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inner_args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;vi&quot;&gt;@_expensive_memo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;alternatives-to-memoization&quot;&gt;Alternatives to memoization&lt;/h2&gt;

&lt;p&gt;The advantage of memoization is that it allows your objects to be lazy and not initialize their state until you need it. But sometimes you have state that you will always need to initialize, even though it’s expensive. In those cases, consider setting the state as part of the initializer and using a simple &lt;code class=&quot;&quot;&gt;attr_reader&lt;/code&gt; to read the state. This is Ruby, after all; if there’s one way to do it, there are a dozen other ways as well.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;When you reach for memoization in Ruby, don’t blindly fall into the habit of &lt;code class=&quot;&quot;&gt;@my_var ||= &quot;something expensive&quot;&lt;/code&gt;. First, think about whether your calculation can return a falsy result. Particularly when you’re fetching single models from ActiveRecord or calculating a Boolean predicate, you have to consider the values returned by the method. If there’s the possibility of a falsy result, then adjust your method to use the check for definition. And lastly, when you need to memoize multiple values from one method, reach for a Hash to store those results.&lt;/p&gt;

&lt;p&gt;Memoization can be a powerful tool in your toolbelt, but like all tools, pay attention to overuse. Also, consider whether the lazy initialization buys you anything in the context of what you’re currently working on. Perhaps it would be better to instantiate the state up front instead.&lt;/p&gt;

&lt;p&gt;Has erroneous memoization ever bit you? What’s the worst example of a mistake you’ve made using memoization? Do you have any particularly great wins from applying the pattern?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;Using &lt;code class=&quot;&quot;&gt;RubyVM::InstructionSequence.compile().disasm&lt;/code&gt; shows that this isn’t quite correct … but it is close enough to illustrate the point. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;We mean languages to be understandable with some exceptions. &lt;a href=&quot;https://en.wikipedia.org/wiki/Malbolge&quot;&gt;Malbolge&lt;/a&gt; comes to mind. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Or a variety of other ways, including &lt;code class=&quot;&quot;&gt;instance_variable_set&lt;/code&gt;, but that is beyond the scope of this article. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content></entry><entry><title type="html">Implementing account impersonation in ActionCable</title><link href="https://michaeljherold.com/articles/implementing-account-impersonation-in-actioncable/" rel="alternate" type="text/html" title="Implementing account impersonation in ActionCable" /><published>2021-01-18T09:00:00-06:00</published><updated>2021-01-18T09:00:00-06:00</updated><id>repo://articles.collection/_articles/implementing-account-impersonation-in-actioncable.md</id><category term="actioncable" /><category term="ruby-on-rails" /><content type="html" xml:base="https://michaeljherold.com/articles/implementing-account-impersonation-in-actioncable/">&lt;p&gt;Account impersonation is when you allow accounts (usually privileged accounts, e.g. support staff or developers) to operate your Rails application as if they are the owner of another account entirely. It’s a helpful feature to have when diagnosing issues that your customers write in about. There are &lt;a href=&quot;https://github.com/astrails/rails%5Fadmin%5Fimpersonate&quot;&gt;plenty&lt;/a&gt; of &lt;a href=&quot;https://github.com/foraker/mr%5Fmime&quot;&gt;gems&lt;/a&gt; that &lt;a href=&quot;https://github.com/oivoodoo/devise%5Fmasquerade&quot;&gt;implement&lt;/a&gt; impersonation for your Rails controllers, but I wasn’t able to turn up any suggestions for implementing this for &lt;a href=&quot;https://guides.rubyonrails.org/action%5Fcable%5Foverview.html&quot;&gt;ActionCable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post outlines a simple method for adding impersonation to your Rails application in both your controllers and your ActionCable channels. I discuss some ActionCable features that are not well-documented and that I found a little confusing, then show examples of how to use the same pattern for identifying both your active account and your impersonating account — called a “shadower” in this article — in your channels.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;active-accounts-in-controllers&quot;&gt;Active accounts in controllers&lt;/h2&gt;

&lt;p&gt;In Rails tutorials, it’s common to see the authors reach for a batteries-included authentication gem like &lt;a href=&quot;https://github.com/heartcombo/devise&quot;&gt;Devise&lt;/a&gt;, &lt;a href=&quot;https://github.com/thoughtbot/clearance&quot;&gt;Clearance&lt;/a&gt;, or &lt;a href=&quot;https://github.com/binarylogic/authlogic&quot;&gt;AuthLogic&lt;/a&gt;. For the purposes of this post, we ignore all of these gems and hand-wave over the authentication bits in favor of a simple example. In the interest of shared understanding, we use the &lt;code class=&quot;&quot;&gt;ApplicationController#current_user&lt;/code&gt; pattern common to all three of the big libraries&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SessionsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticated_user&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;reset_session&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticated_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root_path&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Do some error handling&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authenticated_user&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Do something here to authenticate the session or return `nil`&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;protected&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;helper_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:current_user&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In this example, we see a simple way to load the operating &lt;code class=&quot;&quot;&gt;User&lt;/code&gt; from the database. In our sessions controller, we authenticate the session and then store an account identifier in the session. In our &lt;code class=&quot;&quot;&gt;ApplicationController&lt;/code&gt;, we then use that identifier to look the account up and set up our &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt; value. This is the simplest implementation and works well enough for our purposes.&lt;/p&gt;

&lt;p&gt;Rails gives us some straightforward, but powerful, tools for making the stateless web feel like we are writing a stateful application. Session management is one of those tools&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. By storing a small value in the session, we suddenly have access to the current account in any controller descending from &lt;code class=&quot;&quot;&gt;ApplicationController&lt;/code&gt;. Neat!&lt;/p&gt;

&lt;h2 id=&quot;extending-this-to-actioncable-channels&quot;&gt;Extending this to ActionCable channels&lt;/h2&gt;

&lt;p&gt;As nice as sessions are, they are a construct only available inside the controllers of your applications. Since ActionCable channels are not controllers, we must find a different way to share information between our controllers and channels. Even the controversial &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html&quot;&gt;&lt;code class=&quot;&quot;&gt;ActiveSupport::CurrentAttributes&lt;/code&gt;&lt;/a&gt; tool can’t help us here because your ActionCable channel works on a separate thread from the controller serving the page that starts the channel.&lt;/p&gt;

&lt;p&gt;The behavior of decoding the session cookie belongs to the controller and is not accessible in ActionCable channels&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;. However, that does not mean that &lt;em&gt;cookies&lt;/em&gt; are inaccessible in channels, only that the session isn’t available. In fact, the Rails Guides &lt;a href=&quot;https://guides.rubyonrails.org/action%5Fcable%5Foverview.html#connection-setup&quot;&gt;give an example of using a cookie&lt;/a&gt; to identify the account for the channel. Wire this in as an encrypted cookie&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; at the same place that you set the session value:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SessionsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticated_user&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;reset_session&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticated_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;values: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authenticated_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;expires: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hour&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root_path&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Do some error handling&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;An important decision here is the &lt;code class=&quot;&quot;&gt;expires&lt;/code&gt; value. Depending on what you’re going to do with ActionCable, you might need this to be longer or shorter. If you’re using ActionCable only for short-lived actions and most of the time your customers navigate through your application via controllers, prefer a short expiry. But if you’re building something like a persistent chat service where your customers spend a long time on one page, a longer expiry is likely what you want.&lt;/p&gt;

&lt;p&gt;The trade-off between too short and too long is a trade-off between convenience and security. If you have an overly long expiry and an attacker manages to get hold of your customer’s cookie, then the attacker has a long time to perform &lt;em&gt;session hijacking&lt;/em&gt; attacks against your system and your customer. But if you have an expiry that is too short and the customer gets disconnected — whether that’s through a dropped connection, a redeploy, or any other of a number of reasons — they either must refresh the page for their web socket to reconnect or use some JavaScript to refresh their cookie from an API endpoint.&lt;/p&gt;

&lt;p&gt;Once you’ve decided upon an expiry length and set your cookie, access its value in your channel like the Rails guide shows.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationCable::Connection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;identified_by&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:current_user&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;reject_unauthorized_connection&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;&quot;&gt;identified_by&lt;/code&gt; call metaprograms three things for us:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It creates an &lt;code class=&quot;&quot;&gt;attr_accessor&lt;/code&gt; on the connection for &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt;, which we then use in the &lt;code class=&quot;&quot;&gt;connect&lt;/code&gt; callback.&lt;/li&gt;
  &lt;li&gt;It also adds a delegated method in your &lt;code class=&quot;&quot;&gt;ApplicationCable::Channel&lt;/code&gt; class that exposes the &lt;code class=&quot;&quot;&gt;Connection&lt;/code&gt;’s &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt; as an instance method on the &lt;code class=&quot;&quot;&gt;Channel&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;It sets up &lt;em&gt;connection identification&lt;/em&gt; for the connection when the browser initiates a subscription. This helps ActionCable to route broadcasts meant for a particular account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our simple implementation of &lt;code class=&quot;&quot;&gt;#connect&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Attempts to find the &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt; via the cookie that we set in the &lt;code class=&quot;&quot;&gt;SessionsController&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;If the cookie is not set or the &lt;code class=&quot;&quot;&gt;User&lt;/code&gt; does not exist, the channel rejects the connection and disconnects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once we have this setup, we can stream broadcasts in a particular channel for the &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandsChannel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Channel&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subscribed&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This simple channel now streams any messages you broadcast to the &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt;. To broadcast a message, use the &lt;code class=&quot;&quot;&gt;ActionCable::Channel.broadcast_to&lt;/code&gt; method, like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;CommandsChannel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;broadcast_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;simple-impersonation-in-controllers&quot;&gt;Simple impersonation in controllers&lt;/h2&gt;

&lt;p&gt;Now that you have your &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt; set up for both controllers and channels, it’s time to add the behavior for shadowing a user.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ShadowsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Secure this to make sure it&apos;s only accessible&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# to those who should have access&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadowed_user&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shadower_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadowed_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root_path&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Do some error handling&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shadowed_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_shadowed_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;protected&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Secure this to make sure it&apos;s only accessible&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# to those who should have access&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shadower&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_shadower&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shadower_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This simple addition retains the meaning of &lt;code class=&quot;&quot;&gt;session[:user_id]&lt;/code&gt; to mean “the account whose permissions we should use for accessing resources” and layers on a shadower, an account who is “shadowing” or “looking over the shoulder” of the other account.&lt;/p&gt;

&lt;p&gt;As an exercise,  think about how you would adjust your access policies for specific resources based on who is shadowing whom.&lt;/p&gt;

&lt;h2 id=&quot;impersonation-in-channels&quot;&gt;Impersonation in channels&lt;/h2&gt;

&lt;p&gt;Extending our shadowing system to ActionCable channels is simple, but leaves some open questions as to how to handle the feature. First, we set the cookies in the controller:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ShadowsController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadowed_user&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shadower_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadowed_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;value: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;expires: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hour&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shadower_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;value: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shadower_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;expires: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hour&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;redirect_to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root_path&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

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

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;shadowed_user&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@_shadowed_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next, we add information to &lt;code class=&quot;&quot;&gt;ApplicationCable::Connection&lt;/code&gt; about a subscription’s identification:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApplicationCable::Connection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Connection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;identified_by&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:shadower&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current_user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;reject_unauthorized_connection&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shadower&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:shadower_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice how we do not reject the connection when a shadower doesn’t exist; that’s because it’s the normal mode of operation for the app. Now there are two states for “who is viewing this session”: an account with a shadower and one without. Depending on the type of application and the type of channel that you’re writing, you now have to think about broadcasting strategies for messages within your channel.&lt;/p&gt;

&lt;h2 id=&quot;broadcasting-strategies&quot;&gt;Broadcasting strategies&lt;/h2&gt;

&lt;p&gt;When there is only a single account viewing a session at a time, it’s easy to decide the receiver for a given broadcast: the account. However, when you have two means of identifying a session, you have to decide between two different broadcasting strategies for messages&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The broadcast makes sense to show on sessions for only the account without a shadower.&lt;/li&gt;
  &lt;li&gt;The broadcast makes sense to show on sessions for an account with a shadower.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;broadcasting-to-accounts-regardless-of-shadower&quot;&gt;Broadcasting to accounts, regardless of shadower&lt;/h3&gt;

&lt;p&gt;For messages that should show up for any session involving the shadowed account (whether operated by the account owner or a shadower), you must stream messages for that account and broadcast to that account:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandsChannel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Channel&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subscribed&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As shown in the table below, messages on this stream get delivered anywhere the “current user” matches the “broadcast to,” whether or not the session involves a shadower.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Current User&lt;/th&gt;
      &lt;th&gt;Shadower&lt;/th&gt;
      &lt;th&gt;Broadcast to&lt;/th&gt;
      &lt;th&gt;Received&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;[Alice, Bob]&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;[Alice, Bob]&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This broadcasting strategy is suitable for functionality like notifications, messages, or other streams of information.&lt;/p&gt;

&lt;h3 id=&quot;broadcasting-only-to-shadowed-sessions&quot;&gt;Broadcasting only to shadowed sessions&lt;/h3&gt;

&lt;p&gt;For messages that should show up on any current-shadowed sessions, stream messages to an identifier that uses both accounts by passing them as an array:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandsChannel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Channel&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subscribed&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This channel streams messages only where &lt;code class=&quot;&quot;&gt;shadower&lt;/code&gt; currently shadows &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt;, as shown below. Note that if &lt;code class=&quot;&quot;&gt;shadower&lt;/code&gt; is nil, it’s not equivalent to &lt;code class=&quot;&quot;&gt;stream_for current_user&lt;/code&gt; because the identifier looks different.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Current User&lt;/th&gt;
      &lt;th&gt;Shadower&lt;/th&gt;
      &lt;th&gt;Broadcast to&lt;/th&gt;
      &lt;th&gt;Received&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;[Alice, Bob]&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;[Alice, Bob]&lt;/td&gt;
      &lt;td&gt;x&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This broadcasting strategy is suitable for functionality like transactional actions where the message governs the action to take after a command.&lt;/p&gt;

&lt;h3 id=&quot;mixing-and-matching-streams&quot;&gt;Mixing and matching streams&lt;/h3&gt;

&lt;p&gt;If you have a general channel like, in our example, a &lt;code class=&quot;&quot;&gt;CommandsChannel&lt;/code&gt; that’s accessible for both the shadowed account and the shadower, you can stream messages from multiple sources:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandsChannel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Channel&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subscribed&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This channel streams messages that match &lt;em&gt;either&lt;/em&gt; of the preceding patterns, as shown in the following table.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Current User&lt;/th&gt;
      &lt;th&gt;Shadower&lt;/th&gt;
      &lt;th&gt;Broadcast to&lt;/th&gt;
      &lt;th&gt;Received&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;nil&lt;/td&gt;
      &lt;td&gt;[Alice, Bob]&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;x&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alice&lt;/td&gt;
      &lt;td&gt;Bob&lt;/td&gt;
      &lt;td&gt;[Alice, Bob]&lt;/td&gt;
      &lt;td&gt;x&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;If you use the pattern shown here, you might prefer this construction, where it subscribes to a shadowed session if there is a shadower, otherwise subscribing as the current user:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CommandChannel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationCable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Channel&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subscribed&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shadower&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;compact&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The semantics of issuing a broadcast get a little tricky here. As an exercise, think of how you would write an easy-to-use interface for broadcasting all of these types of strategies without needing to litter your codebase with that logic.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Because ActionCable does not, necessarily, have access to the session from the browser, you have to replicate the behavior for tracking the current user and shadower in the encrypted (or signed) cookie store. There are security implications with your decision, so be aware when you’re selecting an expiry. Tracking a session identifier that you control outside the cookie store is also a good idea; keep in mind the &lt;a href=&quot;https://guides.rubyonrails.org/security.html#sessions&quot;&gt;sessions section of the Rails security guide&lt;/a&gt; for tips on securely handling cookies.&lt;/p&gt;

&lt;p&gt;Once you have the shadowing system set up, you must think about the broadcasting strategy for each channel that you implement. Depending on the type of information you broadcast to the channel, you might want to make sure to only broadcast to sessions by the same “identity pair” so that messages don’t intermingle between an account and a shadowed account; these types of messages are for transactional changes. Or, perhaps, you have something like a notifications channel that you want to send to anyone viewing an account. Your choice of strategy can make either of those things happen.&lt;/p&gt;

&lt;p&gt;Do you allow shadowing in your application? If so, who do you allow to shadow? Do customers shadow each other, or is it only for support?&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;hr&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;I truly detest the term “user” to mean a person who is currently operating our software. Only in drug addiction, abusive relationships, and software development do we find it acceptable to call someone a “user”. But since this is the established pattern, we bend to societal pressure for clarity. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;For more information about sessions, Justin Weiss has &lt;a href=&quot;https://www.justinweiss.com/articles/how-rails-sessions-work/&quot;&gt;a nice article about Rails sessions&lt;/a&gt; and how they interoperate with cookies by default. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;Incidentally, if you use the built-in cookie store from Rails, you can access &lt;code class=&quot;&quot;&gt;session[:user_id]&lt;/code&gt; using &lt;code class=&quot;&quot;&gt;cookies.encrypted[Rails.application.config.session_options.fetch(:key, &apos;_session&apos;)]&lt;/code&gt;. I don’t recommend doing this because there’s no guarantee that it will continue to work in future versions of Rails outside of being briefly mentioned in the ActionCable overview. &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;You can also use &lt;code class=&quot;&quot;&gt;cookies.signed&lt;/code&gt; here instead of &lt;code class=&quot;&quot;&gt;cookies.encrypted&lt;/code&gt;. I recommend an encrypted cookie instead of a signed cookie because there’s no reason you should expose an internal identifier for your models. &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;In actuality, there are four different configurations: &lt;code class=&quot;&quot;&gt;current_user&lt;/code&gt;, &lt;code class=&quot;&quot;&gt;[current_user, shadower]&lt;/code&gt;, &lt;code class=&quot;&quot;&gt;shadower&lt;/code&gt;, and &lt;code class=&quot;&quot;&gt;[shadower, current_user]&lt;/code&gt;, but the last two don’t have cases where I think it makes sense to use them if you’re using the former two. “Shadower-only” streams don’t do what you’d expect them to; they get delivered to other sessions of the shadower that might still be open without shadowing anyone. &lt;code class=&quot;&quot;&gt;[shadower, current_user]&lt;/code&gt; reads more confusingly to me than &lt;code class=&quot;&quot;&gt;[current_user, shadower]&lt;/code&gt; and has the same semantics, as long as you’re consistent. &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://michaeljherold.com/images/martino-pietropoli-pirWeToS2mA-unsplash.jpg" /><media:content medium="image" url="https://michaeljherold.com/images/martino-pietropoli-pirWeToS2mA-unsplash.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>