<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nick</title>
    <description>The latest articles on DEV Community by Nick (@wasabigeek).</description>
    <link>https://dev.to/wasabigeek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F533359%2Fc40efc42-cfa4-40c5-8c22-f99598337034.png</url>
      <title>DEV Community: Nick</title>
      <link>https://dev.to/wasabigeek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wasabigeek"/>
    <language>en</language>
    <item>
      <title>Reading a Ruby gem with VSCode</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Tue, 15 Feb 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/wasabigeek/reading-a-ruby-gem-with-vscode-1id7</link>
      <guid>https://dev.to/wasabigeek/reading-a-ruby-gem-with-vscode-1id7</guid>
      <description>&lt;p&gt;Whether out of curiosity or trying to understand a method for debugging, it's helpful to know how to dig into a Ruby gem. Recently I was curious over how &lt;a href="https://github.com/freerange/mocha/"&gt;mocha&lt;/a&gt;, a mocking library for minitest, allowed stubbing on  &lt;code&gt;any_instance&lt;/code&gt; of a Class (as opposed to injecting a stubbed object), for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_stubbing_an_instance_method_on_all_instances_of_a_class&lt;/span&gt;
  &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stubs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stubbed_name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="n"&gt;assert_equal&lt;/span&gt; &lt;span class="s1"&gt;'stubbed_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Following this are steps you can try when investigating a gem. I'd love to say these were the exact steps I took while investigating &lt;code&gt;any_instance&lt;/code&gt;, but truth is some steps I discovered only while writing this post 😅. The steps are specific to using VSCode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open the Source Code
&lt;/h2&gt;

&lt;p&gt;Assuming the gem is installed, &lt;code&gt;gem open &amp;lt;gemname&amp;gt;&lt;/code&gt; (or &lt;code&gt;bundle open &amp;lt;gemname&amp;gt;&lt;/code&gt; if installed in a project via Bundler) will open it's source code with the command set in the &lt;code&gt;$EDITOR&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;For example &lt;code&gt;EDITOR=code bundle open mocha&lt;/code&gt; from my project opens the gem in VSCode:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3LmPR41w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/wasabigeek/blog/main/content/blog/reading-a-ruby-gem-with-vscode/Mocha%2520opened%2520in%2520VSCode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3LmPR41w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/wasabigeek/blog/main/content/blog/reading-a-ruby-gem-with-vscode/Mocha%2520opened%2520in%2520VSCode.png" alt="Mocha opened in VSCode.png" width="880" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the gem isn't installed, you could also look up the source code online (&lt;a href="https://rubygems.org/"&gt;rubygems.org&lt;/a&gt;) and if the code is on GitHub, there's a handy &lt;a href="https://twitter.com/github/status/1425505817827151872?s=21"&gt;shortcut&lt;/a&gt; to open it in a browser version of VSCode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find your Target
&lt;/h2&gt;

&lt;p&gt;One of the easiest ways to find the method definition is to use &lt;code&gt;#source_location&lt;/code&gt;. Fire up an irb console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mocha/minitest'&lt;/span&gt;
&lt;span class="no"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:any_instance&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;source_location&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ["/.../ruby/3.0.3/gems/mocha-1.13.0/lib/mocha/class_methods.rb", 45]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case that doesn't work, since we have the source code open, we can make use of VSCode's search:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CmiVMJ-A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/wasabigeek/blog/main/content/blog/reading-a-ruby-gem-with-vscode/Searching%2520Mocha%2520in%2520VSCode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CmiVMJ-A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/wasabigeek/blog/main/content/blog/reading-a-ruby-gem-with-vscode/Searching%2520Mocha%2520in%2520VSCode.png" alt="Searching Mocha in VSCode.png" width="880" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some ideas to try if the results list is really long:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prefixing the search term with the declaration syntax (e.g. &lt;code&gt;class &amp;lt;ClassName&amp;gt;&lt;/code&gt; for a class, &lt;code&gt;def &amp;lt;method_name&amp;gt;&lt;/code&gt; for an instance method) generally works well, but not always, given the many ways to metaprogram in Ruby. For example, it's not obvious that &lt;code&gt;any_instance&lt;/code&gt; would have been defined as an instance method.&lt;/li&gt;
&lt;li&gt;"&lt;strong&gt;Match Whole Word&lt;/strong&gt;" (ab) helps exclude classes / methods with the search term as a substring (e.g. it would have excluded &lt;code&gt;any_instance_method&lt;/code&gt; and  &lt;code&gt;mock_impersonating_any_instance_of&lt;/code&gt; in the results above)&lt;/li&gt;
&lt;li&gt;"&lt;strong&gt;files to include&lt;/strong&gt;" knowing the structure of most gems (see later section), we can usually restrict  to the &lt;code&gt;lib/&lt;/code&gt; directory, which will exclude test files, bin scripts etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trace the Callers
&lt;/h2&gt;

&lt;p&gt;While we found the method, it didn't answer our initial question, which was how &lt;code&gt;any_instance&lt;/code&gt; becomes callable on any class. We could probably try a "top-down" or "bottom-up" search here.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;"bottom-up" search&lt;/strong&gt;, since this is plain Ruby as opposed to Rails, we could try to follow the &lt;code&gt;require&lt;/code&gt; trail. But if we have an extension like &lt;a href="https://marketplace.visualstudio.com/items?itemName=castwide.solargraph"&gt;solargraph&lt;/a&gt;, a simpler way is to make use of VSCode's &lt;strong&gt;Go to References&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hmImR9zM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/wasabigeek/blog/main/content/blog/reading-a-ruby-gem-with-vscode/VSCode%2520Go%2520To%2520References%2520in%2520Mocha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hmImR9zM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/wasabigeek/blog/main/content/blog/reading-a-ruby-gem-with-vscode/VSCode%2520Go%2520To%2520References%2520in%2520Mocha.png" alt="VSCode Go To References in Mocha.png" width="880" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above, we find that &lt;code&gt;Mocha::ClassMethods&lt;/code&gt; is included into Class in &lt;code&gt;Mocha::API&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:include&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ClassMethods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can continue doing the same approach, but at some point the "search tree" got quite unwieldy so I switched to a more &lt;strong&gt;"top-down" approach&lt;/strong&gt;, checking the README to see how the gem was &lt;code&gt;require&lt;/code&gt;-d.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know the Structure of a Gem
&lt;/h2&gt;

