<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Stefan Wintermeyer on Medium]]></title>
        <description><![CDATA[Stories by Stefan Wintermeyer on Medium]]></description>
        <link>https://medium.com/@wintermeyer?source=rss-ba3ffab1ae74------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*SkxFJ4BfPtqpwMAVbWUfAg.jpeg</url>
            <title>Stories by Stefan Wintermeyer on Medium</title>
            <link>https://medium.com/@wintermeyer?source=rss-ba3ffab1ae74------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 11 May 2026 09:52:54 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@wintermeyer/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Let’s talk about that 15% Ruby claim]]></title>
            <link>https://medium.com/@wintermeyer/i-call-bs-on-the-15-ruby-claim-5c37a4bd00b6?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/5c37a4bd00b6</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[elixir]]></category>
            <category><![CDATA[phoenix-framework]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Sun, 12 Jan 2020 09:35:20 GMT</pubDate>
            <atom:updated>2020-01-18T08:50:25.107Z</atom:updated>
            <content:encoded><![CDATA[<p>In December 2019 <a href="https://medium.com/u/54bcbf647830">DHH</a> wrote the post <a href="https://m.signalvnoise.com/only-15-of-the-basecamp-operations-budget-is-spent-on-ruby/">“Only 15% of the Basecamp operations budget is spent on Ruby”</a> about how his company only spent 15% of their $ 3,000,000 annual budget on Ruby systems. Obviously he meant well. But as a Rails and Phoenix consultant I often see the fallout of those posts. Therefor I’d like to share some thoughts about the costs of running a Rails application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DRMcVLTdHczZ1YIy2E92qw.jpeg" /><figcaption>This photo has absolutely nothing to do with the article. My kids baked them a couple of days ago. They were very tasty and Medium posts with a nice photo always get more traction. ;-)</figcaption></figure><h3>Basecamp is not the baseline for a Rails company</h3><p>Before talking about the numbers I’d like to point out that David has access to the crème de la crème of Ruby on Rails developers. Being the creator of Rails he and his company are a dream employer for the top notch people. Of course all A players want to play with other A players. All of my Rails clients have serious problems to find good and affordable developers because they don’t have the visibility or coolness factor of Basecamp. Yes, many of my clients are not A players them self. But who am I to blame them for it? Rails is not just for A players. It never should be!</p><p>Of course the developers of Basecamp are better than the average Rails company. And of course they max out the potential of Ruby on Rails. None of my clients could do that. Most companies don’t have access to heros or rockstars. And one of the reasons I love Rails is that they don’t need to. But having just normal developers is first reason why 15% for most companies is unfeasable. But let’s dive into the raw technical arguments because I don’t buy the 15% claim for Basecamp either.</p><h3>A swarm of Redis servers</h3><p>David lists the different kind of servers they run at AWS. He mentions “A swarm of Redis servers providing caching.” Unfortunately he didn&#39;t quantify how much a “swarm” is. Plus he didn’t say which services use the Redis servers. Knowing a bit about caching and performance optimization in Rails (watch my <a href="https://www.youtube.com/watch?v=7uKxDVflXdI">RailsConf “Cache = Cash!” talk on YouTube</a>) I guess that a big chunk of the swarm does caching for Rails because it is a good solution for many Rails performance issues. In my opinion it is unfair to not include those costs in his post. They need those Redis servers because they are using Rails. Without concrete numbers I have to guesstimate: If you spend $ 450,000 on Rails servers … hmmm … let’s say you spent $ 200,000 on Redis which are just used by Rails (including job servers).</p><blockquote>David, in case you are reading this: Please do write an other post with more details so that everybody can make up his/her mind about this. I’ll be happy to update this article once those numbers are out. Thank you in advance!</blockquote><p>Using caching in Rails is a very solid performance booster. But it is costly. Of course you need Redis or any other caching server but you also need more developer time. And as David recognized it those are high costs for any company. The problem with writing code which uses a lot of caching is the complexity. It is very error prone. A lot of my clients hire me to find bugs where they shot themself in the foot by some complex caching system.</p><p>So we are dealing with a relative slow environment which we speed up by using caching but that itself adds complexity which means we need more manpower which is expensive.</p><h3>The cost of deployment</h3><p>The article doesn’t mention how Basecamp deploys it’s application. In my experience this is a tricky aspect of any Rails application. For an application this size and with that many active concurrend users you want a zero downtime deployment and the possiblity to roll back. Without having more knowledge about the used technology at Basecamp I can not argue if the 15% would have to be increased by a small swarm of additional servers to handle this or not.</p><h3>Order of magnitude</h3><p>David writes: “Let’s imagine that there was some amazing technology that would let us do everything we’re doing with Ruby on Rails, but it was TWICE AS FAST!” Later he writes: “Now imagine we found a true silver bullet. One where the compute spend could be reduced by an order of magnitude.”</p><p>Long story short: There is such a solution. It is the <a href="https://www.phoenixframework.org">Phoenix Framework</a>. But I don’t want to reheat the Rails vs. Phoenix discussion here. There are plenty of good reasons why to use Rails (<a href="https://medium.com/hackernoon/phoenix-is-better-but-rails-is-more-popular-8975d5e68879">see my other post about it</a>).</p><h3>The value of $ x00,000</h3><p>David argues that using a silver bullet like Phoenix would reduce their costs by “just” $ 400,000/year which wouldn’t even pay for two developers. I have a couple of issues with this:</p><ul><li>Assuming Basecamp could reduce the cost by $ 400,000 it would mean that the 15% number would be so much lower on that alternative solution.</li><li>As written above: I don’t buy the 15% in the first place. Even for Basecamp. I bet $ 20 that their real operational cost because they are using Rails is higher than 15% (see e.g. the Redis argument).</li><li>$ x00,000 is a lot of money for most companies. Let’s be a little bit more realistic about those numbers.</li></ul><h3>Let’s talk about Heroku for a moment</h3><p>Basecamp uses AWS but many Rails companies use <a href="https://www.heroku.com">Heroku</a>. Heroku became kind of a default way of running Rails for a majority. They lure developers with free mini versions, super easy setup and painless deployment in the beginning. And once the applications run on Heroku companies find it hard to move to AWS or bare metal. Anybody who has ever seen a Heroku invoice for a big installation knows that running a Rails application in that way can be quite expensive.</p><h3>Rails is bad?</h3><p>For a first version of this post I got a lot of feedback about how great Rails is. I never said something different in my text. I just want to <strong>paint a more realistic picture for an average company about the costs</strong>. They will not just spend 15% for Ruby servers. They will spend more. Still it is a good choice. The benefits of Rails are tremendous (e.g. big eco system, relative easy onboarding of new team members, ActiveRecord, I could go on for ever).</p><p><strong>I want people to use Ruby on Rails! I understand why Basecamp uses Rails. I just do not want newbies being lured into using Rails with the promise that only 15% of their operations budget is going to go into Ruby servers.</strong></p><h3>One favor</h3><p>If you have read all the article I’d like to ask you for one favor: Have a look and create an account at my open-source social network <a href="https://www.vutuv.de">https://www.vutuv.de</a></p><p>Think of it as a fast and free LinkedIn which will get some Medium features in the near future. It is not fancy yet but this is a chicken and egg problem. Therefor I need your help to get more people on board. Thank you!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c37a4bd00b6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ask nginx not to log those crawlers]]></title>
            <link>https://medium.com/@wintermeyer/ask-nginx-not-to-log-those-crawlers-2237cbd796d0?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/2237cbd796d0</guid>
            <category><![CDATA[nginx]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Thu, 21 Feb 2019 11:31:09 GMT</pubDate>
            <atom:updated>2019-02-21T11:31:09.965Z</atom:updated>
            <content:encoded><![CDATA[<p>After watching Marie Kondo I asked myself while reading an nginx access log of a big webpage (the one about German <a href="https://www.mehr-schulferien.de">Schulferien</a>) if those log entries of the search engine crawlers spark any joy. The answer was no. I’m happy to have the bots as regular guests on my web server but the information of their visit is not important for me (your mileage may vary).</p><p>To achieve this in nginx you have to create a list of bots you don’t want to log and put it as follows in your /etc/nginx/nginx.conf</p><pre>map $http_user_agent $log_excluded_ua {<br> ~Googlebot 0;<br> ~bingbot 0;<br> ~AhrefsBot 0;<br> ~Yahoo! 0;<br> ~proximic 0;<br> ~GrapeshotCrawler 0;<br> ~MJ12bot 0;<br> ~Mediapartners-Google 0;<br> ~SemrushBot 0;<br> ~MegaIndex.ru 0;<br> ~Mediatoolkitbot 0;<br> ~DotBot 0;<br> default 1;<br>}</pre><p>After that you can tell nginx in your site configuration to only log an access when it’s not a crawler:</p><pre>access_log /var/log/nginx/www.example.com-access.log combined if=$log_excluded_ua;</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2237cbd796d0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rails needs Active Deployment]]></title>
            <link>https://medium.com/@wintermeyer/https-medium-com-wintermeyer-rails-needs-active-deployment-65c207858c3?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/65c207858c3</guid>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Thu, 03 Jan 2019 10:28:36 GMT</pubDate>
            <atom:updated>2019-01-07T14:59:52.290Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve been using Ruby on Rails for a decade now and I still love it. But sometimes it is one of those love-hate relationships. <a href="https://www.wintermeyer-consulting.de">I work as a consultant aka fire fighter</a> to help companies with their Rails projects. Most times I help them fixing all sort out performance and high availability issues. I’ve seen the code base of a lot of different Rails applications. So I don’t just see the happy path but also a lot of not so happy campers. I even wrote <a href="https://www.amazon.com/Learn-Rails-5-2-Accelerated-Development/dp/148423488X">books about Rails</a> to help more Rails beginners.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WfGisd1sT_H_QOnjzTzoHA.jpeg" /><figcaption>This is me failing to light paint a “2019” with a sparkler. Seemed to be as easy as deploying a Rails application.</figcaption></figure><p>It took me a while to understand and appreciate features like Active Storage, Active Job, Action Cable or the new Action Mailbox. Many times my initial thought was <em>“There is already at least one good solution (gem) for that problem. Why reinventing the wheel?”</em> But during my work on so many different Rails projects I began to realize that it is so much better to give each team a good to go initial set of tools. Of course they can use a different gem (like so many do for testing) but they have a very good start with the vanilla Rails project. And the more projects use the same base the better the general documentation and code quality gets. It has never been easier to find help for a Rails problem on <a href="https://stackoverflow.com">stackoverflow.com</a> than today. No need for fancy gems to solve standard problems. BTW: Many times those fancy gems don’t survive the next Ruby upgrade because their maintainer lost interrest or just doesn’t have time for it.</p><p>Development of new Rails projects has never been easier and faster! But …</p><h3>Deployment is still a nightmare</h3><p>What good is a framework if it is not easy to deploy to a production server? In the pre-Rails days many of us just copied a directory of PHP files to the web server via scp or ftp and that was it. With Rails it became more complicated.</p><p>Of course you can use <a href="https://www.heroku.com">Heroku</a> which takes most of the pain out of this. Many companies do use Heroku but it is expensive and not always super fast. Many companies want to deploy their Rails application to a normal out of the box linux distribution (e.g. Debian or Redhat).</p><p>Apache or Nginx don’t do the hard lifting for Rails like they do with PHP. But you still need them. So you have to make a choice of <a href="https://github.com/puma/puma">Puma</a>, Unicorn or whatever to actually run your Rails application and then you have to put Apache or Nginx in front of that. Or you use <a href="https://www.phusionpassenger.com">Phusion Passenger</a> (which involves some fiddling around with the Nginx or Apache modules). All complicated enough for a Rails newbie. Additionally you have to use <a href="https://rvm.io">RVM</a> or <a href="https://github.com/rbenv/rbenv">rbenv</a> to run the needed Ruby version.</p><p>But you still have to find a way to actually copy your source code to the server, run all needed migrations and restart the server. The bravest even go so far to attempt zero downtime deployments (a little secret: You want to have a look at <a href="https://phoenixframework.org">Phoenix Framework</a>). So you have to dive into <a href="https://capistranorb.com">Capistrano</a> or some cool new kid like <a href="https://github.com/mina-deploy/mina">Mina</a>.</p><p>To setup deployment for a simple Rails application on Heroku takes a couple of minutes. 30 minutes tops (if you have to get the credit card information of your boss first). But the setup of a deployment process on a vanilla Linux Server from the ground up takes hours if not days. You have to read too many different tutorials and have to make too many decisions. And you end with a snow flake. Beautiful but one of a kind and hard to support.</p><h3>Docker! ?</h3><p>I hear all those voices which shout: Why don’t you use Docker for this? Docker makes everything so easy!</p><p>No, it does not! I challenge you to find a single tutorial which describes how to setup a Docker deployment from start to finish for a Rails application for a Debian server. Most of the how-tos are half baked at best. They just show you how to run Docker for your development system.</p><p>But don’t get me wrong: I’m all for Docker. This posting is not pro or against Docker. This posting is about a standard deployment for Rails.</p><h3>But we pros know how to do it!</h3><p>That doesn’t help at all. If a normal developer who maybe works on 1 or 2 Rails projects can not create a simple non-Heroku deployment to production than that is a big problem. Rails became visible with @DHH ‘s 15 minute blog video. It became visible and popular because that was so easy to accomplish. But without an easy production environment it’s worth not much.</p><h3>Active Deployment</h3><p>To be honest I don’t care which technology we use on the server. I don’t care how we all deploy. <strong>What I care about is that it has to be an easy to use system which comes out of the box.</strong> No more deployment snow flakes. Convention over configuration! I want to dive into an exisiting Rails project knowing that it probably uses the standard tool for deployment.</p><p>I’m calling out for <a href="https://medium.com/u/54bcbf647830">DHH</a> or any body else in the Rails community to think about this. Take a step back and realize how hard this problem is for a normal Ruby team which does not have super hero DevOps power.</p><p>I don’t have the technical knowledge to find an optimal solution for this problem. I just see the pain and it hurts me that many potential new Rails projects never get a chance just because they don’t have an easy way to deploy it to a non-Heroku environment.</p><p>Please discuss and share this.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=65c207858c3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Check and Update a URL with Ruby]]></title>
            <link>https://medium.com/@wintermeyer/check-and-update-a-url-with-ruby-120e6ba73e4f?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/120e6ba73e4f</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Wed, 05 Dec 2018 09:04:30 GMT</pubDate>
            <atom:updated>2018-12-05T09:04:30.574Z</atom:updated>
            <content:encoded><![CDATA[<p>While checking and updating existing client’s data I needed to update URLs in their database. Very often they had http in it but the side has since moved to https. Other sides moved to totally different URLs and there were even a few which didn’t exist any more. Some entries lacked the http:// or www part because it was a legacy database with suboptimal validation. Time to do some housekeeping for those URLs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XK29wZ4XgEh5iySqg8dQOw.jpeg" /><figcaption>I searched for “crawl” on unsplash.com and it came up with this photo by <a href="https://unsplash.com/photos/qMWgy4LpaIs?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alex Blăjan</a>.</figcaption></figure><p>I needed a method which I can call with the existing URL and it returns the new URL or nil in case that URL doesn’t work any more. I spare you the details but this is not as easily done as said. Redirections have to be handled properly over multiple steps. I tried a lot of different tools and gems for this job. After testing a couple of stackoverflow solutions I stumbled upon the curb gem (<a href="https://github.com/taf2/curb">https://github.com/taf2/curb</a>) which uses libcurl.</p><p>With that I was able to solve the problem with just a few lines of code:</p><pre>def checked_url(url)<br>  begin<br>    result = Curl::Easy.perform(url) do |curl|<br>      curl.head = true<br>      curl.follow_location = true<br>      curl.timeout = 3<br>    end<br>    result.last_effective_url<br>  rescue<br>    nil<br>  end<br>end</pre><p>And here is the test. Let’s assume I want to check the url nyt.com</p><pre>irb(main):042:0&gt; checked_url(‘nyt.com’)<br>=&gt; “https://www.nytimes.com/&quot;<br>irb(main):043:0&gt;</pre><p>As always: If you need Ruby on Rails consulting =&gt; <a href="https://www.wintermeyer-consulting.de">https://www.wintermeyer-consulting.de</a> and follow me on https://twitter.com/wintermeyer</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=120e6ba73e4f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Authentication from Scratch with Rails 5.2]]></title>
            <link>https://medium.com/@wintermeyer/authentication-from-scratch-with-rails-5-2-92d8676f6836?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/92d8676f6836</guid>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Tue, 18 Sep 2018 14:23:46 GMT</pubDate>
            <atom:updated>2019-12-13T13:45:31.439Z</atom:updated>
            <content:encoded><![CDATA[<p>A tutorial to create a simple authentication for your Rails 5.2 application when gems like <a href="https://github.com/plataformatec/devise">Devise</a> are too big or too complicated to customize.</p><p>Background: Often I use Devise as the one-stop-shop solution. But lately I found myself (again) in a Rails application trying to find out how to customize Devise for the specific needs of that application. Just to realize that I would have saved so much time by just implementing the authentication from scratch by myself.</p><blockquote><a href="http://railscasts.com/episodes/250-authentication-from-scratch-revised">The groundwork for tutorial was done by Ryan Bates many years ago.</a></blockquote><p>Important: This tutorial works under the assumption that the session encryption of Rails is secure. If you disagree with that assumption you are bright enough to add an additional random token.</p><h3>Green Field</h3><p>We start with a fresh Rails application:</p><pre>$ <strong>rails new shop</strong><br>$ <strong>cd shop</strong></pre><p>Later we are going to redirect to root. So we start with creating an empty root page:</p><pre>$ <strong>rails g controller home index</strong></pre><p>Please add the following code to config/routes.rb :</p><pre>Rails.application.routes.draw do<br>  root ‘home#index’<br>end</pre><p>And some content for that page in the file app/views/home/index.html.erb :</p><pre>&lt;p id=”notice”&gt;&lt;%= notice %&gt;&lt;/p&gt;</pre><pre>&lt;h1&gt;Example&lt;/h1&gt;<br>&lt;p&gt;Lorem ipsum …&lt;/p&gt;</pre><p>If you like this post I’d like to ask you for a favour: <br>Create an account at my open-source business network <a href="https://www.vutuv.de">https://www.vutuv.de</a></p><p>Thank you and see you there!</p><h3>Password Digest</h3><p>Obviously we do not store the clear text password in the database but a digest of it. For that we need to activate the bcrypt gem in the file Gemfile:</p><pre># Use ActiveModel has_secure_password<br>gem ‘bcrypt’, ‘~&gt; 3.1.7’</pre><p>And run bundle afterwords:</p><pre>$ <strong>bundle</strong></pre><h3>User Model</h3><p>Now we create a User scaffold. Feel free to add any additional fields you might need (e.g. first_name, last_name). I just use email:uniq to store the email address (and create an unique database index) and password:digest to create a password_digest field in the new users table.</p><pre>$ <strong>rails g scaffold User email:uniq password:digest</strong><br>$ <strong>rails db:migrate</strong></pre><p>The digest part puts some Rails magic into action. The Rails generator creates a password_digest field in the table and asks for an additional password_confirmation in the form and the controllers user_params without you having to do anything extra. has_secure_password in the model takes care of encrypting the password and provides theauthenticate method to authenticate with that password.</p><p>Before a first test we need to add some validations in app/models/user.rb to make sure that we have an email address and that it is unique:</p><pre>class User &lt; ApplicationRecord<br>  has_secure_password</pre><pre>  validates :email, presence: true, uniqueness: true<br>end</pre><p>Now we can fire up Rails and create a new user in the browser:</p><pre>$ rails s</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/736/1*xa4bFnfp4nIZRXb6SUzrNQ.gif" /><figcaption>Screencast: Create a new user at <a href="http://localhost:3000/users/new">http://localhost:3000/users/new</a></figcaption></figure><p>Let’s just check how Rails stores the password digest in the table:</p><pre>$ <strong>rails c</strong><br>Running via Spring preloader in process 3204<br>Loading development environment (Rails 5.2.1)<br>&gt;&gt; <strong>User.first</strong><br>User Load (0.2ms) SELECT “users”.* FROM “users” ORDER BY “users”.”id” ASC LIMIT ? [[“LIMIT”, 1]]<br>=&gt; #&lt;User id: 1, email: “sw@wintermeyer-consulting.de”, password_digest: “$2a$10$t6Q2R.N5fevFjhL/W1X.EulEJQ8TDWIzCvHpbDrAtQo…”, created_at: “2018–09–18 12:13:26”, updated_at: “2018–09–18 12:13:26”&gt;</pre><p>So only the digest is saved. Everything is secure. But we still need to create some sort of login to actually use it.</p><h3>Sessions</h3><p>When a user logs in he/she creates a new session. When the same user logs out that session gets destroyed. Therefor we create a sessions controller with three actions:</p><pre>$ <strong>rails g controller sessions new create destroy</strong></pre><p>We put the following code into app/controllers/sessions_controller.rb:</p><pre>class SessionsController &lt; ApplicationController<br>  def new<br>  end</pre><pre>  def create<br>    user = User.find_by_email(params[:email])<br>    if user &amp;&amp; user.authenticate(params[:password])<br>      session[:user_id] = user.id<br>      redirect_to root_url, notice: &quot;Logged in!&quot;<br>    else<br>      flash.now[:alert] = &quot;Email or password is invalid&quot;<br>      render &quot;new&quot;<br>    end<br>  end</pre><pre>  def destroy<br>    session[:user_id] = nil<br>    redirect_to root_url, notice: &quot;Logged out!&quot;<br>  end</pre><pre>end</pre><p>As you can see we use session[:user_id] to store the logged in user id. In case you haven’t worked with sessions yet have a look at <a href="https://guides.rubyonrails.org/security.html#sessions">https://guides.rubyonrails.org/security.html#sessions</a></p><p>We need to put this code for the form in app/views/sessions/new.html.erb :</p><pre>&lt;p id=”alert”&gt;&lt;%= alert %&gt;&lt;/p&gt;</pre><pre>&lt;h1&gt;Login&lt;/h1&gt;</pre><pre>&lt;%= form_tag sessions_path do |form| %&gt;<br>  &lt;div class=”field”&gt;<br>    &lt;%= label_tag :email %&gt;<br>    &lt;%= text_field_tag :email %&gt;<br>  &lt;/div&gt;</pre><pre>  &lt;div class=”field”&gt;<br>    &lt;%= label_tag :password %&gt;<br>    &lt;%= password_field_tag :password %&gt;<br>  &lt;/div&gt;</pre><pre>  &lt;div class=”actions”&gt;<br>    &lt;%= submit_tag “Login” %&gt;<br>  &lt;/div&gt;<br>&lt;% end %&gt;</pre><h3>Routes</h3><p>The routes are a bit cumbersome. But we can fix this with this code in config/routes.rb :</p><pre>Rails.application.routes.draw do<br>  root ‘home#index’<br>  <br>  resources :users<br>  resources :sessions, only: [:new, :create, :destroy]</pre><pre>  get ‘signup’, to: ‘users#new’, as: ‘signup’<br>  get ‘login’, to: ‘sessions#new’, as: ‘login’<br>  get ‘logout’, to: ‘sessions#destroy’, as: ‘logout’<br>end</pre><p>Now a user can use <a href="http://localhost:3000/sessions/new">http://localhost:3000/login</a> to login and <a href="http://localhost:3000/logout">http://localhost:3000/logout</a> to logout. Much easier for everybody.</p><h3>current_user</h3><p>In most Rails applications the logged in user is available with a current_user helper. This come handy too if you want to use an authorization gem like <a href="https://github.com/CanCanCommunity/cancancan">cancancan</a>. The most popular way to add this functionality is this code in app/controllers/application_controller.rb:</p><pre>class ApplicationController &lt; ActionController::Base<br>  helper_method :current_user</pre><pre>  def current_user<br>    if session[:user_id]<br>      @current_user ||= User.find(session[:user_id])<br>    else<br>      @current_user = nil<br>    end<br>  end<br>end</pre><p>To put it into use we change the content of app/views/home/index.html.erb:</p><pre>&lt;% if current_user %&gt;<br>  Logged in as &lt;%= current_user.email %&gt;.<br>  &lt;%= link_to “Log Out”, logout_path %&gt;<br>&lt;% else %&gt;<br>  &lt;%= link_to “Sign Up”, signup_path %&gt; or <br>  &lt;%= link_to “Log In”, login_path %&gt;<br>&lt;% end %&gt;</pre><pre>&lt;p id=”notice”&gt;&lt;%= notice %&gt;&lt;/p&gt;</pre><pre>&lt;h1&gt;Example&lt;/h1&gt;<br>&lt;p&gt;Lorem ipsum …&lt;/p&gt;</pre><h3>The End</h3><p>And here is the screencast where I log in with my account and log out afterwards:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/562/1*BjxzM8nmxh2f4IBl0CSlfA.gif" /><figcaption>Screencast: Log in and Log out</figcaption></figure><h3>Shameless Plug</h3><p>Do you want to learn more about Rails 5.2? Buy my book at <a href="https://www.amazon.com/Learn-Rails-5-2-Accelerated-Development/dp/148423488X">https://www.amazon.com/Learn-Rails-5-2-Accelerated-Development/dp/148423488X</a></p><p>In case you need consulting or training:<br><a href="https://www.wintermeyer-consulting.de">https://www.wintermeyer-consulting.de</a></p><h4>Contact Information</h4><p>Email: sw@wintermeyer-consulting.de<br>Twitter: <a href="https://twitter.com/wintermeyer">https://twitter.com/wintermeyer</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=92d8676f6836" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Caching in Ruby on Rails 5.2]]></title>
            <link>https://medium.com/rubyinside/https-medium-com-wintermeyer-caching-in-ruby-on-rails-5-2-d72e1ddf848c?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/d72e1ddf848c</guid>
            <category><![CDATA[rails-5]]></category>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[web-performance]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Mon, 30 Apr 2018 06:50:52 GMT</pubDate>
            <atom:updated>2019-12-13T13:43:51.365Z</atom:updated>
            <content:encoded><![CDATA[<p>This Medium post is a copy of Chapter 14 <strong>about caching</strong> from my <em>“Learn Rails 5.2”</em> book (<a href="https://amzn.to/2JCdKft">Amazon Link</a>, <a href="https://www.apress.com/us/book/9781484234884">Apress Link</a>) which was published in April 2018 by Apress. Please contact me in case you need consulting or training for Rails, Phoenix or WebPerformance.</p><blockquote><em>Homepage: </em><a href="https://www.wintermeyer-consulting.de"><em>https://www.wintermeyer-consulting.de</em></a><em><br>Email: </em><a href="mailto:sw@wintermeyer-consulting.de"><em>sw@wintermeyer-consulting.de</em></a><em><br>Twitter: </em><a href="https://twitter.com/wintermeyer"><em>@wintermeyer</em></a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*xTdDSCDf1enlPgV2Ap-_Bg.jpeg" /></figure><h3>Abstract</h3><p>With the caching of web applications, most people tend to wait to implement it until they encounter performance problems. First the admin usually looks at the database and adds an index here and there. If that does not help, the admin then takes a look at the views and adds fragment caching. But this is not the best approach for working with caches.</p><p><strong>The aim of this chapter is to help you understand how key-based cache expiration works.</strong> You can then use this approach to plan new applications already on the database structure level in such a way that you can cache optimally during development.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/153/1*3R5e3S9yDdzMaKjV3xWfLw.jpeg" /><figcaption>Cover of “Learn Rails 5.2” by Stefan Wintermeyer</figcaption></figure><p>With the caching of web applications, most people tend to wait to implement it until they encounter performance problems. First the admin usually looks at the database and adds an index here and there.</p><p>If that does not help, the admin then takes a look at the views and adds fragment caching. But this is not the best approach for working with caches. The aim of this chapter is to help you understand how key-based cache expiration works. You can then use this approach to plan new applications already on the database structure level in such a way that you can cache optimally during development.</p><p>There are two main arguments for using caching:</p><ul><li>The application becomes faster for the user. A faster web page<br>results in happier users, which results in a better conversion rate.</li><li>You need less hardware for the web server because you require less<br>CPU and RAM resources for processing the queries.</li></ul><p><em>If these two arguments are irrelevant for you, then there’s no need to<br>read this chapter.</em></p><p>I will cover three caching methods:</p><ul><li><strong>HTTP caching:</strong> This is the sledgehammer among the caching methods and<br>the ultimate performance weapon. In particular, web pages that are<br>intended for mobile devices should try to make the most of HTTP<br>caching. If you use a combination of key-based cache expiration and<br>HTTP caching, you save a huge amount of processing time on the server<br>and also bandwidth.</li><li><strong>Page caching:</strong> This is the screwdriver among the caching methods. You<br>can get a lot of performance out of the system, but it is not as good<br>as HTTP caching.</li><li><strong>Fragment caching:</strong> This is the tweezers among the caching methods, so<br>to speak. But do not underestimate it!</li></ul><p>The aim is to optimally combine all three methods.</p><h3>The Example Application</h3><p>You will use a simple phone book with a company model and an employees<br>model.</p><p>Create the new Rails app, as shown here:</p><pre>$ rails new phone_book<br> […]<br>$ cd phone_book<br>$ rails generate scaffold company name<br> […]<br>$ rails generate scaffold employee company:references \<br> last_name first_name phone_number<br> […]<br>$ rails db:migrate<br> […]</pre><h3>Models</h3><p>Listing [3]14–1 and Listing [4]14–2 show the setup for the two models.</p><pre>class Company &lt; ApplicationRecord<br> validates :name,<br> presence: true,<br> uniqueness: true<br> has_many :employees, dependent: :destroy<br> def to_s<br> name<br> end<br>end</pre><pre>Listing 14–1 app/models/company.rb</pre><pre>class Employee &lt; ApplicationRecord<br> belongs_to :company, touch: true<br> validates :first_name,<br> presence: true<br> validates :last_name,<br> presence: true<br> validates :company,<br> presence: true<br> def to_s<br> “#{first_name} #{last_name}”<br> end<br>end</pre><pre>Listing 14–2 app/models/employee.rb</pre><h3>Views</h3><p>Go ahead and change the two company views, shown in Listing 14–3 and<br>Listing 14–4, to list the number of employees in the index view and<br>all the employees in the show view.</p><pre>[…]<br>&lt;table&gt;<br> &lt;thead&gt;<br> &lt;tr&gt;<br> &lt;th&gt;Name&lt;/th&gt;<br> &lt;th&gt;Number of employees&lt;/th&gt;<br> &lt;th colspan=”3&quot;&gt;&lt;/th&gt;<br> &lt;/tr&gt;<br> &lt;/thead&gt;<br> &lt;tbody&gt;<br> &lt;% <a href="http://twitter.com/companies">@companies</a>.each do |company| %&gt;<br> &lt;tr&gt;<br> &lt;td&gt;&lt;%= company.name %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= company.employees.count %&gt;&lt;/td&gt;<br> […]<br> &lt;/tr&gt;<br> &lt;% end %&gt;<br> &lt;/tbody&gt;<br>&lt;/table&gt;<br>[…]</pre><pre>Listing 14–3 app/views/companies/index.html.erb</pre><pre>&lt;p id=”notice”&gt;&lt;%= notice %&gt;&lt;/p&gt;<br>&lt;p&gt;<br> &lt;strong&gt;Name:&lt;/strong&gt;<br> &lt;%= <a href="http://twitter.com/company">@company</a>.name %&gt;<br>&lt;/p&gt;<br>&lt;% if <a href="http://twitter.com/company">@company</a>.employees.any? %&gt;<br>&lt;h1&gt;Employees&lt;/h1&gt;<br>&lt;table&gt;<br> &lt;thead&gt;<br> &lt;tr&gt;<br> &lt;th&gt;Last name&lt;/th&gt;<br> &lt;th&gt;First name&lt;/th&gt;<br> &lt;th&gt;Phone number&lt;/th&gt;<br> &lt;/tr&gt;<br> &lt;/thead&gt;<br> &lt;tbody&gt;<br> &lt;% <a href="http://twitter.com/company">@company</a>.employees.each do |employee| %&gt;<br> &lt;tr&gt;<br> &lt;td&gt;&lt;%= employee.last_name %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= employee.first_name %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= employee.phone_number %&gt;&lt;/td&gt;<br> &lt;/tr&gt;<br> &lt;% end %&gt;<br> &lt;/tbody&gt;<br>&lt;/table&gt;<br>&lt;% end %&gt;<br>&lt;%= link_to ‘Edit’, edit_company_path(<a href="http://twitter.com/company">@company</a>) %&gt; |<br>&lt;%= link_to ‘Back’, companies_path %&gt;</pre><pre>Listing 14–4 app/views/companies/show.html.erb</pre><p>If you like this post I’d like to ask you for a favour: <br>Create an account at my open-source business network <a href="https://www.vutuv.de">https://www.vutuv.de</a></p><p>Thank you and see you there!</p><h3>Example Data</h3><p>To easily populate the database, you can use the Faker gem <em>(see<br></em><a href="http://faker.rubyforge.org/"><em>http://faker.rubyforge.org/</em></a><em>)</em>. With Faker, you can generate random<br>names and phone numbers. Please add the line shown in Listing 14–5<br>in the Gemfile.</p><pre>[…]<br>gem ‘faker’<br>[…]</pre><pre>Listing 14–5 Gemfile</pre><p>Then start bundle, as shown here:</p><pre>$ bundle</pre><p>With db/seeds.rb, you can create 30 companies with a random number of<br>employees in each case, as shown in Listing 14–6.</p><pre>30.times do<br> company = Company.new(:name =&gt; Faker::Company.name)<br> if company.save<br> SecureRandom.random_number(100).times do<br> company.employees.create(<br> first_name: Faker::Name.first_name,<br> last_name: Faker::Name.last_name,<br> phone_number: Faker::PhoneNumber.phone_number<br> )<br> end<br> end<br>end</pre><pre>Listing 14–6 db/seeds.rb</pre><p>You can populate it via rails db:seed.</p><pre>$ rails db:seed</pre><p>You can start the application with rails server and retrieve the<br>example data with a web browser by going to the URL<br><a href="http://localhost:3000/companies">http://localhost:3000/companies</a> or <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a>.</p><h3>Normal Speed of the Pages to Optimize</h3><p>In this chapter, you will optimize the example web pages. Start the<br>Rails application in development mode with rails server. (The relevant<br>time values, of course, depend on the hardware you are using.)</p><pre>$ rails server</pre><p>To access the web pages, use the command-line tool curl<br>(<a href="http://curl.haxx.se/">http://curl.haxx.se/</a>). Of course, you can also access the web<br>pages with other web browsers. You can look at the time shown in the<br>Rails log for creating the page. In reality, you need to add the time<br>it takes for the page to be delivered to the web browser.</p><h4>List of All Companies (Index View)</h4><p>At the URL <a href="http://localhost:3000/companies">http://localhost:3000/companies</a>, the user can see a list of<br>all the saved companies with the relevant number of employees.</p><p>Generating the page takes 89ms on my machine.</p><p>Completed 200 OK in 89ms (Views: 79.0ms | ActiveRecord: 9.6ms)</p><p>Detailed View of a Single Company (Show View)</p><p>At the URL <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a>, the user can see the<br>details of the first company with all the employees.</p><p>Generating the page takes 51ms on my machine.</p><p>Completed 200 OK in 51ms (Views: 48.9ms | ActiveRecord: 0.9ms)</p><h3>HTTP Caching</h3><p>HTTP caching attempts to reuse already loaded web pages or files. For<br>example, if you visit a web page such as <a href="http://www.nytimes.com">www.nytimes.com</a> or<br><a href="http://www.wired.com">www.wired.com</a> several times a day to read the latest news, then<br>certain elements of that page (for example, the logo at the top of the<br>page) will not be loaded again from the server on your second visit.<br>Your browser already has these files in the local cache, which saves<br>the loading time and bandwidth.</p><p>Within the Rails framework, your aim is to answer the question “Has a<br>page changed?” in the controller. Normally, most of the time is spent<br>on rendering the page in the view. I’d like to repeat that: most of the<br>time is spent on rendering the page in the view!</p><h4>Last-Modified</h4><p>The web browser knows when it has downloaded a resource (e.g., a web<br>page) and then placed it into its cache. On a second request, it can<br>pass this information to the web server in an If-Modified-Since:<br>header. The web server can then compare this information to the<br>corresponding file and either deliver a newer version or return an HTTP<br>304 Not Modified code as response. In the case of a 304, the web<br>browser delivers the locally cached version. Now you are going to say,<br>“That’s all very well for images, but it won’t help me at all for<br>dynamically generated web pages such as the index view of the<br>companies.” However, you are underestimating the power of Rails.<br>[Para, Type = Important, ID = Par41]</p><p><em>Please modify the times used in the examples in accordance with your own<br>circumstances.</em></p><p>Go ahead and edit the show method in the controller file<br>app/controllers/companies_controller.rb, as shown in Listing 14–7.</p><pre># GET /companies/1<br># GET /companies/1.json<br>def show<br> fresh_when last_modified: <a href="http://twitter.com/company">@company</a>.updated_at<br>end</pre><pre>Listing 14–7 app/controllers/companies_controller.rb</pre><p>After restarting the Rails application, take a look at the HTTP header<br>of <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a>, as shown here:</p><pre>$ curl -I <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a><br>HTTP/1.1 200 OK<br>X-Frame-Options: SAMEORIGIN<br>X-XSS-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>Last-Modified: Sat, 27 Jan 2018 18:38:05 GMT<br>[…]</pre><p>The Last-Modified entry in the HTTP header was generated by fresh_when<br>in the controller. If you later go to the same web page and specify<br>this time as well, then you do not get the web page back; you get a 304<br>Not Modified message, as shown here:</p><pre>$ curl -I <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a> — header<br>‘If-Modified-Since: Sat, 27 Jan 2018 18:38:05 GMT’<br>HTTP/1.1 304 Not Modified<br>[…]</pre><p>In the Rails log, you will find this:</p><pre>Started HEAD “/companies/1” for 127.0.0.1 at 2018–01–27 18:24:21 +0100<br>Processing by CompaniesController#show as */*<br> Parameters: {“id”=&gt;”1&quot;}<br> Company Load (0.1ms) SELECT “companies”.* FROM “companies” WHERE<br>“companies”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]<br>Completed 304 Not Modified in 2ms (ActiveRecord: 0.1ms)</pre><p>It took Rails 2ms on my machine to answer this request, compared to the<br>51ms of the standard variation. This is much faster! So, you have used<br>fewer resources on the server and saved a massive amount of bandwidth.<br>The user will be able to see the page much more quickly.</p><h4>etag</h4><p>Sometimes the update_at field of a particular object is not meaningful<br>on its own. For example, if you have a web page where users can log in<br>and this page then generates web page contents based on a role model,<br>it can happen that user A as the admin is able to see an Edit link that is not displayed to user B as a normal user. In such a scenario, the Last-Modified header explained earlier does not help. Actually, it would do harm.</p><p>In these cases, you can use the etag header. The etag is generated by the web server and delivered when the web page is first visited. If the user visits the same URL again, the browser can then check whether the corresponding web page has changed by sending an If-None-Match: query to the web server.</p><p>Please edit the index and show methods in the controller file<br>app/controllers/companies_controller.rb, as shown in Listing 14–8.</p><pre># GET /companies<br># GET /companies.json<br>def index<br> <a href="http://twitter.com/companies">@companies</a> = Company.all<br> fresh_when etag: <a href="http://twitter.com/companies">@companies</a><br>end<br># GET /companies/1<br># GET /companies/1.json<br>def show<br> fresh_when etag: <a href="http://twitter.com/company">@company</a><br>end</pre><pre>Listing 14–8 app/controllers/companies_controller.rb</pre><p>A special Rails feature comes into play for the etag: Rails automatically sets a new CSRF token for each new visitor of the web site. This prevents cross-site request forgery attacks <em>(see </em><a href="http://wikipedia.org/wiki/Cross_site_request_forgery"><em>http://wikipedia.org/wiki/Cross_site_request_forgery</em></a><em>)</em>. But it also means that each new user of a web page gets a new etag for the same page. To ensure that the same users also get identical CSRF tokens, these are stored in a cookie by the web browser and consequently sent back to the web server every time the web page is visited. You have to tell curl that you want to save all cookies in a file and transmit these cookies later if a request is received.</p><p>For saving, you use the -c cookies.txt parameter.</p><pre>$ curl -I <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> -c cookies.txt<br>HTTP/1.1 200 OK<br>X-Frame-Options: SAMEORIGIN<br>X-XSS-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>ETag: W/”53830a75ef520df8ad8e1894cf1e5003&quot;<br> […]</pre><p>With the parameter -b cookies.txt, curl sends these cookies to the web server when a request arrives. Now you get the same etag for two subsequent requests.</p><pre>$ curl -I <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> -b cookies.txt<br>HTTP/1.1 200 OK<br>X-Frame-Options: SAMEORIGIN<br>X-XSS-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>ETag: W/”53830a75ef520df8ad8e1894cf1e5003&quot;<br>[…]<br>$ curl -I <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> -b cookies.txt<br>HTTP/1.1 200 OK<br>X-Frame-Options: SAMEORIGIN<br>X-Xss-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>ETag: W/”53830a75ef520df8ad8e1894cf1e5003&quot;<br>[…]</pre><p>You now use this etag to find out in the request with If-None-Match if<br>the version you have cached is still up-to-date.</p><pre>$ curl -I <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> -b cookies.txt — header<br>‘If-None-Match: W/”53830a75ef520df8ad8e1894cf1e5003&quot;’<br>HTTP/1.1 304 Not Modified<br>X-Frame-Options: SAMEORIGIN<br>X-XSS-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>ETag: W/”53830a75ef520df8ad8e1894cf1e5003&quot;<br>[…]</pre><p>You get a 304 Not Modified in response. Let’s look at the Rails log.</p><pre>Started HEAD “/companies” for 127.0.0.1 at 2018–01–27 18:36:25 +0100<br>Processing by CompaniesController#index as */*<br> (0.2ms) SELECT COUNT(*) AS “size”, MAX(“companies”.”updated_at”) AS<br>timestamp FROM “companies”<br>Completed 304 Not Modified in 24ms (ActiveRecord: 0.2ms)</pre><p>Rails took only 24ms on my machine to process the request. Plus, you have saved bandwidth again. The user will be happy with the speedy web application.</p><p>Find more generic information about etag headers at<br><a href="https://en.wikipedia.org/wiki/HTTP_ETag">https://en.wikipedia.org/wiki/HTTP_ETag</a>.</p><h4>current_user and Other Potential Parameters</h4><p>As the basis for generating an etag, you can pass not just an object but also an array of objects. This way, you can solve the problem with the logged-in user who might get different content than a non-logged-in user. Let’s assume that a logged-in user is output with the method current_user.</p><p>You have to add etag { current_user.try :id } in app/controllers/application_controller.rb to make sure that all etags<br>in the application include the current_user.id value, which is nil if nobody is logged in, as shown in Listing 14–9.</p><pre>class ApplicationController &lt; ActionController::Base<br> etag { current_user.try :id }<br>end</pre><pre>Listing 14–9 app/controllers/application_controller.rb</pre><p>You can chain other objects in this array too and use this approach to<br>define when a page has not changed.</p><h3>The Magic of touch</h3><p>What happens if an employee is edited or deleted? Then the show view<br>and potentially the index view would have to change as well. That is<br>the reason for the following line in the employee model:</p><pre>belongs_to :company, touch: true</pre><p>Every time an object of the class Employee is saved in edited form and<br>if touch: true is used, ActiveRecord updates the superordinate Company<br>element in the database. The updated_at field is set to the current<br>time. In other words, it is “touched.”</p><p>This approach ensures that the correct content is delivered.</p><h4>stale?</h4><p>Up to now, I was assuming that only HTML pages are being delivered. So,<br>I showed how to use fresh_when and then do without the respond_to do<br>|format| block. But HTTP caching is not limited to HTML pages. What if<br>you want to render JSON, for example, as well and want to deliver it<br>via HTTP caching? You need to use the method stale?. Using stale?<br>resembles using the method fresh_when. Here’s an example:</p><pre>def show<br> if stale? <a href="http://twitter.com/company">@company</a><br> respond_to do |format|<br> format.html<br> format.json { render json: <a href="http://twitter.com/company">@company</a> }<br> end<br> end<br>end</pre><h3>Using Proxies (public)</h3><p>I have also been assuming you were using a cache on the web browser.<br>But on the Internet, there are many proxies that are often closer to<br>the user and can therefore be useful for caching in the case of<br>nonpersonalized pages. If the example is a publicly accessible phone<br>book, then you can activate the free services of the proxies with the<br>parameter public: true in fresh_when or with stale?.</p><p>Here’s an example:</p><pre># GET /companies/1<br># GET /companies/1.json<br>def show<br> fresh_when <a href="http://twitter.com/company">@company</a>, public: true<br>end</pre><p>You can go to the web page and get the output, as shown here:</p><pre>$ curl -I <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a><br>HTTP/1.1 200 OK<br>X-Frame-Options: SAMEORIGIN<br>X-XSS-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>ETag: W/”f37a06dbe0ee1b4a2aee85c1c326b737&quot;<br>Last-Modified: Sat, 27 Jan 2018 17:16:53 GMT<br>Content-Type: text/html; charset=utf-8<br>Cache-Control: public<br>[…]</pre><p>The header Cache-Control: public tells all proxies that they can also cache this web page.</p><p>Using proxies always has to be done with great caution. On the one hand, they are brilliantly suited for delivering your own web page quickly to more<br>users, but on the other hand, you have to be absolutely sure that no personalized pages are cached on public proxies. For example, CSRF tags<br>and flash messages should never end up in a public proxy. For CSRF tags, it is a good idea to make the output of csrf_meta_tag in the default app/views/layouts/application.html.erb layout dependent on the<br>question of whether the page may be cached publicly, as shown here:</p><pre>&lt;%= csrf_meta_tag unless response.cache_control[:public] %&gt;</pre><h3>Cache-Control with Time Limit</h3><p>When using etag and Last-Modified, you can assume that the web browser<br>definitely checks once more with the web server if the cached version of a web page is still current. This is a very safe approach.</p><p>But you can take the optimization one step further by predicting the future: if you am already sure when delivering the web page that this web page is not going to change in the next two minutes, hours, or days, then you can tell the web browser this directly. It then does not need to check back again within this specified period of time. This overhead savings has advantages, especially with mobile web browsers with relatively high latency. Plus, you save server load on the web server.</p><p>In the output of the HTTP header, you may already have noticed the corresponding line in the etag and Last-Modified examples, shown here:</p><pre>Cache-Control: max-age=0, private, must-revalidate</pre><p>The item must-revalidate tells the web browser that it should<br>definitely check back with the web server to see whether a web page has<br>changed in the meantime. The second parameter, private, means that only<br>the web browser is allowed to cache this page. Any proxies on the way are not permitted to cache this page.</p><p>If you decide for the phone book that the web page is going to stay unchanged for at least two minutes, then you can expand the code example by adding the method expires_in. The controller app/controllers/companies.rb will then contain the following code for the method show:</p><pre># GET /companies/1<br># GET /companies/1.json<br>def show<br> expires_in 2.minutes<br> fresh_when <a href="http://twitter.com/company">@company</a>, public: true<br>end</pre><p>Now you get a different cache control information in response to a request.</p><pre>$ curl -I <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a><br>HTTP/1.1 200 OK<br>X-Frame-Options: SAMEORIGIN<br>X-XSS-Protection: 1; mode=block<br>X-Content-Type-Options: nosniff<br>Date: Sat, 27 Jan 2018 17:58:56 GMT<br>ETag: W/”f37a06dbe0ee1b4a2aee85c1c326b737&quot;<br>Last-Modified: Sat, 27 Jan 2018 17:16:53 GMT<br>Content-Type: text/html; charset=utf-8<br>Cache-Control: max-age=120, public<br>[…]</pre><p>The two minutes are specified in seconds (max-age=120), and you no longer need must-revalidate. So, in the next 120 seconds, the web browser does not need to check back with the web server to see whether the content of this page has changed.</p><p>This mechanism is also used by the asset pipeline. Assets created there in the Production environment can be identified clearly by the checksum in the file name and can be cached for a long time both in the web browser and in public proxies. That’s why you have the following section in the Nginx configuration file:</p><pre>location ^~ /assets/ {<br> gzip_static on;<br> expires max;<br> add_header Cache-Control public;<br>}</pre><h3>Fragment Caching</h3><p>With fragment caching, you can cache individual parts of a view. You can safely use it in combination with HTTP caching and page caching.<br>The advantages, once again, are a reduction of server load and faster web page generation, which means increased usability.</p><p>Please create a new example application (see “The Example Application”).</p><h4>Enabling Fragment Caching in Development Mode</h4><p>Fragment caching is by default disabled in the Development environment.<br>You can activate it with the command rails dev:cache, which touches the<br>file tmp/caching-dev.txt.</p><pre>$ rails dev:cache<br>Development mode is now being cached.</pre><p>To deactivate caching, run the same command again (this will delete the<br>file tmp/caching-dev.txt).</p><pre>$ rails dev:cache<br>Development mode is no longer being cached.</pre><p>In production mode, fragment caching is enabled by default.</p><h3>Caching the Table of the Index View</h3><p>On the page <a href="http://localhost:3000/companies">http://localhost:3000/companies</a>, a computationally intensive table with all the companies is rendered. You can cache this table as a whole. To do so, you need to enclose the table in a &lt;% cache(‘name_of_cache’) do %&gt; … &lt;% end %&gt; block.</p><pre>&lt;% cache(‘name_of_cache’) do %&gt;<br>[…]<br>&lt;% end %&gt;</pre><p><em>Please edit the file app/views/companies/index.html.erb as shown in<br>Listing 14–10.</em></p><pre>&lt;h1&gt;Companies&lt;/h1&gt;<br>&lt;% cache(‘table_of_all_companies’) do %&gt;<br>&lt;table&gt;<br> &lt;thead&gt;<br> &lt;tr&gt;<br> &lt;th&gt;Name&lt;/th&gt;<br> &lt;th&gt;Number of employees&lt;/th&gt;<br> &lt;th colspan=”3&quot;&gt;&lt;/th&gt;<br> &lt;/tr&gt;<br> &lt;/thead&gt;<br> &lt;tbody&gt;<br> &lt;% <a href="http://twitter.com/companies">@companies</a>.each do |company| %&gt;<br> &lt;tr&gt;<br> &lt;td&gt;&lt;%= company.name %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= company.employees.count %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Show’, company %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Edit’, edit_company_path(company) %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Destroy’, company, method: :delete, data: {<br>confirm:<br> ‘Are you sure?’ } %&gt;&lt;/td&gt;<br> &lt;/tr&gt;<br> &lt;% end %&gt;<br> &lt;/tbody&gt;<br>&lt;/table&gt;<br>&lt;% end %&gt;<br>&lt;br /&gt;<br>&lt;%= link_to ‘New Company’, new_company_path %&gt;</pre><pre>Listing 14–10 app/views/companies/index.html.erb</pre><p>Then you can start the Rails server with rails server and go to the URL<br><a href="http://localhost:3000/companies">http://localhost:3000/companies</a>.</p><p>The first time, a page that has a fragment cache is a little bit slower because the cache has to be written. The second time it is a lot of faster.</p><h3>Deleting the Fragment Cache</h3><p>With the method expire_fragment, you can clear specific fragment caches. Basically, you can build this idea into the model in the same way as shown in the section “Deleting Page Caches Automatically.”</p><p>The model file app/models/company.rb will look like Listing 14–11.</p><pre>class Company &lt; ActiveRecord::Base<br> validates :name,<br> presence: true,<br> uniqueness: true<br> has_many :employees, dependent: :destroy<br> after_create :expire_cache<br> after_update :expire_cache<br> before_destroy :expire_cache<br> def to_s<br> name<br> end<br> def expire_cache<br> ActionController::Base.new.expire_fragment(‘table_of_all_companies’<br>)<br> end<br>end</pre><pre>Listing 14–11 app/models/company.rb</pre><p>Because the number of employees also has an effect on this table, you also have to expand the file app/models/employees.rb accordingly, as shown in Listing 14–12.</p><pre>class Employee &lt; ActiveRecord::Base<br> belongs_to :company, touch: true<br> validates :first_name,<br> presence: true<br> validates :last_name,<br> presence: true<br> validates :company,<br> presence: true<br> after_create :expire_cache<br> after_update :expire_cache<br> before_destroy :expire_cache<br> def to_s<br> “#{first_name} #{last_name}”<br> end<br> def expire_cache<br> ActionController::Base.new.expire_fragment(‘table_of_all_companies’<br>)<br> end<br>end</pre><pre>Listing 14–12 app/models/employees.rb</pre><p>Deleting specific fragment caches often involves a lot of effort in terms of programming. First, you often miss things; second, in big projects it’s not easy to keep track of all the different cache names. Often it is easier to automatically create names via the method cache_key. These then expire automatically in the cache.</p><h3>Auto-expiring Caches</h3><p>Managing fragment caching is rather complex with the naming convention<br>used in the section “Caching the Table of the Index View.” On the one hand, you can be sure that the cache does not have any superfluous ballast if you have programmed neatly, but on the other, it does not really matter. A cache is structured in such a way that it deletes old and no longer required elements on its own. If you use a mechanism that gives a fragment cache a unique name, as in the asset pipeline, then you do not need to go to the trouble of deleting fragment caches.</p><p>Rails has you covered. And it is pretty easy to do.</p><p>Let’s edit the index view in the file app/views/companies/index.html.erb, as shown in Listing 14–13.</p><pre>&lt;h1&gt;Companies&lt;/h1&gt;<br>&lt;% cache(<a href="http://twitter.com/companies">@companies</a>) do %&gt;<br>&lt;table&gt;<br> &lt;thead&gt;<br> &lt;tr&gt;<br> &lt;th&gt;Name&lt;/th&gt;<br> &lt;th&gt;Number of employees&lt;/th&gt;<br> &lt;th colspan=”3&quot;&gt;&lt;/th&gt;<br> &lt;/tr&gt;<br> &lt;/thead&gt;<br> &lt;tbody&gt;<br> &lt;% <a href="http://twitter.com/companies">@companies</a>.each do |company| %&gt;<br> &lt;tr&gt;<br> &lt;td&gt;&lt;%= company.name %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= company.employees.count %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Show’, company %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Edit’, edit_company_path(company) %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Destroy’, company, method: :delete, data: {<br>confirm:<br> ‘Are you sure?’ } %&gt;&lt;/td&gt;<br> &lt;/tr&gt;<br> &lt;% end %&gt;<br> &lt;/tbody&gt;<br>&lt;/table&gt;<br>&lt;% end %&gt;<br>&lt;br /&gt;<br>&lt;%= link_to ‘New Company’, new_company_path %&gt;</pre><pre>Listing 14–13 app/views/companies/index.html.erb</pre><p>You ask Rails to generate a cache key for @companies and use it. If you want to see the name of that cache key in your log, you have to add config.action_controller.enable_fragment_cache_logging = true in the<br>file config/environments/development.rb.</p><p>There is no general answer to the question of how much detail you should use<br>fragment caching. Do some experimenting with it and then look in the log to see how long things take.</p><h3>Russian Doll Caching</h3><p>In the previous example, you created one fragment cache for the whole table of companies. If one company within that table changes, the whole table has to be re-rendered. Depending on the kind of data, that might take a lot of time.</p><p>The idea of Russian doll caching is that you cache not only the whole table but each row of the table too. So, when one row changes, just this row has to be rendered; all other rows can be fetched from the cache. When done well, this can save a lot of resources.</p><p>Please take a look at the updated example, as shown in Listing 14–14.</p><pre>&lt;h1&gt;Companies&lt;/h1&gt;<br>&lt;% cache(<a href="http://twitter.com/companies">@companies</a>) do %&gt;<br>&lt;table&gt;<br> &lt;thead&gt;<br> &lt;tr&gt;<br> &lt;th&gt;Name&lt;/th&gt;<br> &lt;th&gt;Number of employees&lt;/th&gt;<br> &lt;th colspan=”3&quot;&gt;&lt;/th&gt;<br> &lt;/tr&gt;<br> &lt;/thead&gt;<br> &lt;tbody&gt;<br> &lt;% <a href="http://twitter.com/companies">@companies</a>.each do |company| %&gt;<br> &lt;% cache(company) do %&gt;<br> &lt;tr&gt;<br> &lt;td&gt;&lt;%= company.name %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= company.employees.count %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Show’, company %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Edit’, edit_company_path(company) %&gt;&lt;/td&gt;<br> &lt;td&gt;&lt;%= link_to ‘Destroy’, company, method: :delete, data: {<br>confirm: ‘Are you sure?’ } %&gt;&lt;/td&gt;<br> &lt;/tr&gt;<br> &lt;% end %&gt;<br> &lt;% end %&gt;<br> &lt;/tbody&gt;<br>&lt;/table&gt;<br>&lt;% end %&gt;<br>&lt;br /&gt;<br>&lt;%= link_to ‘New Company’, new_company_path %&gt;</pre><pre>Listing 14–14 app/views/companies/index.html.erb</pre><p>Change the Code in the View Results in an Expired Cache</p><p>Rails tracks an MD5 sum of the view you use. So if you change the file<br>(e.g., app/views/companies/index.html.erb), the MD5 changes, and all<br>the old caches will expire.</p><h3>Cache Store</h3><p>The cache store manages the stored fragment caches. If not configured<br>otherwise, this is the Rails MemoryStore. This cache store is good for developing but less suitable for a production system because it acts<br>independently for each Ruby on Rails process. So, if you have several<br>Ruby on Rails processes running in parallel in the production system,<br>each process holds its own MemoryStore.</p><h4>MemCacheStore</h4><p>Most production systems use memcached ([23]<a href="http://memcached.org/">http://memcached.org/</a>) as a<br>cache store. To enable memcached as a cache store in your production system, you need to add the line shown in Listing [24]14–15 in the file config/environments/production.rb.</p><pre>config.cache_store = :mem_cache_store</pre><pre>Listing 14–15 config/environments/production.rb</pre><p>The combination of appropriately used auto-expiring caches and memcached is an excellent recipe for a successful web page.</p><h4>Other Cache Stores</h4><p>In the official Rails documentation you will find a list of other cache<br>stores; see <a href="http://guides.rubyonrails.org/caching_with_rails.html#cache-stores">http://guides.rubyonrails.org/caching_with_rails.html#cache-stores</a>.</p><h3>Page Caching</h3><p>Page caching was removed from the core of Rails 4.0, but it is still available as a gem, and it is powerful. To do page caching, you need a bit of knowledge to configure your web server (e.g., Nginx or Apache). Page caching is not for the faint-hearted.</p><p>With page caching, it’s all about placing a complete HTML page (in other words, the render result of a view) into a subdirectory of the public directory and having it delivered directly from there by the web server (for example, Nginx) whenever the web page is visited next. Additionally, you can also save a compressed .gz version of the HTML page there. A production web server will automatically deliver files under public itself and can also be configured so that any .gz files present are delivered directly.</p><p>In complex views, that may take 500ms or even more for rendering; the<br>amount of time you save is of course considerable. As a web page operator, you once more save valuable server resources and can service more visitors with the same hardware. The web page user profits from a faster delivery of the web page.</p><p>When programming your Rails application, please ensure that you also update this page or delete it! You will find a description of how to do this in the section “Deleting the Page Caches Automatically.” Otherwise, you will end up with an outdated cache later.</p><p>Please also ensure that page caching rejects all URL parameters by default. For example, if you try to go to: <a href="http://localhost:3000/companies?search=abc">http://localhost:3000/companies?search=abc</a>, this automatically becomes <a href="http://localhost:3000/companies">http://localhost:3000/companies</a>. But that can easily be fixed with different route logic.</p><p>Please install a fresh example application (see the section “The Example Application”) and add the gem with the following line in Gemfile:</p><pre>gem ‘actionpack-page_caching’</pre><p>Now install it with the command bundle install.</p><pre>$ bundle install<br>[…]</pre><p>Lastly, you have to tell Rails where to store the cache files. Please add the line shown in Listing 14–16 in your config/application.rb file.</p><pre>config.action_controller.page_cache_directory =<br>“#{Rails.root.to_s}/public/deploy”</pre><pre>Listing 14–16 config/application.rb</pre><h4>Activating Page Caching in Development Mode</h4><p>First you need to go to the file config/environments/development.rb and<br>set the item config.action_controller.perform_caching to true, as shown in Listing 14–17.</p><pre>config.action_controller.perform_caching = true</pre><pre>Listing 14–17 config/environments/development.rb</pre><p>Otherwise, you cannot try page caching in development mode. In production mode, page caching is enabled by default.</p><h4>Configure Your Web Server</h4><p>Now you have to tell your web server (e.g., Nginx or Apache) that it should check the /public/deploy directory first before hitting the Rails application. You have to configure it so that it will deliver a .gz file if one is available.</p><p>There is no one perfect way of doing it. You have to find the best way of doing it in your environment on your own.</p><p>As a quick and dirty hack for development, you can set page_cache_directory<br>to public. Then your development system will deliver the cached page.</p><pre>config.action_controller.page_cache_directory =<br>“#{Rails.root.to_s}/public”</pre><h4>Caching the Company Index and Show View</h4><p>Enabling page caching happens in the controller. If you want to cache the show view for Company, you need to go to the controller<br>app/controllers/companies_controller.rb and enter the command caches_page :show at the top, as shown in Listing [28]14–18.</p><pre>class CompaniesController &lt; ApplicationController<br> caches_page :show<br>[…]</pre><pre>Listing 14–18 app/controllers/companies_controller.rb</pre><p>Before starting the application, the public directory looks like this:</p><pre>public/<br>+ — 404.html<br>+ — 422.html<br>+ — 500.html<br>+ — apple-touch-icon-precomposed.png<br>+ — apple-touch-icon.png<br>+ — favicon.ico<br>+ — robots.txt</pre><p>After starting the application with rails server and going to the URLs<br><a href="http://localhost:3000/companies">http://localhost:3000/companies</a> and <a href="http://localhost:3000/companies/1">http://localhost:3000/companies/1</a><br>via a web browser, it looks like this:</p><pre>public<br>+ — 404.html<br>+ — 422.html<br>+ — 500.html<br>+ — apple-touch-icon-precomposed.png<br>+ — apple-touch-icon.png<br>+ — deploy<br>| + — companies<br>| + — 1.html<br>+ — favicon.ico<br>+ — robots.txt</pre><p>The file public/deploy/companies/1.html has been created by page caching.</p><p>From now on, the web server will only deliver the cached versions when<br>these pages are accessed.</p><h3>gz Versions</h3><p>If you use page caching, you should also cache directly zipped .gz files. You can do this via the option :gzip =&gt; true or use a specific compression parameter as a symbol instead of true (for example,<br>:best_compression).</p><p>The controller app/controllers/companies_controller.rb will look like Listing 14–19 at the beginning.</p><pre>class CompaniesController &lt; ApplicationController<br> caches_page :show, gzip: true<br>[…]</pre><pre>Listing 14–19 app/controllers/companies_controller.rb</pre><p>This automatically saves a compressed version and an uncompressed<br>version of each page cache.</p><pre>public<br>+ — 404.html<br>+ — 422.html<br>+ — 500.html<br>+ — apple-touch-icon-precomposed.png<br>+ — apple-touch-icon.png<br>+ — deploy<br>| + — companies<br>| + — 1.html<br>| + — 1.html.gz<br>+ — favicon.ico<br>+ — robots.txt</pre><h3>The File Extension .html</h3><p>Rails saves the page accessed at <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> under the file name companies.html. So, the upstream web server will find and deliver this file if you go to <a href="http://localhost:3000/companies.html">http://localhost:3000/companies.html</a>, but not if you try to go to <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> because the extension .html at the end of the URL is missing.</p><p>If you are using the Nginx server, the easiest way to do this is to adapt the try_files instruction in the Nginx configuration file as follows:</p><pre>try_files $uri/index.html $uri $uri.html <a href="http://twitter.com/unicorn">@unicorn</a>;</pre><p>Nginx then checks if a file with the extension .html of the currently<br>accessed URL exists.</p><h3>Deleting Page Caches Automatically</h3><p>As soon as the data used in the view changes, the saved cache files<br>have to be deleted. Otherwise, the cache would no longer be up-to-date.</p><p>According to the official Rails documentation, the solution for this problem is the class ActionController::Caching::Sweeper. But this approach, described at <a href="http://guides.rubyonrails.org/caching_with_rails.html#sweepers">http://guides.rubyonrails.org/caching_with_rails.html#sweepers</a>, has<br>a big disadvantage: it is limited to actions that happen within the controller. So, if an action is triggered via URL by the web browser, the corresponding cache is also changed or deleted. But if an object is deleted in the console, for example, the sweeper would not realize this. For that reason, I will show you an approach that does not use a sweeper but works directly in the model with ActiveRecord callbacks.</p><p>In the phone book application, you always need to delete the cache for <a href="http://localhost:3000/companies">http://localhost:3000/companies</a> and <a href="http://localhost:3000/companies/company_id">http://localhost:3000/companies/company_id</a> when editing a company. When editing an employee, you also have to delete the corresponding cache<br>for the relevant employee.</p><h3>Models</h3><p>You still need to fix the models so that the corresponding caches are deleted automatically as soon as an object is created, edited, or deleted, as shown in Listing 14–20 and Listing 14–21.</p><pre>class Company &lt; ActiveRecord::Base<br> validates :name,<br> presence: true,<br> uniqueness: true<br> has_many :employees, dependent: :destroy<br> after_create :expire_cache<br> after_update :expire_cache<br> before_destroy :expire_cache<br> def to_s<br> name<br> end<br> def expire_cache<br> ActionController::Base.expire_page(Rails.application.routes.url_hel<br>pers.company_path(self))<br> ActionController::Base.expire_page(Rails.application.routes.url_hel<br>pers.companies_path)<br> end<br>end</pre><pre>Listing 14–20 app/models/company.rb</pre><pre>class Employee &lt; ActiveRecord::Base<br> belongs_to :company, touch: true<br> validates :first_name,<br> presence: true<br> validates :last_name,<br> presence: true<br> validates :company,<br> presence: true<br> after_create :expire_cache<br> after_update :expire_cache<br> before_destroy :expire_cache<br> def to_s<br> “#{first_name} #{last_name}”<br> end<br> def expire_cache<br> ActionController::Base.expire_page(Rails.application.routes.url_hel<br>pers.employee_path(self))<br> ActionController::Base.expire_page(Rails.application.routes.url_hel<br>pers.employees_path)<br> self.company.expire_cache<br> end<br>end</pre><pre>Listing 14–21 app/models/employee.rb</pre><h3>Preheating</h3><p>Now that you have read your way through this chapter, here is a final tip: preheat your cache!</p><p>For example, if you have a web application in a company and you know that at 9 a.m. all employees are going to log in and then access this web application, then it’s a good idea to let your web server go through all those views a few hours in advance with a cron job. At night, your server is probably bored anyway.</p><p>Check out the behavior patterns of your users. With public web pages, this can be done, for example, via Google Analytics (<a href="http://www.google.com/analytics/">www.google.com/analytics/</a>). You will find that at certain times of the day, there is a lot more traffic going in. If you have a quiet phase prior to this, you can use it to warm up your cache.</p><p>The purpose of preheating is to save server resources and achieve better quality for the user because the web page is displayed more quickly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*xTdDSCDf1enlPgV2Ap-_Bg.jpeg" /></figure><h3>Further Information</h3><p>The best source of information on this topic is in the Rails<br>documentation at: <a href="http://guides.rubyonrails.org/caching_with_rails.html">http://guides.rubyonrails.org/caching_with_rails.html</a>. There you can find additional information (e.g., low-level caching).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d72e1ddf848c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/rubyinside/https-medium-com-wintermeyer-caching-in-ruby-on-rails-5-2-d72e1ddf848c">Caching in Ruby on Rails 5.2</a> was originally published in <a href="https://medium.com/rubyinside">Ruby Inside</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[301 SEO Voodoo to avoid 404]]></title>
            <link>https://medium.com/@wintermeyer/301-seo-voodoo-to-avoid-404-7fd8279e0ec0?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/7fd8279e0ec0</guid>
            <category><![CDATA[seo]]></category>
            <category><![CDATA[google]]></category>
            <category><![CDATA[consulting]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Tue, 20 Feb 2018 17:08:15 GMT</pubDate>
            <atom:updated>2018-02-20T17:08:15.503Z</atom:updated>
            <content:encoded><![CDATA[<p>Most SEO consultants will run their tool of choice to give their new client a list of 404 pages which according to them have to be changed into 301 to save all the link juice (backlinks). It’s a low hanging fruit for them: They have minimal work with it because they use automated tools to generate it. The result paints a gruel picture of lost opportunities if the client does not move all 404s to 301s immediately. To them any 404 is a SEO coffin nail. Your Google ranking is doomed!</p><blockquote>Their argumentation is so plausible: <br>Every 404 loses incoming link juice (backlinks). Google will slap you hands. Obviously! Who in his right mind would use a 404 when a 301 would save all that beautiful link juice?!</blockquote><p>For any non-technical and even for the many technical clients their argument is valid and makes sense. <strong>But let’s take a deeper look into this.</strong></p><h3>What is a 404 or 301?</h3><p>Web server communicate with web browsers. One part of that communication is a status code. The most common one is 200 which says that the requested URL exists. If it doesn’t exist the web server answers with a <strong>404 (Not Found Error Message)</strong> error message. So the web browser understands that this URL doesn’t exist. It can display an error message. But most times the web server will transfer an error HTML file itself. This can contain anything. It doesn’t even have to say “404” anywhere. It is for humans only!</p><p>Sometimes files move on the server. They get renamed. And because a URL is kind of a file name itself there is a way to tell a web browser that a URL has moved to a different URL. Maybe someone started a blog and used this format for a web page: /articles/2017–02–28-foobar.html. But after some time he/she decides to use a different URL: /2017/02/28/articles/foobar.html. This would be a perfect example to use a <strong>301(Moved Permanently)</strong> which tells the web client that the URL has changed. So the web browser can use this information to fetch the content from the new URL. It’s like a forwarding request for your mail.</p><h3>The SEO Anti 404 Argument</h3><p>Let’s assume you sell apples and oranges in your online shop. A customer can get informations about apples and buy them at /products/apples. And a customer can get information about oranges and buy them at /products/oranges. One day you decide to not sell apples any more. Your SEO consultants says that countless webpages link to your /products/apples page. He can proof this to you with his fancy tools. He knows that Google calculates your search result position by that number of incoming links. So he wants you to not respond with a 404 but a 301 which redirects to /products/oranges. By that you can recycle the incoming apple link power for your oranges.</p><p>Great argumentation and logic! And thanks god nobody can argue against it. You can’t ask Google how they rate. Or can you? Actually you can and I did. I ask John Mueller who is Webmaster Trends Analyst at Google about exactly this problem:</p><h3>Stefan Wintermeyer on Twitter</h3><p>@JohnMu Assuming I sell apples and oranges online. Many people link to my /products/apples and /products/oranges. Now I stop selling apples. SEO consultants tell me to 301 on /products/apples but I feel like 404 or even 410. SEO c. tell me I&#39;ll lose energy/juice/whatever. Right?</p><p>He answered right away:</p><h3>John ☆.o(≧▽≦)o.☆ on Twitter</h3><p>@wintermeyer If people want apples, why would you show them oranges? If they&#39;re the same kind of product, then that (301) might make sense, but if people want something different, then I&#39;d treat them as something different (404).</p><p>But a Antonio Sangio smelled deception and asked a follow up question:</p><h3>Antonio Sangiao on Twitter</h3><p>@JohnMu @wintermeyer But if @wintermeyer has 404 in his website, Google will penalize his website, right?</p><p>See! Antonio rubbed salt into the wound. Google will penalize this website. For sure. Obviously! But wait! Here’s John answer:</p><h3>John ☆.o(≧▽≦)o.☆ on Twitter</h3><p>@pasquinoweb @wintermeyer No, definitely not. Returning a 404 for a missing page is something a technically correct, high-quality website should do.</p><p>Wait a minute! So a 404 is correct and a misleading 301 is not?! Who would have thought of this!</p><blockquote>John Mueller of Google says that you should return a correct 404 for /products/apples without the fear of being penalized! Google will not penalize correct behavior.</blockquote><h3>Did your SEO guy lie to you?</h3><p>No, he probably didn’t lie. At least not by purpose. He just doesn’t understand his business. He follows the lead of all the other SEO consultants he knows. Like a lemming following his fellow lemmings.</p><p>SEO consultants love to use FUD (<a href="https://en.wikipedia.org/wiki/Fear,_uncertainty_and_doubt">Fear, uncertainty and doubt</a>). And the whole 301 vs. 404 argument is about your biggest fear: Losing Google search result ranking.</p><h3>Let’s use our Brain</h3><p>We know that Google counts the amount of incoming links to rank your webpage. So if e.g. 500 other webpages link you your /products/apples webpage it means that this page must be important. It must be an important resource for all things “apples”. By using a 301 to redirect them to /products/oranges you will water down the relevance of your orange page. Because people expect some sort of information about apples and not oranges. <strong>So by using a 301 you will not just not save any link juice but harm your existing ranking for oranges!</strong> Let that sink in for a moment.</p><p>By using a 404 you tell Google and any other search engine that this URL doesn’t exist any more. Google will still calculate the overall importance of your online shop high because of those 500 incoming links even when they result in a 404. But Google will send less “apple” requests to your page. Which makes sense since you stopped selling apples.</p><h3>404 can still deliver a human readable web page!</h3><p>So we established that you return a 404 for /products/apples because you stopped selling apples. But for those humans who click on that incoming link you can display a page which says “Sorry, we don’t sell apples any more but have a look at our great oranges! Just click on /products/oranges for a complete list.”</p><p>By that you don’t lose the human but you give Google the needed information.</p><h3>What about 302?</h3><p>In short: 301 means for ever and 302 means just for a while. But since we established the fact that you do not want to 301 any of your apple requests it doesn’t matter anyhow.</p><p>BTW: There is no “for ever” for a search engine. It will try less often but will never not try it at all.</p><h3>Conclusion</h3><p>It’s very easy: If you stop selling a product or offering a service of any sort than you want to deliver a 404 for that URL. Keep using 301 but only if a URL has changed. Not if it doesn’t exist any more.</p><p>And next time your SEO consultant aka Lemming brings up the “use 301 and not 404” fairy tale you’ll give him the URL of this article.</p><p>PS: Let me close with a link to my business homepage so that Google adds this to my ranking: <a href="https://www.wintermeyer-consulting.de">Best SEO advise ever!</a> SCNR. ;-)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pCG7Fg3Hzonz6dwS4xsb3g.jpeg" /><figcaption>medium.com just loves images in their stories. So here is a photo I took a couple of days ago. It’s still winter in Germany.</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7fd8279e0ec0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Goodbye Secrets. Welcome Credentials!]]></title>
            <link>https://medium.com/@wintermeyer/goodbye-secrets-welcome-credentials-f4709d9f4698?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/f4709d9f4698</guid>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Sun, 28 Jan 2018 13:00:17 GMT</pubDate>
            <atom:updated>2019-12-13T13:46:34.359Z</atom:updated>
            <content:encoded><![CDATA[<p>Secrets were introduced with Ruby on Rails 5.1 to make life easier for developers who need to store encrypted credentials or API keys in their repository. Many people were confused by the way it had to be done. It did include a couple of extra steps and an often unnecessary seperation of different environments. I have no opinion about this but I do agree that the new way of achieving it in Ruby on Rails 5.2 is a lot easier. <a href="https://medium.com/u/54bcbf647830">DHH</a> got rid of the secrets and introduced credentials (<a href="https://github.com/rails/rails/pull/30067">read his PR for more information</a>).</p><blockquote>Kudos to <a href="https://medium.com/u/54bcbf647830">DHH</a> and the core team for doing this. Most projects would have stick with the old way because it was kind of ok.</blockquote><p>Once you installed Rails 5.2 each of your new Rails projects has an already good to go setup for using credentials. No more generating keys manually. The important master key is automatically generated and stored in the file config/master.key which can be shared with other developers in the team but which should never be checked into the Git repository. The default .gitignore has been updated accordingly:</p><pre># Ignore master key for decrypting credentials and more.<br>/config/master.key</pre><h3>Editing Credentials</h3><p>All credentials are stored encrypted in the file config/credentials.yml.enc. Obviously you can not edit the file directly. You have to use the command rails credentials:editto edit them. For that to work you have to set the shell environment variable EDITOR first. Or you can do both with this one liner in your Bash shell:</p><pre>$ <strong>EDITOR=vim rails credentials:edit</strong></pre><p>Now you can edit your credentials in yaml format. In this example I add a credential with the name foobar and the value test:</p><pre># aws:<br># access_key_id: 123<br># secret_access_key: 345</pre><pre># Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.<br>secret_key_base: 9846dad34a3168…68d634f<br>foobar: test</pre><h3>Accessing Credentials</h3><p>You can access a credential anywhere in your application with AppName::Application.credentials.name_of_the_credential. An example from within the console:</p><pre>$ <strong>rails console</strong><br>Running via Spring preloader in process 19662<br>Loading production environment (Rails 5.2.0)<br>&gt;&gt; <strong>Shop::Application.credentials.foobar</strong><br>=&gt; “test”<br>&gt;&gt; <strong>exit</strong></pre><p>If you like this post I’d like to ask you for a favour: <br>Create an account at my open-source business network <a href="https://www.vutuv.de">https://www.vutuv.de</a></p><p>Thank you and see you there!</p><h3>Server</h3><p>To use the credentials in production you have to copy the config/master.keyfile to your production environment or setting it up with an environment variable.</p><h3>Screencast</h3><p>I’m a big fan of screencasts too. So here it is:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/1*j79kZckQfIv5S1fzS2BgIw.gif" /><figcaption>Screencast for Credentials in Ruby on Rails 5.2</figcaption></figure><h3>More?</h3><p>In case you need on site Ruby on Rails training: <br>Please send me an email to sw@wintermeyer-consulting.de</p><p>I’m currently working on my <a href="http://amzn.to/2FEL4QX">new Ruby on Rails 5.2 book</a>. You can follow me on Twitter to get updates too: <a href="https://twitter.com/wintermeyer">https://twitter.com/wintermeyer</a></p><p>In case you speak German: <a href="https://www.wintermeyer-consulting.de">https://www.wintermeyer-consulting.de</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/350/1*xGzCRz9KcdWpqUR9L6yXkg.jpeg" /><figcaption>Cover of my Ruby on Rails 5.2 book</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4709d9f4698" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rails 5.2 and Ruby 2.5 install How-To]]></title>
            <link>https://medium.com/@wintermeyer/rails-5-2-and-ruby-2-5-install-how-to-bc287f3dacef?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/bc287f3dacef</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[how-to]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[macos]]></category>
            <category><![CDATA[debian]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Fri, 19 Jan 2018 13:25:55 GMT</pubDate>
            <atom:updated>2019-12-13T13:48:05.183Z</atom:updated>
            <content:encoded><![CDATA[<p>As part of writing my new <a href="https://amzn.to/2Q246XI">Learn Rails 5.2</a> book I had to update it’s install how-to for Debian and macOS. For something that seems to be so basic there is a constant stream of changes in the procedure (e.g. this time the dirmng package for Debian).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cukgTCgqP07KggnC7FIVRw.jpeg" /><figcaption>medium.com says that a story always should have an image. So here is a photo of my son climbing. You’re welcome. ;-)</figcaption></figure><p>I always use <a href="http://rvm.io/">RVM</a> to install Ruby. Why should you too?</p><ul><li>You simply do not have any root rights on the system. In that case,<br>you have no other option.</li><li>You want to run several Ruby or Rails versions that are separated cleanly. Maybe not today but for a future system upgrades it is always handy to have the old version still available in case you have to roll back. This can be easily done with RVM.</li></ul><h3>Ruby on Rails 5.2 on Debian 9.3 (Stretch)</h3><p>This description assumes that you have a freshly installed Debian<br>GNU/Linux 9.3 (“Stretch”). You will find an ISO image for the<br>installation at <a href="http://www.debian.org">http://www.debian.org</a>. I recommend the approximately 250<br>MB net installation CD image. For instructions on how to install<br>Debian-GNU/Linux, please go to <a href="http://www.debian.org/distrib/netinst">http://www.debian.org/distrib/netinst</a>.</p><p>If you have root rights on the target system, you can use the following<br>commands to ensure that all required programs for a successful<br>installation of RVM are available. If you do not have root rights, you<br>have to either hope that your admin has already installed everything you<br>need, or send them a quick e-mail with the corresponding lines.</p><p>Login as root, update the package lists and upgrade the system:</p><pre>root@debian:~# apt-get update<br>[..]<br>root@debian:~# apt-get upgrade</pre><p>Installation of the packages required for the RVM installation:</p><pre>root@debian:~# apt-get -y install curl gawk g++ \<br>make libreadline6-dev zlib1g-dev libssl-dev \<br>libyaml-dev libsqlite3-dev sqlite3 autoconf \<br>libgdbm-dev libncurses5-dev libtool bison nodejs \<br>pkg-config libffi-dev libgmp-dev libgmp-dev git \<br>dirmngr</pre><p>Now is a good time to log out as root:</p><pre>root@debian:~# exit<br>logout<br>xyz@debian:~$</pre><p>Log in with your normal user account (in our case, it’s the user `xyz`) and run the following commands:</p><pre>xyz@debian:~$ gpg — keyserver hkp://keys.gnupg.net --recv-keys \<br>409B6B1796C275462A1703113804BB82D39DC0E3 \<br>7D2BAF1CF37B13E2069D6956105BD0E739499BDB<br>[…]<br>xyz@debian:~$ curl -sSL <a href="https://get.rvm.io">https://get.rvm.io</a> | bash<br>[…]</pre><p>To be able to use RVM you need to run a script first.<br>rvm will tell you which script (it’s path depends on the user name):</p><pre>xyz@debian:~$ source /home/xyz/.rvm/scripts/rvm</pre><p>Now you can use RVM to install Ruby 2.5 and after that Rails 5.2 with gem:</p><pre>xyz@debian:~$ rvm install 2.5<br>[…]<br>xyz@debian:~$ gem install rails<br>[…]</pre><p>gem install rails installs the current stable Rails version. You can use the<br> format gem install rails -v 5.2.0 to install a specific version and gem install rails — pre to install a current beta version.</p><p>RVM, Ruby 2.5 and Rails 5.2 are now installed. You can check it<br>with the following commands:</p><pre>xyz@debian:~$ ruby -v<br>ruby 2.5.0p0 (2017–12–25 revision 61468) [x86_64-linux]<br>xyz@debian:~$ rails -v<br>Rails 5.2.0<br>xyz@debian:~$</pre><p>If you like this post I’d like to ask you for a favour: <br>Create an account at my open-source business network <a href="https://www.vutuv.de">https://www.vutuv.de</a></p><p>Thank you and see you there!</p><h3>Ruby on Rails 5.2 on macOS 10.13 (High Sierra)</h3><p>macOS 10.13 includes Ruby by default. Not interesting for our purposes.<br>We want Ruby 2.5 and Rails 5.2. To avoid interfering with the existing<br>Ruby and Rails installation and therefore the packet management of Mac<br>OS X, we install Ruby 2.5 and Rails 5.2 with RVM (Ruby Version Manager).</p><p>Before you start installing Ruby on Rails, you must install the _latest_<br>Apple Xcode tools on your system. The easiest way is via the Mac App<br>Store (search for “xcode”) or via the website<br><a href="https://developer.apple.com/xcode/">https://developer.apple.com/xcode/</a></p><p>Please take care to install all the command line tools!</p><p>Log in with your normal user account (in our case, it’s the user `xyz`) and run the following commands:</p><pre>$ gpg — keyserver hkp://keys.gnupg.net --recv-keys \<br>409B6B1796C275462A1703113804BB82D39DC0E3 \<br>7D2BAF1CF37B13E2069D6956105BD0E739499BDB<br>[…]<br>$ curl -sSL <a href="https://get.rvm.io">https://get.rvm.io</a> | bash<br>[…]</pre><p>rvm will tell you a source command which you can run to setup rvm <br>for your current shell/terminal. Most times it is just easier to close <br>the current shell and open a new terminal window. Than everything in <br>the new terminal is setup properly.</p><pre>$ rvm install 2.5<br>[…]<br>$ gem install rails<br>[…]</pre><p>gem install rails installs the current stable Rails version. You can use the<br> format gem install rails -v 5.2.0 to install a specific version and gem install rails — pre to install a current beta version.</p><p>RVM, Ruby 2.5 and Rails 5.2 are now fully installed. You can check it<br>with the following commands.</p><pre>$ ruby -v<br>ruby 2.5.0p0 (2017–12–25 revision 61468) [x86_64-darwin17]<br>$ rails -v<br>Rails 5.2.0</pre><p>Have fun with Rails 5.2!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bc287f3dacef" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nginx with Brotli on Debian Stretch]]></title>
            <link>https://medium.com/@wintermeyer/nginx-with-brotli-on-debian-stretch-2917b1147aec?source=rss-ba3ffab1ae74------2</link>
            <guid isPermaLink="false">https://medium.com/p/2917b1147aec</guid>
            <category><![CDATA[nginx]]></category>
            <category><![CDATA[compression]]></category>
            <category><![CDATA[how-to]]></category>
            <category><![CDATA[debian]]></category>
            <category><![CDATA[web-performance]]></category>
            <dc:creator><![CDATA[Stefan Wintermeyer]]></dc:creator>
            <pubDate>Wed, 06 Sep 2017 16:02:05 GMT</pubDate>
            <atom:updated>2017-09-07T07:55:43.924Z</atom:updated>
            <content:encoded><![CDATA[<p>You’ve probably heard about <a href="https://github.com/google/brotli">Brotli</a>. It’s an <a href="https://en.wikipedia.org/wiki/Brotli">open source data compression library</a> which can be used to deliver smaller compressed versions of HTML, CSS and JavaScript files. Think of it as a better alternative to gzip. It’s a big deal for anybody who wants to improve WebPerformance. To use it the browser has to support it (Chrome and Firefox already do) and the web-server has to support it. But Debian’s official Nginx doesn’t yet. There are a couple of hack-ish how-tos which describe how to compile a customised version but that is a safe way into admin hell. If you run a clean Debian server you want to have clean packages which are able to be replaced in the future by the official packages (once they support brotli). We need a clean interim solution. But do not worry! I have you covered. This how-to describes a clean way to solve this problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w7be5g9PTFcUwOwCNVzWCA.jpeg" /><figcaption>Brotli is named after a <a href="https://en.wikipedia.org/wiki/Bread_roll#Europe">Swiss bakery product, brötli</a>. Photo by <a href="https://unsplash.com/photos/RXySkOTi3kk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">James Harris</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h3>The Build Environment</h3><p>I assume you run a stable and up-to-date Debian Stretch (9.1) system. You must be root for this how-to. Probably you already have some of the following packages installed. But to be safe run the following commands:</p><pre>apt-get install git<br>apt-get build-dep nginx</pre><h3>Install the Brotli packages</h3><p>Stretch provides a stable Brotli package which we install first:</p><pre>apt-get install brotli</pre><p>But we need the development libraries too and those are only available in experimental Debian. That is still better than compiling them by ourself so we add this line to the <strong>/etc/apt/sources.list</strong> file:</p><pre>deb <a href="http://deb.debian.org/debian">http://deb.debian.org/debian</a> experimental main</pre><p>Now we can install it:</p><pre>apt-get update<br>apt-get install libbrotli-dev/experimental</pre><p>Once this package becomes stable we want to replace it with the stable version.</p><h3>The ngx_brotli Modul</h3><p>Next we have to compile the Nginx Brotli module:</p><pre>cd /opt<br>git clone <a href="https://github.com/google/ngx_brotli">https://github.com/google/ngx_brotli</a><br>cd /opt/ngx_brotli<br>git submodule update —- init</pre><p>Once there is an official Debian module for this we have to delete the directory <strong>/opt/ngx_brotli</strong> and install the official package.</p><h3>Nginx Debian Package</h3><p>We have to download the Nginx Debian package sources:</p><pre>cd /usr/src<br>mkdir nginx<br>cd nginx<br>apt-get source nginx<br>cd nginx-1.10.3</pre><p>The “1.10.3” part may vary depending on the time you are using this how-to. Please do an <strong>ls</strong> to get the right directory name.</p><p>Use your favourite editor to open the file <strong>debian/rules</strong> and search the part where <strong>extras</strong> are defined (nginx comes in a couple of different flavours in Debian we are going to use the extra version which has all the batteries included). Add this line there:</p><pre> —-add-module=/opt/ngx_brotli</pre><p>It’s time to build all the Debian packages. That might take a moment:</p><pre>cd /usr/src/nginx/nginx-1.10.3/<br>dpkg-buildpackage -b</pre><p>Once the packages are build we can install them:</p><pre>cd ..<br>dpkg -i nginx-common_*.deb libnginx-mod*_amd64.deb nginx-extras*_amd64.deb</pre><p>Once Debian provides official Nginx packages which support Brotli you can easily replace those with them.</p><h3>Configure Nginx</h3><p>Open <strong>/etc/nginx/nginx.conf</strong> with you editor and add the following configuration in the <strong>http</strong> block of it:</p><pre>brotli on;<br>brotli_comp_level 6;<br>brotli_static on;<br>brotli_types text/html text/plain text/css application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon image/vnd.microsoft.icon image/bmp image/svg+xml;</pre><p>Lastly you have to restart nginx:</p><pre>service nginx restart</pre><p>Now you are good to go. Your Nginx delivers pre-compressed files with the file extension .br (brotli_static on;) and compresses everything else on the fly.</p><h3>Only with SSL</h3><p>Chrome and Firefox only ask for Brotli compression for <em>https://</em> connections. So be sure to configure SSL next. And while you are doing that add HTTP/2.</p><h3>Shameless Plug</h3><p>In case you need WebPerformance, Ruby on Rails or Phoenix Framework consulting or training please don’t hesitate to send me an email to sw@wintermeyer-consulting.de or a DM at <a href="https://twitter.com/wintermeyer">https://twitter.com/wintermeyer</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2917b1147aec" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>