&lt;p&gt;A quick detour on how a gem is structured - &lt;code&gt;lib/&lt;/code&gt; generally contains the gem's core logic. There you'll find a ruby file and a folder corresponding to the name of the gem. For example, in &lt;code&gt;mocha&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lib/
└── mocha.rb
└── mocha/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ruby file (e.g. &lt;code&gt;lib/mocha.rb&lt;/code&gt;) generally contains most of the gem's bootstrapping logic, as this is the file loaded when requiring the gem (i.e. &lt;code&gt;require 'mocha'&lt;/code&gt; would load &lt;code&gt;lib/mocha.rb&lt;/code&gt;). So typically we'd start our "top-down" search here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mocha&lt;/code&gt; is slightly different - it supports multiple test libraries, and in the &lt;a href="https://github.com/freerange/mocha/#minitest-1"&gt;README&lt;/a&gt; we are instructed to require a different file depending on our test library. For example, for &lt;code&gt;minitest&lt;/code&gt; we'd do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'minitest/unit'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'mocha/minitest'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So in this case we'd start our search from &lt;code&gt;lib/mocha/minitest.rb&lt;/code&gt;. There on it's pretty straightforward Ruby, leading us back to &lt;code&gt;Mocha::API&lt;/code&gt; (albeit with knowledge of how it gets required!).&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.justinweiss.com/articles/how-to-dispel-ruby-magic-and-understand-your-gems"&gt;How to Dispel Ruby Magic and Understand Your Gems (Justin Weiss)&lt;/a&gt; - an inspiration for much of this&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://guides.rubygems.org/what-is-a-gem/"&gt;Structure of a Gem (RubyGems)&lt;/a&gt; - more details on how a gem is structured, along with a quick start so you can build your own gem!&lt;/li&gt;
&lt;li&gt;VSCode docs on &lt;a href="https://code.visualstudio.com/docs/editor/codebasics#_search-across-files"&gt;searching across files&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Don’t give up on your Rails Generators!</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Sun, 21 Mar 2021 10:17:09 +0000</pubDate>
      <link>https://dev.to/wasabigeek/don-t-give-up-on-your-rails-generators-2h90</link>
      <guid>https://dev.to/wasabigeek/don-t-give-up-on-your-rails-generators-2h90</guid>
      <description>&lt;p&gt;From speaking with Rails devs, many quickly stop using the built-in generators (save for a few like &lt;code&gt;model&lt;/code&gt; or &lt;code&gt;migration&lt;/code&gt;), the common reason being that the generated files no longer suit the project - so it’s easier to start from scratch rather generate and then modify them. What a pity! To paraphrase DHH, generators can help &lt;strong&gt;improve team productivity&lt;/strong&gt; and &lt;strong&gt;encourage consistency&lt;/strong&gt; - we just need to tweak them.&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--v6DxttUX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/975876868455809024/eK7mDppU_normal.jpg" alt="DHH profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        DHH
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @dhh
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      A good way to get more people to do the right thing: Generate the pattern by default via built-in generators. Like we do with tests etc.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      00:38 AM - 05 Mar 2012
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=176466771698659328" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=176466771698659328" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=176466771698659328" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;In this post, I look at one approach for customising generators, based on one we customised recently at work: the rake task generator.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR on Overriding a Generator
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Copy over the Generator classes to &lt;code&gt;/lib/generators/...&lt;/code&gt; and modify&lt;/li&gt;
&lt;li&gt;Copy over the template file to &lt;code&gt;/lib/templates/...&lt;/code&gt; and modify&lt;/li&gt;
&lt;li&gt;Bonus: Use &lt;code&gt;hook_for&lt;/code&gt; to generate a test file if the generator doesn’t do it automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;At work, we use rake tasks quite a fair bit, both for cron-like jobs and one-off migration or support tasks. Rails has a built-in &lt;a href="https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/task/USAGE"&gt;task generator&lt;/a&gt;, with the form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails g task file_name action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s never quite worked for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We like to keep one task “action” per file, with the file named the same as the action - this makes it easier to grep for the task. The built-in generator requires separate &lt;code&gt;file_name&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; arguments, which is a little unnecessary.&lt;/li&gt;
&lt;li&gt;We sometimes namespace tasks e.g. with a “onetime” namespace for one-off tasks, but the built-in generator ignores paths in the &lt;code&gt;file_name&lt;/code&gt; and also uses the &lt;code&gt;file_name&lt;/code&gt; as the namespace.&lt;/li&gt;
&lt;li&gt;We write tests for our rake tasks, but the generator doesn’t create test files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s address these! There are different approaches available, but in this case it made sense to override the generator entirely - the team wasn’t using it, so we weren’t breaking established conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding the Task Generator
&lt;/h2&gt;

&lt;p&gt;Rails searches for generators in a &lt;a href="https://guides.rubyonrails.org/generators.html#generators-lookup"&gt;few paths&lt;/a&gt;, so as long as we place our file in &lt;code&gt;/lib/generators/task/task_generator.rb&lt;/code&gt;, Rails will load our custom file instead.&lt;/p&gt;

&lt;p&gt;We’ll copy over the original task generator to use as a base. Most of the default generators can be found in &lt;code&gt;railties/lib/rails/generators/rails&lt;/code&gt;, the one we’re after is &lt;a href="https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/task/task_generator.rb"&gt;here&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rails&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Generators&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskGenerator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;NamedBase&lt;/span&gt; &lt;span class="c1"&gt;# :nodoc:&lt;/span&gt;
      &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="ss"&gt;:actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;banner: &lt;/span&gt;&lt;span class="s2"&gt;"action action"&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_task_files&lt;/span&gt;
        &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="s2"&gt;"task.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lib/tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.rake"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy it over to &lt;code&gt;/lib/rails/generators/task/task_generator.rb&lt;/code&gt;. As a simple sanity check to see if we've overridden the class, let’s make the generator spit out a hardcoded file name and run it. Change this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="s2"&gt;"task.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lib/tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hardcoded_file_name.rake"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails g task path/to/my_task
&lt;span class="c"&gt;# create  lib/tasks/hardcoded_file_name.rake&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works! Let's revert the hardcoding and leave the rest of the class as-is for now. We'll be back, promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding the Generator Template
&lt;/h2&gt;

&lt;p&gt;We'll going to be customising the template as well, so let's override that. When invoked, generators search for templates in a different set of paths from the invoker (I explored this more in another &lt;a href="https://wasabigeek.com/blog/customising-generator-templates-in-rails-gems-too/"&gt;post&lt;/a&gt;) - we'll be wanting to place our template at &lt;code&gt;/lib/templates/rails/task/task.rb.tt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Copy the &lt;a href="https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/task/templates/task.rb.tt"&gt;original&lt;/a&gt; and modify it as such (the lack of indentation is intentional):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&amp;lt;% class_path.each_with_index &lt;span class="k"&gt;do&lt;/span&gt; |path_fragment, index| -%&amp;gt;
&amp;lt;%&lt;span class="o"&gt;=&lt;/span&gt; indent&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"namespace :#{path_fragment}"&lt;/span&gt;, index &lt;span class="k"&gt;*&lt;/span&gt; 2&lt;span class="o"&gt;)&lt;/span&gt; %&amp;gt; &lt;span class="k"&gt;do&lt;/span&gt;
&amp;lt;% end -%&amp;gt;
&amp;lt;% content &lt;span class="o"&gt;=&lt;/span&gt; capture &lt;span class="k"&gt;do&lt;/span&gt; -%&amp;gt;
desc &lt;span class="s2"&gt;"TODO"&lt;/span&gt;
task &amp;lt;%&lt;span class="o"&gt;=&lt;/span&gt; file_name %&amp;gt;: :environment &lt;span class="k"&gt;do
&lt;/span&gt;end
&amp;lt;% end -%&amp;gt;
&amp;lt;%&lt;span class="o"&gt;=&lt;/span&gt; indent&lt;span class="o"&gt;(&lt;/span&gt;content, class_path.size &lt;span class="k"&gt;*&lt;/span&gt; 2&lt;span class="o"&gt;)&lt;/span&gt; %&amp;gt;
&amp;lt;% &lt;span class="o"&gt;(&lt;/span&gt;0...class_path.size&lt;span class="o"&gt;)&lt;/span&gt;.reverse_each &lt;span class="k"&gt;do&lt;/span&gt; |index| -%&amp;gt;
&amp;lt;%&lt;span class="o"&gt;=&lt;/span&gt; indent&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'end'&lt;/span&gt;, index &lt;span class="k"&gt;*&lt;/span&gt; 2&lt;span class="o"&gt;)&lt;/span&gt; %&amp;gt;
&amp;lt;% end -%&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's breakdown the changes. First, we'll sort our name-spacing, including the indentation (note that &lt;code&gt;indent&lt;/code&gt; is actually a private method on &lt;code&gt;Rails::Generators::Base&lt;/code&gt;, so you won't be able to use it in your regular view templates):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;% class_path.each_with_index do |path_fragment, index| -%&amp;gt;
&amp;lt;%= indent("namespace :#{path_fragment}", index * 2) %&amp;gt; do
&amp;lt;% end -%&amp;gt;
...
&amp;lt;% (0...class_path.size).reverse_each do |index| -%&amp;gt;
&amp;lt;%= indent('end', index * 2) %&amp;gt;
&amp;lt;% end -%&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the previous command &lt;code&gt;bin/rails g task path/to/my_task&lt;/code&gt;, the above results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace :path do
  namespace :to do
    ...
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lets sort the task itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
&amp;lt;% content = capture do -%&amp;gt;
desc "TODO"
task &amp;lt;%= file_name %&amp;gt;: :environment do
end
&amp;lt;% end -%&amp;gt;
&amp;lt;%= indent(content, class_path.size * 2) %&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we indent the task description strings by first assigning it to a variable, &lt;code&gt;content&lt;/code&gt; via the &lt;code&gt;capture&lt;/code&gt; helper. Then, we indent and insert it into the file. So running the command again will give us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace :path do
  namespace :to do
    desc "TODO"
    task my_task: :environment do
    end

  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost there! Our file is still being created as &lt;code&gt;/lib/tasks/my_task.rake&lt;/code&gt; though, instead of &lt;code&gt;/lib/tasks/path/to/my_task.rake&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the Generated Path
&lt;/h2&gt;

&lt;p&gt;Let's fix that. Here's the diff for our generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;4,5d3
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt;       argument :actions, type: :array, default: [], banner: "action action"
&amp;lt;
&lt;/span&gt;&lt;span class="p"&gt;7c5
&lt;/span&gt;&lt;span class="gd"&gt;&amp;lt;         template "task.rb", File.join("lib/tasks", "#{file_name}.rake")
&lt;/span&gt;&lt;span class="p"&gt;---
&lt;/span&gt;&lt;span class="gi"&gt;&amp;gt;         template "task.rb", File.join("lib/tasks", class_path, "#{file_name}.rake")
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've removed the unnecessary &lt;code&gt;actions&lt;/code&gt; argument and added the &lt;code&gt;class_path&lt;/code&gt; to the generated file's path. Running the command again nests the file nicely now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooking in a Test file
&lt;/h2&gt;

&lt;p&gt;“Should you test rake tasks?”... is not a discussion for this article, let’s assume you do! We can customise our generator to create a test file.&lt;/p&gt;

&lt;p&gt;The method we’re looking for is &lt;a href="https://api.rubyonrails.org/v6.1.3/classes/Rails/Generators/Base.html#method-c-hook_for"&gt;hook_for&lt;/a&gt;, which will cause the generator to attempt to also invoke other generators. Add the following DSL to your generator class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;hook_for&lt;/span&gt; &lt;span class="ss"&gt;:test_framework&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short, this attempts to invoke &lt;code&gt;Rspec::Generators::TaskGenerator&lt;/code&gt;, which... doesn’t exist yet. Let’s resolve that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding RSpec Generator and Template
&lt;/h2&gt;

&lt;p&gt;RSpec doesn’t have a custom generator for tasks, but the steps for overriding it are more or less the same. Let’s start by creating the TaskGenerator class in &lt;code&gt;/lib/generators/rspec/task/task_generator.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# frozen_string_literal: true&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'generators/rspec'&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Rspec&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Generators&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TaskGenerator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt; &lt;span class="c1"&gt;# :nodoc:&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_task_specs&lt;/span&gt;
        &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="s2"&gt;"task_spec.rb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"spec/tasks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_rake_spec.rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to create the corresponding template in &lt;code&gt;/lib/templates/rspec/task/task_spec.rb.tt&lt;/code&gt; (note: we can definitely DRY this out more, you might be able to draw some inspiration from these examples by &lt;a href="https://thoughtbot.com/blog/test-rake-tasks-like-a-boss"&gt;Thoughtbot&lt;/a&gt; and &lt;a href="https://blog.10pines.com/2019/01/14/testing-rake-tasks/"&gt;10Pines&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rake'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;%= class_path.join('&lt;/span&gt;&lt;span class="ss"&gt;:') %&amp;gt;'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;':&amp;lt;%= file_name %&amp;gt;'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:all&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
      &lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rake_require&lt;/span&gt; &lt;span class="s1"&gt;'tasks/&amp;lt;%= File.join(class_path, file_name) %&amp;gt;'&lt;/span&gt;
      &lt;span class="vi"&gt;@task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rake&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;%= class_path.concat([file_name]).join('&lt;/span&gt;&lt;span class="ss"&gt;:') %&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'works'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that done, running the generator command now creates the files just as we wanted. Neat!&lt;/p&gt;

&lt;h2&gt;
  
  
  Go forth and generate!
&lt;/h2&gt;

&lt;p&gt;I hope this has given you some inspiration to give generators a second chance - with a little tweaking, they can be really useful in saving time for you and your team. What ideas do you have for improving your generators?&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Javascript Architectures for Rails Apps</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Tue, 09 Feb 2021 14:50:05 +0000</pubDate>
      <link>https://dev.to/wasabigeek/javascript-frontends-for-rails-apps-38ag</link>
      <guid>https://dev.to/wasabigeek/javascript-frontends-for-rails-apps-38ag</guid>
      <description>&lt;p&gt;Javascript has become an integral part of modern websites, allowing developers to create interactive, app-like experiences. While the ecosystem has seen some consolidation over the years, there are still quite a few choices you could make. Let's talk about two typical approaches for Rails: "multi-page" and single-page apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Multi-Page" App
&lt;/h2&gt;

&lt;p&gt;The web was originally envisioned as a collection of documents linked together. Each time you clicked a link, it would load a new document. Rails was built with this in mind, so if you've done any beginner tutorials, this should be the approach you'd be most familiar with.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk9qe56yymp0rrchayz6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk9qe56yymp0rrchayz6a.png" alt="Multi-Page Rails"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this setup, &lt;strong&gt;routing is handled by the backend&lt;/strong&gt; - Rails decides which pages to serve based on the requested URL e.g. in the above example, we might have set &lt;code&gt;/videos/cats&lt;/code&gt; to route to a &lt;code&gt;VideosController&lt;/code&gt;, which will render a &lt;code&gt;videos/index.html.erb&lt;/code&gt; view.&lt;/p&gt;

&lt;p&gt;Javascript plays two main roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sprinkles of &lt;strong&gt;on-page interactivity&lt;/strong&gt; e.g. hiding or showing in accordions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;partial page replacement&lt;/strong&gt;, mainly via a technique known as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX/Getting_Started" rel="noopener noreferrer"&gt;AJAX&lt;/a&gt;, to make interactions feel speedier and more app-like
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fxec4hozmg31hkm44moww.png" alt="AJAX"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example libraries that can work in this paradigm include JQuery, Stimulus, React or VueJS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Single Page Apps (SPAs)
&lt;/h2&gt;

&lt;p&gt;SPAs are a pretty radical departure from the multi-page paradigm. They essentially take partial page replacement to the extreme - the Javascript framework handles almost everything. Let's look at an example setup:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fo2he29usyo5w9s9zat1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fo2he29usyo5w9s9zat1u.png" alt="Single Page Apps with Rails"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above example, we host our frontend on a separate server. When someone enters a URL, our frontend server will return a single page, and the Javascript takes over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the framework handles and simulates "routing" as well. The TLDR; is that it decides what to render for &lt;code&gt;/videos/cats&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;it makes the required AJAX calls to our Rails backend (e.g. &lt;code&gt;/api/videos/&lt;/code&gt;) based on what's being rendered, retrieving data as JSON. This is somewhat similar to our Rails controllers extracting data from a model.&lt;/li&gt;
&lt;li&gt;based on the JSON data, the framework will create, replace or delete HTML elements on the fly, making for a very fluid experience. A close parallel in Rails would be the rendering of data in views.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the most part, Rails is "&lt;a href="https://guides.rubyonrails.org/api_app.html" rel="noopener noreferrer"&gt;API-only&lt;/a&gt;" - we may not even need the &lt;a href="https://dev.to/wasabigeek/asset-compilation-in-rails-51b5"&gt;Asset Pipeline&lt;/a&gt;! This might sound antithetical what Rails is ("everything you need to build fantastic applications"), but as with anything in technology, there are tradeoffs that make this worth considering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The greater separation of frontend and backend in SPA setups lead to increased complexity, but also allows for a separate frontend and backend team that move individually.&lt;/li&gt;
&lt;li&gt;SPA frameworks offer a more "declarative" programming paradigm, making it easier to create complex interactions, at the cost of a steeper learning curve.&lt;/li&gt;
&lt;li&gt;SPA frameworks generally enable more fluid web-apps as you'd potentially make smaller JSON-only requests and never need to load full pages (at least after the first), though Rails has a few tricks up it's sleeve to close this gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples of SPA frameworks include React (with some companion libraries), VueJS, EmberJS and AngularJS. React and VueJS are repeated here because they can operate in both paradigms: as "sprinkled" Javascript, or as a full framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  So, what to choose?
&lt;/h2&gt;

&lt;p&gt;If you're new to Rails and web development, I'd humbly suggest to lean toward a "multi-page" setup. You'd have less complexity to maintain and a gentler learning curve. SPAs can come later (in fact, you might &lt;a href="https://jamstack.org/" rel="noopener noreferrer"&gt;not even need&lt;/a&gt; to host your own backend to work with an SPA).&lt;/p&gt;

&lt;p&gt;We'll take a look at different options for "multi-page" apps in the next post. You can follow me on &lt;a href="https://twitter.com/wasabigeek" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; to be the first to know when it's ready.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Which CSS framework - Bootstrap or Tailwind?</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Thu, 04 Feb 2021 19:40:04 +0000</pubDate>
      <link>https://dev.to/wasabigeek/which-css-framework-bootstrap-or-tailwind-2k0g</link>
      <guid>https://dev.to/wasabigeek/which-css-framework-bootstrap-or-tailwind-2k0g</guid>
      <description>&lt;p&gt;If you're new to web development, you may have heard these two CSS frameworks being name-dropped. TailwindCSS, in particular, has &lt;a href="https://explodingtopics.com/topic/tailwind-css" rel="noopener noreferrer"&gt;caught on&lt;/a&gt; recently, but you'd likely have also heard about the venerable Bootstrap. What should you pick?&lt;/p&gt;

&lt;p&gt;Both take different approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrap is what I'd call a &lt;strong&gt;Component-based Framework&lt;/strong&gt;.  It's designed to help developers quickly &lt;em&gt;bootstrap&lt;/em&gt; a typical website or app and so comes with a set of pre-made components, along with some utilities for spacing etc. Other similar frameworks include &lt;a href="https://bulma.io/" rel="noopener noreferrer"&gt;Bulma&lt;/a&gt; and &lt;a href="https://get.foundation/" rel="noopener noreferrer"&gt;Foundation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;TailwindCSS is a &lt;strong&gt;Utility-first Framework&lt;/strong&gt;. In contrast to Bootstrap, which provides a few utilities, Tailwind is &lt;em&gt;all&lt;/em&gt; utilities. The core framework has no pre-made components, so you'll need to build your own.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Learning by Example
&lt;/h1&gt;

&lt;p&gt;An example will help crystallise the differences - let's say we want to build a photo gallery with a “Load More” button:&lt;/p&gt;

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

&lt;p&gt;We also want it to be responsive, so on the tinier screens of our smartphones, the gallery morphs into a column layout:&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Bootstrap
&lt;/h2&gt;

&lt;p&gt;Here's how you could achieve the layout with Bootstrap (&lt;a href="https://codepen.io/wasabigeek/pen/wvzNLXd" rel="noopener noreferrer"&gt;codepen&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-12 col-md-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://via.placeholder.com/300x200.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Card Text.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- repeat 2x... --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Load More&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the CSS classes, you'll probably have already noticed Bootstrap's built-in components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;container&lt;/code&gt;, &lt;code&gt;row&lt;/code&gt; and &lt;code&gt;col-*&lt;/code&gt; classes are for layout

&lt;ul&gt;
&lt;li&gt;Bootstrap's layout is based on a grid of 12 units, so &lt;code&gt;col-12&lt;/code&gt; is telling Bootstrap that this column should take up the full width of it's &lt;code&gt;container&lt;/code&gt;. This is why if you resize the browser window smaller, it will stretch to the full width.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;col-md-4&lt;/code&gt; says, on a medium-sized (md) screen and above, take only 4 units. This is why in a typical desktop view, it will show (12 / 4 =) 3 columns.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The &lt;code&gt;&lt;a href="https://getbootstrap.com/docs/4.5/components/card/" rel="noopener noreferrer"&gt;card&lt;/a&gt;&lt;/code&gt; and &lt;code&gt;&lt;a href="https://getbootstrap.com/docs/4.5/components/buttons/" rel="noopener noreferrer"&gt;btn&lt;/a&gt;&lt;/code&gt; (button) components are hopefully quite self-explanatory.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;We also used some of Bootstrap's utility classes, which map pretty close to the actual CSS being applied:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mb-4&lt;/code&gt; applies &lt;code&gt;margin-bottom: 4rem&lt;/code&gt; by default.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text-center&lt;/code&gt; applies &lt;code&gt;text-align: center!important&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Observations:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Because we were able to use Bootstrap's own components, there was little we had to write, and very little CSS we had to actually know. Some built-in components will also handle Javascript interactions for you, such as &lt;a href="https://getbootstrap.com/docs/4.6/components/collapse/#accordion-example" rel="noopener noreferrer"&gt;accordions&lt;/a&gt; or one of my favourites, &lt;a href="https://getbootstrap.com/docs/4.6/components/scrollspy/" rel="noopener noreferrer"&gt;scrollspy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In keeping with the framework's conventions, we'd probably start grouping and naming our CSS classes by components too. e.g. I might add a &lt;code&gt;chat-message&lt;/code&gt; class in my CSS/SASS stylesheet, with a modifier like &lt;code&gt;chat-message-primary&lt;/code&gt; to set the colour. (If it sounds tricky deciding how to name classes, it is! People have come up with whole methodologies like &lt;a href="https://css-tricks.com/bem-101/" rel="noopener noreferrer"&gt;BEM&lt;/a&gt; in an attempt to solve this.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  In the Wild
&lt;/h3&gt;

&lt;p&gt;Bootstrap has an &lt;a href="https://getbootstrap.com/docs/4.5/examples/" rel="noopener noreferrer"&gt;examples&lt;/a&gt; page showing some basic layouts, and you can take a look at some &lt;a href="https://expo.getbootstrap.com/" rel="noopener noreferrer"&gt;curated examples&lt;/a&gt; in production and their &lt;a href="https://themes.getbootstrap.com/" rel="noopener noreferrer"&gt;official themes&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  TailwindCSS
&lt;/h2&gt;

&lt;p&gt;A similar layout built with Tailwind (&lt;a href="https://codepen.io/wasabigeek/pen/abmMvgg" rel="noopener noreferrer"&gt;codepen&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col md:flex-row md:justify-center md:space-x-4 items-center space-y-4 md:space-y-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"border border-gray-200 rounded"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://via.placeholder.com/300x200.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; 
      &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Card Text.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- repeat 2x... --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-center mt-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Load More
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While Bootstrap provided some utility classes, the Tailwind core &lt;em&gt;only&lt;/em&gt; has utility classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for layout, we are making use of &lt;a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/" rel="noopener noreferrer"&gt;flexbox&lt;/a&gt; via classes like &lt;code&gt;flex&lt;/code&gt; and &lt;code&gt;flex-col&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;responsiveness is achieved using the breakpoint prefixes, e.g. &lt;code&gt;md:flex-row&lt;/code&gt; changes the flex-direction to "row" only on medium-sized ("md") screens or larger&lt;/li&gt;
&lt;li&gt;for the "components" like the button, we are almost writing the CSS using the utility classes e.g.:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;px-4&lt;/code&gt; adds a padding of 1rem (it has a different default scale from Bootstrap) to the left and right (the x-axis).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bg-blue-500&lt;/code&gt; sets the background to a "medium" blue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hover:bg-blue-600&lt;/code&gt; darkens the background colour when someone hovers over it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Observations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Using Tailwind isn't too far off from writing plain CSS, which isn't necessarily a bad thing. If you are working with a design that deviates from the norm, a framework like Bootstrap doesn't help much - you'd end up writing most of your own CSS. With Tailwind, that becomes easier because of the utility classes.&lt;/li&gt;
&lt;li&gt;We're able to do all our styling without leaving our HTML, just by adding utility classes. So instead of structuring our components via CSS class names, we'd be relying on extracting components via our frontend framework. In Rails we might break up the code into partials:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;%# gallery.html.erb %&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-center mt-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="s1"&gt;'Load More'&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;%# _button.html.erb %&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a whole debate about whether this is a good thing, you can read Adam Watham's (the creator of Tailwind) argument &lt;a href="https://adamwathan.me/css-utility-classes-and-separation-of-concerns/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the Wild
&lt;/h3&gt;

&lt;p&gt;Adam Watham's &lt;a href="https://www.youtube.com/channel/UCy1H38XrN7hi7wHSClfXPqQ" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt; channel is a great resource for learning best practices on Tailwind. He also has a set of (mostly paid) &lt;a href="https://tailwindui.com/" rel="noopener noreferrer"&gt;pre-built Tailwind components&lt;/a&gt; that you can use as a base. It's definitely in the spotlight now - for Rails devs, there's even an official &lt;a href="https://github.com/rails/tailwindcss-rails" rel="noopener noreferrer"&gt;tailwind-rails&lt;/a&gt; gem.&lt;/p&gt;




&lt;h1&gt;
  
  
  So, which to pick?
&lt;/h1&gt;

&lt;p&gt;My personal opinion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're a new &lt;strong&gt;backend&lt;/strong&gt; or &lt;strong&gt;"full-stack"&lt;/strong&gt; developer (e.g. Rails developers), I'd highly recommend trying Bootstrap first - make use of their built-in components, and only pick up CSS as needed when tweaking the styles or starting your own components. That way, you'd have more time to spend on learning the other parts of your chosen stack.&lt;/li&gt;
&lt;li&gt; If you're set on the &lt;strong&gt;frontend&lt;/strong&gt; path or are more seasoned with CSS, then Tailwind (or maybe even no framework) is a good choice, seeing as you'll need to get good with CSS anyway. I am personally using Tailwind for my new projects and while I'm still figuring out how to structure my code, am enjoying it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have unanswered questions, please do comment below or send a &lt;a href="https://twitter.com/wasabigeek" rel="noopener noreferrer"&gt;tweet&lt;/a&gt; - I'd love to incorporate your feedback into this post to help others!&lt;/p&gt;

</description>
      <category>css</category>
    </item>
    <item>
      <title>Asset Compilation in Rails</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Thu, 04 Feb 2021 19:38:02 +0000</pubDate>
      <link>https://dev.to/wasabigeek/asset-compilation-in-rails-51b5</link>
      <guid>https://dev.to/wasabigeek/asset-compilation-in-rails-51b5</guid>
      <description>&lt;p&gt;In the previous post, we talked about what a frontend is, and briefly looked at how it works in Rails. Now, we'll look specifically at &lt;strong&gt;pre-compilation&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw0a296sjfoaeyh8v474m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw0a296sjfoaeyh8v474m.jpg" alt="Ruby on Rails Frontend Pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why compilation?
&lt;/h1&gt;

&lt;p&gt;Any moderately complex interactive web-app of today contains a &lt;em&gt;lot&lt;/em&gt; of CSS and Javascript, so we smart developers devised extensions and frameworks to make them easier to manage, for example: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;webpack&lt;/a&gt; does a lot of things, one of them being the bundling of our numerous CSS and JS files (which we break up for ease of development) into fewer files that are faster to load&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/autoprefixer" rel="noopener noreferrer"&gt;autoprefixer&lt;/a&gt; lets us write CSS without having to worry about vendor-prefixes (some browsers named CSS properties differently as they were experimental at the time etc. For example, &lt;code&gt;transition&lt;/code&gt; is &lt;code&gt;-webkit-transition&lt;/code&gt; in Safari).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These make it more convenient (once you get past the learning curve) to work on our frontend, but this means our assets usually can't be used in their raw form - Rails has to convert them into the actual HTML, JS and CSS files before sending it to your browser.&lt;/p&gt;

&lt;p&gt;In local development, the &lt;code&gt;rails server&lt;/code&gt; will mostly do this conversion on the fly for you. But in production, you'll want to pre-compile production-ready assets, so the server can focus on generating and sending pure HTML.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is “production-ready”?
&lt;/h1&gt;

&lt;p&gt;This includes things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combining separate JS and CSS files into less files so a browser needs to make less requests&lt;/li&gt;
&lt;li&gt;Minifying (compressing) the files so they’re smaller and load faster&lt;/li&gt;
&lt;li&gt;Fingerprinting so the browser knows when to ignore its cached files and request for the latest ones&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What are my options for pre-compilation?
&lt;/h1&gt;

&lt;p&gt;Rails 6 actually has two mechanisms for compiling static assets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Asset Pipeline&lt;/strong&gt; is the traditional way, using a gem called &lt;code&gt;sprockets&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webpack&lt;/strong&gt; is pretty much the a modern standard in the JS ecosystem, and Rails includes the &lt;code&gt;webpacker&lt;/code&gt; gem to enable this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can actually just pick one or the other, but it seems the Rails-recommended way is to use Webpacker for JS files, and the Asset Pipeline for everything else e.g. CSS.&lt;/p&gt;

&lt;p&gt;A brand new Rails 6 project loads with that in mind, so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/app/assets/stylesheets&lt;/code&gt; should be where your CSS files go&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/app/javascript/&lt;/code&gt; should be where your JS files go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the curious, here's some &lt;a href="https://rossta.net/blog/why-does-rails-install-both-webpacker-and-sprockets.html" rel="noopener noreferrer"&gt;backstory&lt;/a&gt; on why there are two ways to compile assets, by Ross Kaffenberger.&lt;/p&gt;

&lt;h1&gt;
  
  
  How do I set up pre-compilation for production?
&lt;/h1&gt;

&lt;p&gt;If you’re deploying with Heroku it will do all the pre-compilation for you, unless you've customised your build process. For other services, you'll have to run &lt;code&gt;rake assets:precompile&lt;/code&gt; in your deployment scripts. See the &lt;a href="https://guides.rubyonrails.org/asset_pipeline.html#precompiling-assets" rel="noopener noreferrer"&gt;Rails guide&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Next, we'll take a look at the different options for styling our web-sites/apps (i.e. CSS).&lt;/p&gt;

</description>
      <category>rails</category>
    </item>
    <item>
      <title>Choosing a Rails Frontend in 2021</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Thu, 04 Feb 2021 19:35:12 +0000</pubDate>
      <link>https://dev.to/wasabigeek/choosing-a-rails-frontend-in-2021-37ob</link>
      <guid>https://dev.to/wasabigeek/choosing-a-rails-frontend-in-2021-37ob</guid>
      <description>&lt;p&gt;As a new web developer starting with Ruby on Rails, there's a dizzying array of choices for the frontend of your site! This is first in a series of posts that attempts to give a lay of the land, with some personal suggestions on where to start. I'll assume you've at least built a simple Rails app before, if not you can't go wrong with the excellent &lt;a href="https://www.railstutorial.org/" rel="noopener noreferrer"&gt;https://www.railstutorial.org/&lt;/a&gt;. First, let's get a shared understanding of what a frontend is.&lt;/p&gt;

&lt;h1&gt;
  
  
  Explaining the frontend
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Oxford Dictionary 1. COMPUTING -&lt;/em&gt; relating to or denoting the part of a computer system or application with which the user interacts directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you enter a URL in your browser and click enter, a chain of events happen, culminating in your browser receiving a response - this is basically a plain text document that is written in HTML, CSS and Javascript - interpreting and rendering it.&lt;/p&gt;

&lt;p&gt;A nice metaphor could be a theatre:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc7p8w0ggh1zx7xlz37da.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc7p8w0ggh1zx7xlz37da.png" alt="Theatre Metaphor of Backend and Frontend"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The stage where all the actors perform and sometimes respond to audience interactions is like the &lt;strong&gt;frontend&lt;/strong&gt;. Our browser renders the HTML/CSS and handles user interactions (mouse-clicks, typing) based on the HTML conventions and instructions we wrote in Javascript.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;backend&lt;/strong&gt; is like a theatre's backstage, where stagehands arrange and prepare props and actors get changed for the next scene. This is akin to all the logic we have in our Rails controllers, combining data from our models with the views we wrote to be sent to the browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How does it work in Rails?
&lt;/h1&gt;

&lt;p&gt;When Rails devs say "&lt;strong&gt;frontend&lt;/strong&gt;" though, we usually refer to a bit more, encompassing not just the actual HTML, CSS and Javascript that is sent to the user, but &lt;em&gt;how&lt;/em&gt; we've set up Rails to generate them.&lt;/p&gt;

&lt;p&gt;There was once a time when web developers would need to write the HTML for every individual page - check the &lt;code&gt;404.html&lt;/code&gt; page in your Rails app for an example. This was fine for early websites, but perhaps impossible for creating the dynamic, interactive websites of today.&lt;/p&gt;

&lt;p&gt;In a framework like Rails, you typically wouldn't write the exact HTML for every single page. Rather, there are multiple components involved that Rails combines to generate the resulting HTML. A simplistic illustration:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fy8vho3rxf7eb6mcj6bfj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fy8vho3rxf7eb6mcj6bfj.jpg" alt="Ruby on Rails Frontend Pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;our "raw" Javascript and CSS &lt;strong&gt;assets&lt;/strong&gt; are usually structured in a way that's convenient for development, but not optimised for sending to users&lt;/li&gt;
&lt;li&gt;there is a &lt;strong&gt;pre-compilation&lt;/strong&gt; step that compiles and optimises our assets&lt;/li&gt;
&lt;li&gt;a Rails controller &lt;strong&gt;renders&lt;/strong&gt; our view and inserts the correct URLs to the compiled assets, then sends that back in a response to the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll dive a little deeper into the compilation step next, talking about the Asset Pipeline and Webpacker.&lt;/p&gt;

</description>
      <category>rails</category>
    </item>
    <item>
      <title>Window Functions, Visualized - Rankings</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Mon, 04 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/wasabigeek/window-functions-visualized-rankings-3p4g</link>
      <guid>https://dev.to/wasabigeek/window-functions-visualized-rankings-3p4g</guid>
      <description>&lt;p&gt;In the previous post, we talked about how &lt;strong&gt;Window Function Calls&lt;/strong&gt; worked. Today, we'll dive deeper into the different &lt;strong&gt;Window Functions&lt;/strong&gt; that are available, starting with &lt;code&gt;row_number&lt;/code&gt;, &lt;code&gt;rank&lt;/code&gt;, &lt;code&gt;dense_rank&lt;/code&gt; and &lt;code&gt;percent_rank&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll be using the same expenses table from the previous post, which had entries like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the following examples, our &lt;strong&gt;window frame&lt;/strong&gt; will be the whole expenses table, ordered by highest to lowest cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9mF3qOcH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cuup3h1zycpd7gz3oxgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9mF3qOcH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cuup3h1zycpd7gz3oxgd.png" alt="Original table to window frame"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  row_number
&lt;/h2&gt;

&lt;p&gt;Let's start with &lt;code&gt;row_number&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All &lt;code&gt;row_number&lt;/code&gt; does is add a running number for each row in the frame, starting from 1:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;row_number&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  dense_rank
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dense_rank&lt;/code&gt; is more interesting. Reviewing the previous example, we can see that there were a few entries that have the same cost. The Postgres documentation refers to these as &lt;strong&gt;peer groups&lt;/strong&gt;, and &lt;code&gt;dense_rank&lt;/code&gt; adds a running number counting by the groups instead of rows:&lt;/p&gt;

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

&lt;p&gt;So changing the SQL to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;DENSE_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gives us this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: I'm actually not sure how Postgres orders the rows &lt;em&gt;within&lt;/em&gt; a peer group - in my small example, it looks like an implicit &lt;code&gt;ORDER BY ID DESC&lt;/code&gt; was added, but the Postgres &lt;a href="https://www.postgresql.org/docs/current/queries-order.html"&gt;docs&lt;/a&gt; also say for general ordering, &lt;em&gt;if sorting is not chosen, the rows will be returned in an unspecified order&lt;/em&gt; 🤷‍♂️.&lt;/p&gt;

&lt;h2&gt;
  
  
  rank
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rank&lt;/code&gt; has one big difference from &lt;code&gt;dense_rank&lt;/code&gt; - it counts the "gaps" in the previous peer group:&lt;/p&gt;

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

&lt;p&gt;Let's compare the results of each function to show the difference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  percent_rank
&lt;/h2&gt;

&lt;p&gt;This is an interesting one - how it's derived is a bit of a mouthful, so let's show by example, starting with the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;PERCENT_RANK&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;percent_rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.11...&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.22...&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.33...&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.33...&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.33...&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.66...&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.77...&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.88...&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.88...&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Instead of a running number, we get the "relative rank" from 0 to 1 inclusive. This is useful if you need to know how close something is to the highest rank - though, as the above example shows, it may not end with 1 if there are ties for the highest rank (a deeper explanation &lt;a href="https://dba.stackexchange.com/a/144015"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  side-by-side
&lt;/h2&gt;

&lt;p&gt;Finally, let's look at results side by side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;row_number&lt;/th&gt;
&lt;th&gt;dense_rank&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;th&gt;percent_rank&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.11...&lt;/td&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0.22...&lt;/td&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.33...&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.33...&lt;/td&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0.33...&lt;/td&gt;
&lt;td&gt;supper&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;0.66...&lt;/td&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0.77...&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0.88...&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0.88...&lt;/td&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I hope that helped! Here's an &lt;a href="http://sqlfiddle.com/#!17/d3ff0a/8"&gt;sqlfiddle&lt;/a&gt; you can play around with.&lt;/p&gt;

&lt;p&gt;In future articles, we'll look into more window functions. Follow me on &lt;a href="https://twitter.com/wasabigeek"&gt;Twitter&lt;/a&gt; to be informed of the next one!&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
    </item>
    <item>
      <title>Window Function Calls in Postgres - a Visual Introduction</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Thu, 31 Dec 2020 21:56:03 +0000</pubDate>
      <link>https://dev.to/wasabigeek/window-function-calls-a-visual-introduction-12k7</link>
      <guid>https://dev.to/wasabigeek/window-function-calls-a-visual-introduction-12k7</guid>
      <description>&lt;p&gt;What is a &lt;strong&gt;window function call&lt;/strong&gt;? My explanation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;adds a column to query results that is calculated based on other rows in the table&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, that's probably still not immediately understandable, so let's walk through an example. Imagine you've been tracking expenses in Postgres:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now, say you wanted to see how much each expense compares to the average:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;avg&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;17.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; use a subquery to calculate the average:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we could also be a little fancier and use a window function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;over&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although we have some new syntax to understand (which we'll get into later), that's noticeably shorter! What just happened?&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello, Window Frames
&lt;/h2&gt;

&lt;p&gt;Let's try to visualise the window function call. For each row, Postgres calculates the average cost of the rest of the table, which is the default &lt;strong&gt;"window frame"&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OxTZEuuU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y4dqqrbd809lwhedn9yv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OxTZEuuU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y4dqqrbd809lwhedn9yv.png" alt="Calculating average cost over the default window frame"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now if that was all you could do with window function calls, I'd be disappointed too. Thankfully, it's not! Aside from being shorter, there are some key features that are harder to replicate with subqueries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calculations can be done over a subset of the table (i.e. a smaller &lt;strong&gt;window frame&lt;/strong&gt;) instead of the whole table&lt;/li&gt;
&lt;li&gt;in addition to the usual aggregation functions like &lt;code&gt;avg&lt;/code&gt; or &lt;code&gt;max&lt;/code&gt;, there are &lt;strong&gt;window functions&lt;/strong&gt;, which can only be used as part of window function calls&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reframing the Window
&lt;/h2&gt;

&lt;p&gt;Let's try calculating over a subset. Knowing the average across all expenses wasn't very helpful - a big purchase could skew the calculation, so let's instead compare each expense to the &lt;em&gt;category average&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;over&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;partition&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;"avg in category"&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our result now looks something like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;avg in category&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;tea break&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;taxi to home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a bit more helpful! Let's visualise this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kL08BP27--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/21hd6x2p8a5ca281urs2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kL08BP27--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/21hd6x2p8a5ca281urs2.png" alt="Calculating average cost over each category"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each row, our new window frame includes only other rows with the same category, and the average is calculated based on those rows.&lt;/p&gt;

&lt;p&gt;This is also a good time to take a brief look at the syntax we've used so far:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;avg(cost)&lt;/code&gt; is the aggregation function that we want to execute over the window frame - we could use other functions such as &lt;code&gt;max&lt;/code&gt; or &lt;code&gt;min&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OVER&lt;/code&gt; is the keyword which tells Postgres we were using a window function call. Everything in parentheses specifies how our table will be divided into different window frames&lt;/li&gt;
&lt;li&gt;in this case, we are using &lt;code&gt;PARTITION BY category&lt;/code&gt;, which means we are dividing the table into different window frames based on their category&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Window Functions
&lt;/h2&gt;

&lt;p&gt;Now that we know a little more about the syntax, let's look at the other hidden power of window functions calls... &lt;strong&gt;window functions&lt;/strong&gt; themselves!&lt;/p&gt;

&lt;p&gt;It's coming to the end of the month, and you want to know the biggest expense in each category, for each day. There's a window function for that - &lt;code&gt;rank&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;rank&lt;/code&gt;, we can calculate the order of each row in it's window frame. Note we'll still be returning every row in the table, not just the top expenses - we'll fix that later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt;
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;rank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;over&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;partition&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
    &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;expenses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the additional syntax in our window function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;we've added a second condition to &lt;code&gt;PARTITION BY&lt;/code&gt; to further divide each window frame by the day each entry was &lt;code&gt;created_at&lt;/code&gt; (this is very similar to &lt;code&gt;GROUP BY&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;there's now an &lt;code&gt;ORDER BY ... DESC&lt;/code&gt; that orders the rows within each frame from highest to lowest cost, so we'll know that a rank of 1 would be the most expensive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another quick visual:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AUJGAAVo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eua5c0b7ogkww7uhz5fz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AUJGAAVo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eua5c0b7ogkww7uhz5fz.png" alt="Calculating rank over each category and date"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So for each row, our window frame is other rows with the same category and created_at date, and we order the rows from highest to lowest cost.&lt;/p&gt;

&lt;p&gt;The query gives us a result like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dinner&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;taxi&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-02&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now that we have a ranking, we could use it as a subquery and filter out expenses that are rank 2 or higher.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;-- our earlier query&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;"ranked_expenses"&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;rank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which leaves us with exactly what we want - nice!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;created_at&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;th&gt;rank&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;groceries&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lunch&lt;/td&gt;
&lt;td&gt;food &amp;amp; drinks&lt;/td&gt;
&lt;td&gt;2020-01-02&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;taxi to home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-01&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bus ride home&lt;/td&gt;
&lt;td&gt;transport&lt;/td&gt;
&lt;td&gt;2020-01-02&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I hoped that helped you grok window functions! If you want to play around with the data in this example, there's an &lt;a href="http://sqlfiddle.com/#!17/f33e78/3"&gt;sqlfiddle&lt;/a&gt; - in case that doesn't exist at the time of reading, you can also get the SQL to create the table and query the data from this &lt;a href="https://gist.github.com/wasabigeek/2b9fb05eba5c26928bab85bcf408511f"&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the next post, we'll look deeper into Window Functions that involve ranking.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
