<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Peter Gundel</title>
    <description>The latest articles on Forem by Peter Gundel (@peterfication).</description>
    <link>https://forem.com/peterfication</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F13923%2F7c01cd9a-858e-417e-8841-06a619bef2f0.png</url>
      <title>Forem: Peter Gundel</title>
      <link>https://forem.com/peterfication</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/peterfication"/>
    <language>en</language>
    <item>
      <title>RSpec GraphQL integration testing</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Mon, 16 Jan 2023 09:35:19 +0000</pubDate>
      <link>https://forem.com/peterfication/rspec-graphql-integration-testing-1m2l</link>
      <guid>https://forem.com/peterfication/rspec-graphql-integration-testing-1m2l</guid>
      <description>&lt;p&gt;While working on different Ruby projects, I noticed one pattern when writing integration tests for GraphQL: You write your query in a multiline string, get the response, parse it (probably with a helper) and write some expectations, maybe even expecting a whole multi-dimensional &lt;code&gt;Hash&lt;/code&gt;. This could then look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Query.currentUser"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:query_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;MySchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;as_json&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;current_user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;GRAPHQL&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      query {
        currentUser {
          id
          email
        }
      }
&lt;/span&gt;&lt;span class="no"&gt;    GRAPHQL&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:expected_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"data"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"currentUser"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"email"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;as_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"returns the current user"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For small queries, this is fine. But for big queries (and hence, big responses) this gets unhandy very fast. This is subjective of course ;)&lt;/p&gt;

&lt;p&gt;Another issue is that we can't leverage the GraphQL language server while writing/maintaining these integration tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solution to this
&lt;/h2&gt;

&lt;p&gt;I decided to use this opportunity to write my first gem: &lt;a href="https://github.com/peterfication/rspec-graphql-integration" rel="noopener noreferrer"&gt;&lt;code&gt;rspec-graphql-integration&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gem tries to improve this situation by moving the query and the response in their own files with a proper file type. This way, the integration test files are smaller and can focus on mocking data/instances. Also, the GraphQL language server will give you autocompletion/linting in your GraphQL files (if you've set up your editor for it).&lt;/p&gt;

&lt;p&gt;The simple integration test from above then looks like this:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;current_user_spec.rb&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"Query.currentUser"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;current_user: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:response_variables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user_email: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;is_expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;match_graphql_response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;code&gt;current_user.graphql&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;currentUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;code&gt;current_user.json&lt;/code&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currentUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{user_id}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{user_email}}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ruby</category>
      <category>rspec</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Tech documentation culture at store2be</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Thu, 09 May 2019 11:51:37 +0000</pubDate>
      <link>https://forem.com/peterfication/tech-documentation-culture-at-store2be-11m9</link>
      <guid>https://forem.com/peterfication/tech-documentation-culture-at-store2be-11m9</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3dF1VsV7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5qfgovtpkmyggtjexyww.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3dF1VsV7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/5qfgovtpkmyggtjexyww.jpg" alt="Our documenation setup" width="880" height="587"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Our documentation setup (photo by Michael D Beckwith, flickr)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Ink is better than the best memory. 🎓&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the subtitle of our tech documentation!&lt;/p&gt;




&lt;p&gt;If you haven't thought about documentation in a small team, with a growing team you eventually realise how important documentation is. Even for one person alone, it takes a lot of cognitive load to remember all the knowledge when it is not written down. In a team, documentation is even more important and acts as a communication tool and ensures that everyone has the same understanding of certain topics. Documentation also helps a lot in conducting non-automated regular tasks that occur in a low frequency.&lt;/p&gt;

&lt;p&gt;In this article, we want to share how we approach documentation at store2be. Documentation happens on different levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company-wide documentation&lt;/li&gt;
&lt;li&gt;Tech documentation&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code"&gt;Infrastructure as code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;App-specific documentation&lt;/li&gt;
&lt;li&gt;Code documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything that is of importance for all departments needs to go into our company-wide documentation. For example, if something technical is important for the product team as well, we will put it in the company-wide documentation.&lt;/p&gt;

&lt;p&gt;The tech documentation is for all the technical things that are only relevant for the engineering and the DevOps team. In this part of the documentation, there is also frontend and backend related documentation that is shared across all our apps, so we don't have to repeat it in these repositories.&lt;/p&gt;

&lt;p&gt;Our tech documentation is a Git repository with markdown files. This has the advantage that we can easily write it offline and use our favourite tools to edit and search the documentation. Also, everyone has always a local copy of the whole documentation present.&lt;/p&gt;

&lt;p&gt;Infrastructure as code happens, when you use tools to describe your infrastructure in a declarative way and you don't manage your infrastructure manually. Some tools that we use in that context are &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; and &lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If something specific to an app needs to be documented, we will mostly put it into the README.md of the respective Git repository. Examples here are eg. app specific scripts/commands and what they are there for.&lt;/p&gt;

&lt;p&gt;We won't go into details about code documentation, because this is something a whole blog post on its own can talk about. We want to mention it for completeness here anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Documentation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vU8IppK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0jkin7jao4v5ozzyh3vi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vU8IppK4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0jkin7jao4v5ozzyh3vi.png" alt="The Tech Handbook" width="800" height="509"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Tech Handbook&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The main topic of this blog post is our tech documentation. In this part of the documentation, we collect the most knowledge of the engineering team and this is what helps us scale the engineering team. We call it: &lt;em&gt;The Tech Handbook&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Looking back into 2017, we had nearly no tech documentation at all. We were still just a team of three and we hadn't thought too much about documentation at that point. In hindsight, this is super bad and something we will avoid in the future, because now we know how valuable proper documentation is.&lt;/p&gt;

&lt;p&gt;By the end of 2017, we had three people joining in the beginning of 2018 and we knew, we had to share a lot of knowledge with them. That's when we began the groundwork of our Tech Handbook.&lt;/p&gt;

&lt;p&gt;In the beginning, we focused on the areas that we needed to share with the new developers from day one, eg. processes and tools. The infrastructure sections followed quickly, as infrastructure work is mostly done once and then looked at again a lot later when you forgot a lot of it probably.&lt;/p&gt;

&lt;p&gt;Since then we continuously add new things along the way, and documentation has become part of our tech culture.&lt;/p&gt;

&lt;p&gt;Keeping documentation up to date is a whole different challenge. At store2be, it's a collective effort. As soon as we discuss for example process changes, we assign someone with the task of updating the relevant part in the tech documentation via our project management tool. This helps us organizing documentation changes together with our normal development workflow. Thankfully, we use Git and GitHub for our Tech Handbook, everyone else in the team can review the documentation changes and comment on them. So in the end, we're all on the same page again.&lt;/p&gt;

&lt;p&gt;We divided our Tech Handbook into the following areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;General&lt;/li&gt;
&lt;li&gt;Processes and tools&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;Backend&lt;/li&gt;
&lt;li&gt;Frontend&lt;/li&gt;
&lt;li&gt;Post mortems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These sections should be all pretty self-explanatory. The last section, post mortems, is the newest section that we added in autumn 2018. We realized that most of the time the same people worked on finding and fixing critical issues in our systems without much transparency to the rest of the team. We occasionally had small presentations of what caused the problem, but if a similar problem occurred again, the person working on it had to remember it. This approach doesn't scale well, and even if the same person worked on a similar problem again, this person might have forgotten parts of the previous solution.&lt;/p&gt;

&lt;p&gt;With the post mortems in our Tech Handbook, we can now share the steps on how we were able to debug an issue easily, and because everyone reviews it, everyone gets a feeling for possible issues and can account for it while coding.&lt;/p&gt;

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

&lt;p&gt;All in all, we are very happy with our choice of building up our Tech Handbook and can really recommend it to everyone. It gives us a lot of safety regarding not losing knowledge, especially if someone goes on holidays. Also, onboarding new people has become way easier and faster.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>culture</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Tested Elasticsearch on Rails with Docker</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Fri, 02 Nov 2018 15:03:24 +0000</pubDate>
      <link>https://forem.com/peterfication/tested-elasticsearch-on-rails-with-docker-2chi</link>
      <guid>https://forem.com/peterfication/tested-elasticsearch-on-rails-with-docker-2chi</guid>
      <description>&lt;p&gt;Recently, the product team has been mentioning the need for a better search solution in our apps. That’s why I took the time to look into Elasticsearch after a two year break. For me, it was amazing to see how easy it was to set up a proof of concept that was even integrated into CI. The last part is quite important for us because we are very keen on testing in general ‚Äî especially when it comes to CI.&lt;/p&gt;

&lt;p&gt;Our development setup is already running on docker compose, which while having some disadvantages of course, mostly regarding speed, has the benefit that you can share and spin up the whole infrastructure without installing anything but the docker related things. If you only have a Rails app with PostgreSQL this might not be worth it. Sharing your development infrastructure via code however also gives you the advantage of sharing the correct version of PostgreSQL with the team. Also, the more things you add to the mix, the more annoying it gets to spin everything up. For instance, we use Redis for caching, sessions and our worker queue: yet another infrastructure dependency we have to share across the team.&lt;/p&gt;

&lt;p&gt;Having our development and CI setup already on docker made adding Elasticsearch to our app super easy! &lt;strong&gt;I won't go into details about how to use Elasticsearch with Rails, &lt;a href="https://medium.com/@tranduchanh.ms/global-full-text-search-experiences-in-rails-with-aws-elasticsearch-ea3b47a00a80" rel="noopener noreferrer"&gt;because there are other articles doing this well already&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only one thing that I wasn‚Äôt getting right from the beginning: The &lt;code&gt;match&lt;/code&gt; parameter of searchkick (Ruby library around Elasticsearch, used in the article mentioned above). You might want to think about whether you need &lt;code&gt;word_start&lt;/code&gt; or &lt;code&gt;word_middle&lt;/code&gt; when deciding how to match your queries ;)**&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup of Elasticsearch
&lt;/h2&gt;

&lt;p&gt;I needed to add Elasticsearch to our development environment and to our CI setup. For the local environment, we use docker compose. So this addition to our &lt;code&gt;docker-compose.yml&lt;/code&gt; was needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="s"&gt;...&lt;/span&gt;
  &lt;span class="s"&gt;elasticsearch&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/elasticsearch/elasticsearch:6.4.2&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
     &lt;span class="s"&gt;ports&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9200:9200"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells docker compose to spin up an Elasticsearch container and hook it up via DNS to our app container. So you just need to set environment variable &lt;code&gt;ELASTICSEARCH_URL: http://elasticsearch:9200&lt;/code&gt; and you are good to go.&lt;/p&gt;

&lt;p&gt;For CI we use &lt;a href="https://circleci.com/blog/build-cicd-piplines-using-docker/#configyml-file" rel="noopener noreferrer"&gt;CircleCI with Docker&lt;/a&gt;. There it was just a one-liner to get it up and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
      &lt;span class="s"&gt;- image&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/elasticsearch/elasticsearch:6.4.2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that‚Äôs it!&lt;/p&gt;

&lt;p&gt;I won‚Äôt talk about Elasticsearch in production here, because this is a whole other thing of course and you should think about using a managed solution if you don‚Äôt have any experience with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the search endpoint
&lt;/h2&gt;

&lt;p&gt;We use RSpec for testing. This is how a basic test of the search would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# searches_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;blank?&lt;/span&gt;
      &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="no"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:query&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# searches_spec.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;SearchController&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'#execute'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'works'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="s1"&gt;'Alexa Shopping Center'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# The query is misspelled on purpose!&lt;/span&gt;
      &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'/search?query=Alxa'&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_http_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="ss"&gt;data: &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;title: &lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},],&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also use &lt;a href="https://github.com/vcr/vcr" rel="noopener noreferrer"&gt;VCR&lt;/a&gt; for recording HTTP requests to external APIs for future test runs. However, the Elasticsearch calls are local and can be executed during tests. That‚Äôs why we needed to add an exception to VCR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ignore_hosts&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'elasticsearch'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why not just use PostgreSQL full text search?
&lt;/h2&gt;

&lt;p&gt;There are &lt;a href="http://rachbelaid.com/postgres-full-text-search-is-good-enough/" rel="noopener noreferrer"&gt;good&lt;/a&gt; &lt;a href="https://robots.thoughtbot.com/implementing-multi-table-full-text-search-with-postgres" rel="noopener noreferrer"&gt;articles&lt;/a&gt; out there that explain how to enable a similar full-text search in PostgreSQL without adding another dependency to the infrastructure. This is definitely an option as well!&lt;/p&gt;

&lt;p&gt;Following these tutorials was not as easy as it was for me to setup Elasticsearch with searchkick, there is still a lot of setup required, even though it‚Äôs only code and not infrastructure. The option of using a managed Elasticsearch service is great in that it doesn‚Äôt add many operational difficulties. The argument for Elasticsearch in my opinion is the reduced complexity of development, however we are still just trying things out when it comes taking our app search to the next level and might go for a PostgreSQL solution in the end.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com" rel="noopener noreferrer"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retail space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com" rel="noopener noreferrer"&gt;the store2be tech page&lt;/a&gt; or follow us on &lt;a href="https://twitter.com/store2be_tech" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>elasticsearch</category>
      <category>docker</category>
    </item>
    <item>
      <title>Email templates at store2be and GDPR — How we migrated away from Sendwithus</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Thu, 14 Jun 2018 14:35:31 +0000</pubDate>
      <link>https://forem.com/peterfication/email-templates-at-store2be-and-gdprhow-we-migrated-away-from-sendwithus-4go7</link>
      <guid>https://forem.com/peterfication/email-templates-at-store2be-and-gdprhow-we-migrated-away-from-sendwithus-4go7</guid>
      <description>&lt;p&gt;When &lt;a href="https://www.store2be.com"&gt;store2be&lt;/a&gt; started 3 years ago we were searching for a nice way to handle email templating and sending. I came across &lt;a href="https://www.sendwithus.com"&gt;Sendwithus&lt;/a&gt;, an email template service that connects to a lot of different email providers, like SendGrid, Mailjet, etc.&lt;/p&gt;

&lt;p&gt;We decided to use Sendwithus as it decoupled the email templating from our main application and allowed non developers to handle email template changes. Furthermore, it was very helpful to have different email sending providers integrated automatically. Once, we had to switch the email provider and it took a matter of minutes with Sendwithus.&lt;/p&gt;

&lt;p&gt;Now that the GDPR is coming into effect we have to evaluate all the services we use and check whether they are compliant. In February, Sendwithus informed its users about their way of handling GDPR compliance:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unfortunately, we will not be making Sendwithus GDPR compliant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was a bummer for us. Although they now provide a new, compliant service, back then we started looking for a solution right away hearing from Sendwithus that they would not be attempting GDPR compliance. So we tried to find another service that matched our requirements but we weren’t successful.&lt;/p&gt;




&lt;p&gt;At store2be, we are very keen on code quality and the tools around it (testing, linting, etc.). This was always a problem with Sendwithus. It kind of worked, but we were never sure whether we were going to break something and reviews only happened visually and not by looking at the actual code. Also, there was no nice Git history of the changes. Finally, there were a lot of hacks to get around the limitations of the templating possibilities of Sendwithus, eg. regarding snippets.&lt;/p&gt;

&lt;p&gt;In the end we decided to move email templating into the hands of the developers again. The main reason for that might be the fact that Mailjet open sourced its email template markup language, &lt;a href="https://mjml.io"&gt;MJML&lt;/a&gt;, which makes writing HTML email templates super easy. In the frontend we are mainly developing with React in Typescript and Jest for tests. This seemed like a perfect fit for this project regarding code quality, testability and ease of use.&lt;/p&gt;

&lt;p&gt;Of course we lose one very important attribute with that approach: All email template changes have to be done by developers again.&lt;/p&gt;




&lt;p&gt;The open source project &lt;a href="https://github.com/inventid/maily"&gt;Maily&lt;/a&gt; provided a lot of inspiration on how to get started with this service (&lt;a href="https://medium.com/@Rogier.Slag/creating-emails-with-the-maily-api-a-how-to-part-1-7f63306a7ad4"&gt;here is a Medium post about it from the creator of Maily&lt;/a&gt;). Unfortunately, it is not maintained anymore and my issues and PRs are not being tackled. But in its core, Maily is just one file that creates the express server. So we copied this file into our repository and adjusted it to our needs (moving it to Typescript, satisfying the linter, updating MJML, adding more functionality).&lt;/p&gt;

&lt;p&gt;This is what we are working with now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typescript:&lt;/strong&gt; All our code for the email templates is in Typescript. Hence, a lot of bugs are caught early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linter:&lt;/strong&gt; We use TSLint to comply to a coding standard we like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prettier:&lt;/strong&gt; We use Prettier to format our code. No discussions about each individual coding style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; All components (snippets and email templates) are unit and snapshot tested. This means every developer feels confident about changing an email template. Furthermore, we use &lt;a href="https://github.com/cetra3/lorikeet"&gt;lorikeet&lt;/a&gt; for integration tests. This adds an additional layer of safety we hadn’t thought about in the beginning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localization:&lt;/strong&gt; We use a very simple approach where each email template has a JSON file with the keys for each language we want to support. So the actual React component does not contain any literals but uses the translate function that reads this JSON file. Both, TXT and HTML templates use the same translations which reduces the possibility of inconsistencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview:&lt;/strong&gt; For development, you make GET request to the local express server (without hot reloading at the moment) to see a preview of the email. Online, the product team can do the same with the staging or production server. Furthermore, we have Swagger definitions for the email templates that can be transformed into Postman collections which makes the life of the product team even easier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review:&lt;/strong&gt; All code at store2be is reviewed. This also applies to the new email template service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in all, we are very happy with our decision of developing the email template service ourselves. Email templates are finally fun to work with.&lt;/p&gt;

&lt;p&gt;Here is how an email template could look like now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateFetchLocale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Closing&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Closing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Footer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Footer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;FullWidthBorder&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/FullWidthBorder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Greeting&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Greeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Layout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Title&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/html/snippets/Title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;locales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;templates/locales/Welcome.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Welcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SFC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WelcomeProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.store2be.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchLocale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;generateFetchLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fetchLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Greeting&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;lastname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fetchLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;welcome_please_confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fetchLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fetchLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button_not_working&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Text&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Closing&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FullWidthBorder&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Footer&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Welcome&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6VPjZL6l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://tech.store2be.com/images/email-template.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6VPjZL6l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://tech.store2be.com/images/email-template.jpg" alt="Example email preview."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retail space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com"&gt;the store2be tech page&lt;/a&gt; or follow us on &lt;a href="https://twitter.com/store2be_tech"&gt;Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>email</category>
      <category>mjml</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>How to use SweetAlert2 for your Rails +5.1 (rails-ujs) confirms without jQuery</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Sat, 02 Dec 2017 15:16:01 +0000</pubDate>
      <link>https://forem.com/peterfication/how-to-use-sweetalert2-for-your-rails-51-rails-ujs-confirms-withoutjquery-67h</link>
      <guid>https://forem.com/peterfication/how-to-use-sweetalert2-for-your-rails-51-rails-ujs-confirms-withoutjquery-67h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xzt8s4s5h613rb4ovrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xzt8s4s5h613rb4ovrq.png" alt="Screenshot SweetAlert2" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR see &lt;a href="https://github.com/store2be/rails-sweetalert" rel="noopener noreferrer"&gt;this demo project&lt;/a&gt; for the final solution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please be aware that the examples here are provided with ES6 syntax. &lt;a href="https://stackoverflow.com/a/42464363" rel="noopener noreferrer"&gt;There are different ways to get ES6 to work in Rails&lt;/a&gt;. The demo project has an &lt;a href="https://github.com/store2be/rails-sweetalert/tree/master-es5" rel="noopener noreferrer"&gt;ES5 branch&lt;/a&gt; for reference.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;If you are reading this I assume you are familiar with &lt;a href="http://rubyonrails.org/" rel="noopener noreferrer"&gt;Ruby on Rails&lt;/a&gt; and &lt;a href="https://limonte.github.io/sweetalert2/" rel="noopener noreferrer"&gt;SweetAlert2&lt;/a&gt;. With Rails before version 5.1, &lt;a href="https://blog.bigbinary.com/2017/06/20/rails-5-1-has-dropped-dependency-on-jquery-from-the-default-stack.html" rel="noopener noreferrer"&gt;when rails-ujs was still jquery-ujs&lt;/a&gt;, there was an easy way &lt;a href="http://alexzerbach.com/use-sweet-alert-rails/" rel="noopener noreferrer"&gt;to hook up SweetAlert (or SweetAlert2)&lt;/a&gt; with &lt;a href="https://stackoverflow.com/questions/16668949/how-to-add-confirm-message-with-link-to-ruby-on-rails" rel="noopener noreferrer"&gt;the Rails confirm functionality&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One way to achieve it was to overwrite the confirm handler of Rails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Override the default confirm dialog of Rails&lt;/span&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleConfirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;showSweetAlertConfirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We had this solution in one of our apps and I wanted to use the new &lt;code&gt;rails-ujs&lt;/code&gt;. My first thought was, that it should be an easy task to adapt. Just change &lt;code&gt;$.rails.&lt;/code&gt; into &lt;code&gt;Rails.&lt;/code&gt; and we’re good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Override the default confirm dialog of Rails&lt;/span&gt;
&lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleConfirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;showSweetAlertConfirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it turns out some things have changed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Rails.handleConfirm&lt;/code&gt; can be overwritten, but that will not override the event listener that is already attached since &lt;code&gt;rails-ujs&lt;/code&gt; was initialised. But no problem, let’s just write our own event handler and plug it into the new &lt;code&gt;rails-ujs&lt;/code&gt; way of doing things. If you have a look at &lt;a href="https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs/start.coffee" rel="noopener noreferrer"&gt;the source code of the start part of &lt;code&gt;rails-ujs&lt;/code&gt;&lt;/a&gt; you see how event listeners are created. The code to add an event listener for our own method then looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Do your thing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[data-confirm-swal]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, cool. It works for links with the attribute &lt;code&gt;data-confirm-swal="Are you sure?"&lt;/code&gt; now 🎉 …&lt;strong&gt;but wait&lt;/strong&gt;, if you have a delete link, the confirm dialog never shows up because the method never gets called. 🤔 Turns out the event listener for &lt;code&gt;method: :delete&lt;/code&gt; is called earlier because it got initialised before our event listener for SweetAlert2. This is because the event listeners of &lt;code&gt;rails-ujs&lt;/code&gt; are hooked up directly when evaluating the code.&lt;/p&gt;

&lt;p&gt;If we want to add our event listener by requiring our Javascript code before &lt;code&gt;rails-ujs&lt;/code&gt;, then we get into the Problem that &lt;code&gt;Rails.delegate&lt;/code&gt; is not defined yet. When you require &lt;code&gt;rails-ujs&lt;/code&gt; this is what’s happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define event handlers and &lt;code&gt;Rails.delegate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fire &lt;code&gt;rails:attachBindings&lt;/code&gt; event on document&lt;/li&gt;
&lt;li&gt;Attach event handlers (&lt;code&gt;Rails.start&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in order to get in between the definition of &lt;code&gt;Rails.delegate&lt;/code&gt; and the execution of &lt;code&gt;Rails.start&lt;/code&gt; we have to attach to the &lt;code&gt;rails:attachBindings&lt;/code&gt; event. (For that to happen we need to require our script &lt;em&gt;before&lt;/em&gt; &lt;code&gt;rails-ujs&lt;/code&gt;!)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rails:attachBindings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[data-confirm-swal]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 Now everything works as expected 🎉&lt;/p&gt;




&lt;p&gt;For the final solution &lt;a href="https://github.com/store2be/rails-sweetalert" rel="noopener noreferrer"&gt;have a look at this demo project&lt;/a&gt; (with ES5 and ES6 version) or see the code below (only ES6).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This file has to be required before rails-ujs&lt;/span&gt;
&lt;span class="c1"&gt;// To use it change `data-confirm` of your links to `data-confirm-swal`&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;allowAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopEverything&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-confirm-swal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;showConfirmationDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Display the confirmation dialog&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showConfirmationDialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-confirm-swal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;swal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Are you sure?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;showCancelButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;confirmButtonText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cancelButtonText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cancel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;confirmed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;confirmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// User clicked confirm button&lt;/span&gt;
      &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-confirm-swal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Hook the event before the other rails events so it works togeter&lt;/span&gt;
  &lt;span class="c1"&gt;// with `method: :delete`.&lt;/span&gt;
  &lt;span class="c1"&gt;// See https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts/rails-ujs/start.coffee#L69&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rails:attachBindings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[data-confirm-swal]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;To find this all out it took me some hours as I wasn’t familiar with the codebase of &lt;code&gt;rails-ujs&lt;/code&gt;. But I learnt a lot along the way. Hopefully with this writeup I can help some other developers as well who want to use the latest version of Rails with SweetAlert2 and without jQuery.&lt;/p&gt;




&lt;p&gt;If you use &lt;a href="https://github.com/rails/webpacker" rel="noopener noreferrer"&gt;Webpacker&lt;/a&gt;, there is an easy way to get in between the &lt;code&gt;rails-ujs&lt;/code&gt; code and the start script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rails&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rails-ujs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Do your thing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Add event listener before the other Rails event listeners like the one&lt;/span&gt;
&lt;span class="c1"&gt;// for `method: :delete`&lt;/span&gt;
&lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delegate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a[data-confirm-swal]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleConfirm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com" rel="noopener noreferrer"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retail space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com" rel="noopener noreferrer"&gt;the store2be tech page&lt;/a&gt; or follow our &lt;a href="https://medium.com/store2be-tech" rel="noopener noreferrer"&gt;Medium channel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>sweetalert2</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Heroku CLI meets fzf</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Mon, 14 Aug 2017 09:14:53 +0000</pubDate>
      <link>https://forem.com/peterfication/heroku-cli-meets-fzf</link>
      <guid>https://forem.com/peterfication/heroku-cli-meets-fzf</guid>
      <description>&lt;p&gt;While working with tmux and &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt; I came across another interesting fzf use case: Use a fuzzy finder to simplify the use of the &lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you have some apps on Heroku and now and then you want to tail their logs or you want to ssh into one of them. There is one problem with that: You always have to memorize the exact app name in order to run &lt;code&gt;$ heroku logs -t -a awesome-app-staging&lt;/code&gt;. Or was it &lt;code&gt;staging-awesome-app&lt;/code&gt;? Well, you see the problem with that. Another solution might be to define an alias for every app. However, this doesn’t scale too well…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But what about a fuzzy finder for your Heroku apps in the CLI? 🤖&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tech.store2be.com/vim/2017/04/18/finally-switching-to-vim/" rel="noopener noreferrer"&gt;When I switched to vim&lt;/a&gt;, I first heard about fzf and began using it with joy to open my files. I recently started using tmux and there fzf can be a great help as well. &lt;a href="https://stackoverflow.com/questions/37730996/tmux-script-for-fast-window-switching-with-fzf-tmux-gives-me-the-wrong-options" rel="noopener noreferrer"&gt;This stackoverflow post&lt;/a&gt; showed me how to use fzf for switching between tmux windows. I also used this approach to switch between tmux sessions (and shared the solution in the linked stackoverflow post). This eventually lead to the idea of using fzf in combination with the Heroku CLI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstore2be.github.io%2Fimages%2Fheroku-cli-with-fzf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstore2be.github.io%2Fimages%2Fheroku-cli-with-fzf.jpg" alt="Heroku CLI with fzf"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The magic command for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku apps --all | grep '(' | sed 's/ .*$//' | fzf --header='Select the app you want to tail the logs' | xargs heroku logs -t -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course you should create aliases for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alias heroku-logs="heroku apps --all | grep '(' | sed 's/ .*$//' | fzf --header='Select the app you want to tail the logs' | xargs heroku logs -t -a"
alias heroku-bash="heroku apps --all | grep '(' | sed 's/ .*$//' | fzf --header='Select the app you want to bash into' | xargs heroku logs -t -a"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step by step explanation
&lt;/h2&gt;

&lt;p&gt;The first command should be self explanatory. It gives you a list of all your apps on Heroku. But the format here is important! The command returns two lists. Your own apps and the apps on which you are a collaborator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku apps --all

=== peterfication@example.com Apps
obscure-tundra-15966 (us)
nameless-basin-19036 (eu)

=== Collaborated Apps
shrouded-fortress-18816 (eu)          someone-else@example.com

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

&lt;/div&gt;






&lt;p&gt;First, we remove all the lines by piping the output to grep. Every line with an app contains brackets so we use the brackets to filter the lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku apps --all | grep '('

obscure-tundra-15966 (us)
nameless-basin-19036 (eu)
shrouded-fortress-18816 (eu)          someone-else@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Next, we have to ensure that we only take the app name with nothing else. So we remove everything after the app name, starting with the empty space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku apps --all | grep '(' | sed 's/ .*$//'

obscure-tundra-15966
nameless-basin-19036
shrouded-fortress-18816
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This is a nice list we can pipe to fzf. The result of fzf is then piped to the heroku command that we want to execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku apps --all | grep '(' | sed 's/ .*$//' | fzf --header='Select the app you want to bash into' | xargs heroku logs -t -a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After selecting the desired app with fzf, the command finally expands for example to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku logs -t -a shrouded-fortress-18816
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💪&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com" rel="noopener noreferrer"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retails space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com" rel="noopener noreferrer"&gt;the store2be tech page&lt;/a&gt; or follow our &lt;a href="https://medium.com/store2be-tech" rel="noopener noreferrer"&gt;Medium channel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>heroku</category>
      <category>fzf</category>
      <category>productivity</category>
    </item>
    <item>
      <title>So you switched to Vim - Don't forget to install Vimium for Chrome!</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Wed, 02 Aug 2017 14:58:13 +0000</pubDate>
      <link>https://forem.com/peterfication/so-you-switched-to-vim---dont-forget-to-install-vimium-for-chrome</link>
      <guid>https://forem.com/peterfication/so-you-switched-to-vim---dont-forget-to-install-vimium-for-chrome</guid>
      <description>&lt;p&gt;A few months ago I wrote about my first steps with Vim. Now I can finally say: I am faster with Vim than I ever have been with RubyMine 🎉 As soon as you get used to the Vim key bindings you demand them from every other program as well. In my mail program for example I try doing the most things with shortcuts now, too.&lt;/p&gt;

&lt;p&gt;And recently I started using &lt;a href="https://chrome.google.com/webstore/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb" rel="noopener noreferrer"&gt;the Chrome Plugin Vimium&lt;/a&gt;. It makes surfing the web much more efficient if you are used to the Vim key bindings already.&lt;/p&gt;

&lt;p&gt;The equivalent plugin for Firefox is &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/vimfx/" rel="noopener noreferrer"&gt;VimFx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are my top 3 Vimium features&lt;/p&gt;

&lt;p&gt;&lt;em&gt;J&lt;/em&gt;/&lt;em&gt;K&lt;/em&gt; lets you go to the previous/next tab (Way easier to type compared to &lt;em&gt;&amp;lt;Ctrl-Tab&amp;gt;&lt;/em&gt;/&lt;em&gt;&amp;lt;Ctrl-Shift-Tab&amp;gt;&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstore2be.github.io%2Fimages%2Fvimium%2Ftab-switcher.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstore2be.github.io%2Fimages%2Fvimium%2Ftab-switcher.jpg" alt="fzf-like tab switcher"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;T&lt;/em&gt; gives you an &lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;fzf&lt;/a&gt;-like tab switcher. With &lt;em&gt;&amp;lt;Ctrl-j/k&amp;gt;&lt;/em&gt; you can go up and down in the results (like in fzf). Similarly, o gives you an fzf-like menu for your history and bookmarks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstore2be.github.io%2Fimages%2Fvimium%2Flink-opener.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstore2be.github.io%2Fimages%2Fvimium%2Flink-opener.jpg" alt="fast link opener"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;f&lt;/em&gt;/&lt;em&gt;F&lt;/em&gt; gives you letters in overlays for every link of the page and opens the link in the same tab/new tab.&lt;/p&gt;




&lt;p&gt;Vimium has even more features. &lt;a href="https://chrome.google.com/webstore/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb" rel="noopener noreferrer"&gt;So check it out :)&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com" rel="noopener noreferrer"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retails space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com" rel="noopener noreferrer"&gt;the store2be tech page&lt;/a&gt; or follow our &lt;a href="https://medium.com/store2be-tech" rel="noopener noreferrer"&gt;Medium channel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vim</category>
    </item>
    <item>
      <title>Finally switching to vim</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Wed, 19 Apr 2017 14:33:36 +0000</pubDate>
      <link>https://forem.com/peterfication/finally-switching-to-vim</link>
      <guid>https://forem.com/peterfication/finally-switching-to-vim</guid>
      <description>&lt;p&gt;This article is just a selection of things I found helpful when switching to vim. So when someone asks for help because he or she wants to switch to vim, I just pass him or her the link to this article. Some of the links in this article also describe why you should switch to vim. So I won't get into this here.&lt;/p&gt;




&lt;p&gt;Before I started using vim, I thought switching would be just a timeframe of 2-4 weeks of being less productive and learning vim and then I would have it all set up and I am used to the vim style of doing things. But then I realized switching to vim is more like a long and ongoing journey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most important thing about vim is that you can't just start with a complete-guide-to-vim and then you're good to go. It is a steady process of learning new things (and be overwhelmed by them). The secret in the beginning is to give yourself some time to get up to speed and understand the brilliance behind the concepts of vim.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;I recommend to start by &lt;a href="https://www.youtube.com/watch?v=_NUO4JEtkDw"&gt;watching this introduction talk to vim by Mike Coutermash&lt;/a&gt;. He describes really good what I was experience as well when starting with vim. Here are also his &lt;a href="https://mikecoutermarsh.com/learning-vim-in-a-week/"&gt;blog article&lt;/a&gt; and &lt;a href="https://mikecoutermarsh.com/boston-vim-learning-vim-in-a-week/"&gt;his slides for the talk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to get a first impression &lt;a href="http://www.openvim.com/"&gt;I started with an interactive online tutorial&lt;/a&gt;. This tutorial provides a very good interactive introduction to the principles of vim. &lt;a href="http://www.viemu.com/a-why-vi-vim.html"&gt;And this is a very good read about the why of vim.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that I realized that I need a list of all these movement commands. &lt;a href="https://bitbucket.org/tednaleid/vim-shortcut-wallpaper/raw/tip/vim-shortcuts2560x1600.png"&gt;Here is a cheat sheet to start with&lt;/a&gt;. Or even better you print out a more complete vim cheat sheet: &lt;a href="https://rumorscity.com/wp-content/uploads/2014/08/10-Best-VIM-Cheat-Sheet-02.jpg"&gt;This&lt;/a&gt; or &lt;a href="http://michael.peopleofhonoronly.com/vim/vim_cheat_sheet_for_programmers_print.png"&gt;this&lt;/a&gt;. The vim wiki has &lt;a href="http://vim.wikia.com/wiki/Category:Getting_started"&gt;a getting started page&lt;/a&gt; that I found quite useful.&lt;/p&gt;

&lt;p&gt;In order to train the basic movements you can start playing &lt;a href="https://vim-adventures.com/"&gt;VIM Adventures&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mhinz/vim-galore"&gt;This Github repository&lt;/a&gt; provides a long guide to vim.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://vimcasts.org/episodes/archive/"&gt;Here are 68 free screencasts about vim&lt;/a&gt; and &lt;a href="https://thoughtbot.com/upcase/onramp-to-vim"&gt;here are two more free ones and other paid ones&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You also might wanna have a look at &lt;a href="https://neovim.io/"&gt;neovim&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vim has a very rich ecosystem of plugins. There are multiple ways of installing plugins. I started with &lt;a href="https://github.com/tpope/vim-pathogen"&gt;pathogen.vim&lt;/a&gt; and it was ok. But my coworkers showed me the advantages of &lt;a href="https://github.com/junegunn/vim-plug"&gt;vim-plug&lt;/a&gt; and as they are using vim-plug I switched, too.&lt;/p&gt;

&lt;p&gt;Then you have to decide which plugins to use (and not to use 😉) and how to customize your settings and keymaps. As a starting point I will list some plugins someone might have a look at. The most important thing here is: read through the documentation of each plugin and decide whether you need it or not!&lt;/p&gt;

&lt;p&gt;Here are some plugins that I found useful for the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tpope/vim-sensible"&gt;vim-sensible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tpope/vim-surround"&gt;vim-surround&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/airblade/vim-gitgutter"&gt;vim-gitgutter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/scrooloose/nerdtree"&gt;nerdtree&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And every programming language normally has a vim plugin on its own.&lt;/p&gt;

&lt;p&gt;Finally, &lt;a href="http://www.vimgolf.com/"&gt;start playing vimgolf&lt;/a&gt; from the beginning. It's never too early to start (I had that thought). Just look at the other solutions and try to understand what they did.&lt;/p&gt;




&lt;p&gt;After one week of vim I'm not as productive as with RubyMine. If I really need to get something done fast I still open RubyMine. But most of the time I use vim now and I enjoy working with it. Two more weeks and RubyMine stays closed. What I miss the most is the awesome multi cursor implementation of RubyMine. But I already have been told &lt;a href="https://medium.com/@schtoeffel/you-don-t-need-more-than-one-cursor-in-vim-2c44117d51db"&gt;that in vim you do things differently&lt;/a&gt; 😉&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retails space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com"&gt;the store2be tech page&lt;/a&gt; or follow our &lt;a href="https://medium.com/store2be-tech"&gt;Medium channel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vim</category>
    </item>
    <item>
      <title>Introduction to Metabase</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Thu, 06 Apr 2017 16:55:20 +0000</pubDate>
      <link>https://forem.com/peterfication/introduction-to-metabase</link>
      <guid>https://forem.com/peterfication/introduction-to-metabase</guid>
      <description>&lt;p&gt;When you start a company, setting up a business intelligence (BI) solution is not the first thing most founding teams implement, I guess. Now that I'm past the step of implementing such a solution, I think it is something that should be implemented in the very early days to build a culture of measuring everything.&lt;/p&gt;

&lt;p&gt;The main reason why it wasn't implemented directly from the beginning at my company was that no one had the experience with a good solution yet. Therefore, reports were conducted manually against the database in raw SQL or implemented into the internal admin area to make them a little bit more accessible (but without graphs of course because it would have cost too much time). As you might guess, this was some work and couldn't be changed or extended fast.&lt;/p&gt;

&lt;p&gt;In the fall of 2015, &lt;a href="https://aws.amazon.com/blogs/aws/amazon-quicksight-fast-easy-to-use-business-intelligence-for-big-data-at-110th-the-cost-of-traditional-solutions/"&gt;AWS QuickSight was announced&lt;/a&gt;. It seemed to be a very good solution for us as our databases are hosted on AWS. In August 2016 we got access to the preview version. It wasn't really easy to use and the user management was not really good. Reports and datasources weren't shared accross users or I didn't find the right way to do it. In most cases we use UUIDs as our primary keys and AWS QuickSight had no support for UUIDs back then. This lead to the situation that I didn't put more time and effort in using AWS QuickSight. When &lt;a href="https://aws.amazon.com/blogs/aws/amazon-quicksight-now-generally-available-fast-easy-to-use-business-analytics-for-big-data/"&gt;AWS QuickSight was finally released&lt;/a&gt;, I took another look at it but I still wasn't satisfied with the look and feel. (This is very subjective and might be different if I gave it another try today!)&lt;/p&gt;

&lt;p&gt;At the same time of the release of AWS QuickSight, my former CTO at &lt;a href="https://www.fotograf.de"&gt;fotograf.de&lt;/a&gt;, &lt;a href="https://github.com/beinbm"&gt;Marco Beinbrech&lt;/a&gt;, showed me their BI solution, an open source software called &lt;a href="http://www.metabase.com/"&gt;Metabase&lt;/a&gt;. He told me that it is super simple to setup and easy to use for all team members (especially for non-technical team members ðŸ˜‰).&lt;/p&gt;

&lt;p&gt;And indeed, he is right. &lt;a href="http://www.metabase.com/start/"&gt;Metabase provides different ways to deploy it anywhere.&lt;/a&gt; Most of our servers were at Heroku then and Metabase provides a one click deployment to Heroku. Pretty neat! But at that time we were also undertaking our first steps with Kubernetes and Metabase seemed to a be a perfect fit to play around with Kubernetes. So we wrote the Kubernetes objects for it and voilÃ , it was up and running. No hassle at all!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further setup
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A collection of things you should consider when setting up Metabase&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Using the production PostgreSQL user creates a security risk because this user can change data in the database. As Metabase is only for reporting, and therefore reading data, we created a read-only PostgreSQL user for our production database.&lt;/p&gt;

&lt;p&gt;It is not a good idea to give Metabase direct access to your production database. When you create huge and slow queries, it will directly affect your production servers. Fortunately, &lt;a href="https://aws.amazon.com/rds/details/read-replicas/"&gt;AWS RDS makes it super easy to create a read replica of your production database.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Metabase needs a database it can read and write from for itself in order to manage data about the users, reports and so on. To make the database of Metabase persistent across Kubernetes deployments, we created another database on our RDS instance. This way, the Metabase data is also backed up by our current backup procedure. You just have to provide the PostgreSQL connection details to the Metabase deployment in Kubernetes and it works out of the box. Then you can create and rebuild the cluster without loosing any Metabase internal data.&lt;/p&gt;

&lt;p&gt;We use Google Apps at my company and Metabase has a Google OAuth login built in. &lt;a href="http://www.metabase.com/docs/v0.23.1/administration-guide/10-single-sign-on.html"&gt;So you just have to follow the guide&lt;/a&gt; and all your colleagues can register with their Google Apps account.&lt;/p&gt;

&lt;p&gt;User management can be handled with user groups and collections (folders for your reports in Metabase). Marco gave me the tip to set up collections and and user groups according to the department structure. This way you can implement user access on a collection level very easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  First steps
&lt;/h2&gt;

&lt;p&gt;I really recommend &lt;a href="http://www.metabase.com/docs/v0.23.1/"&gt;reading the docs of Metabase&lt;/a&gt;. They are really good, especially for the first steps.&lt;/p&gt;

&lt;p&gt;In Metabase reports are called questions. So in order to create the first report you click on &lt;code&gt;New Question&lt;/code&gt;, select the database, select the table, add filters, specify what you want to see (eg. count of users) and add a grouping (eg. created_at by month). And all of that in a simple click interface that can be used by a lot of users and not only the SQL pros in the company. (It still gives you the possibility to create custom SQL queries).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Me encanta!&lt;/em&gt; ðŸ˜Š&lt;/p&gt;

&lt;p&gt;Finally, you can put multiple questions on a dashboard and have a nice overview of what's going on in the company.&lt;/p&gt;

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

&lt;p&gt;All in all, Metabase was really easy to setup for the tech team. If we had used Heroku, it would have been even easier. And most importantly, it is really easy and fun to use for all team members. ðŸ˜Š&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks Marco, for all the tips here&lt;/em&gt; ðŸ˜Š &lt;em&gt;In further posts I will go into detail on how we create advanced reports at &lt;a href="https://www.store2be.com"&gt;store2be&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi there, we’re &lt;a href="https://www.store2be.com"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retails space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com"&gt;the store2be tech page&lt;/a&gt; or follow our &lt;a href="https://medium.com/store2be-tech"&gt;Medium channel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>metabase</category>
      <category>bi</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Multi AZ Kubernetes with kops in AWS regions with only 2 AZ</title>
      <dc:creator>Peter Gundel</dc:creator>
      <pubDate>Thu, 30 Mar 2017 10:21:39 +0000</pubDate>
      <link>https://forem.com/peterfication/multi-az-kubernetes-with-kops-in-aws-regions-with-only-2-az</link>
      <guid>https://forem.com/peterfication/multi-az-kubernetes-with-kops-in-aws-regions-with-only-2-az</guid>
      <description>&lt;p&gt;At the moment we are setting up Kubernetes at store2be. We are a company from Germany and most of our business is taking place in Germany. Therefore, we want our AWS servers to be in Frankfurt.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://github.com/kubernetes/kops"&gt;kops&lt;/a&gt; for our Kubernetes cluster management and we want our cluster to be a multi availability zone (AZ) deployment. With this in mind we tried out the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kops create cluster \
    --node-count 4 \
    --zones eu-central-1a,eu-central-1b \
    --master-zones eu-central-1a,eu-central-1b \
    --node-size t2.medium \
    --master-size m3.medium \
    --yes \
    kubernetes-cluster.example.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the result was not that promising:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;There should be an odd number of master-zones, for etcd's quorum.  Hint: Use --zones and --master-zones to declare node zones and master zones separately.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So unfortunately, at the moment it seems that it is not possible to use kops out of the box in an AWS region with only 2 AZ. But after some digging we found a &lt;a href="https://github.com/kubernetes/kops/issues/732"&gt;Github issue&lt;/a&gt; that described our problem and even had a &lt;a href="https://github.com/kubernetes/kops/issues/732#issuecomment-275888160"&gt;work around&lt;/a&gt; for that! So the following steps are based on the solution provided by &lt;a href="https://github.com/kamilhristov"&gt;Kamil Hristov&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create a cluster.yml for a AWS region with 3 AZ
&lt;/h2&gt;

&lt;p&gt;Create a cluster in eu-west (because it has 3 AZ):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kops create cluster \
    --node-count 4 \
    --zones eu-west-1a,eu-west-1b,eu-west-1c \
    --master-zones eu-west-1a,eu-west-1b,eu-west-1c \
    --node-size t2.medium \
    --master-size m3.medium \
    --yes \
    kubernetes-cluster.example.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dump the cluster and instancegroup configuration and append it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kops get cluster --name=kubernetes-cluster.example.org --output yaml &amp;gt; cluster.yml &amp;amp;&amp;amp; \
  echo "\n---\n" &amp;gt;&amp;gt; cluster.yml &amp;amp;&amp;amp; \
  kops get instancegroups --name=kubernetes-cluster.example.org --output yaml &amp;gt;&amp;gt; cluster.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Adjust the cluster.yml
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Replace all the occurrences of eu-west with eu-central&lt;/li&gt;
&lt;li&gt;Replace all the zone definitions of eu-central-1c with eu-central-1a&lt;/li&gt;
&lt;li&gt;Replace all the name definitions of eu-central-1c with something different than eu-central-1a like for example eu-central-1a2 (otherwise there will be name conflicts)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Create the cluster by using the adjusted cluster.yml
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h1&gt;
  
  
  We have to use replace here, because the cluster spec already has
&lt;/h1&gt;

&lt;h1&gt;
  
  
  been created in step 1.
&lt;/h1&gt;

&lt;p&gt;$ kops replace -f cluster.yml&lt;br&gt;
$ kops create secret --name kubernetes-cluster.example.org sshpublickey admin -i ~/.ssh/id_rsa.pub&lt;br&gt;
$ kops update cluster --yes&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And ✨, there is the multi AZ cluster in Frankfurt 🎉&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I created this article because it took us a while to find out about this solution and I hope we could help someone with it. Thanks to &lt;a href="https://github.com/kamilhristov"&gt;Kamil Hristov&lt;/a&gt; for sharing the  solution. Thanks to &lt;a href="https://github.com/tomhoule"&gt;Tom Houlé&lt;/a&gt; for reviewing the article.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Hi there, we're &lt;a href="https://www.store2be.com"&gt;store2be&lt;/a&gt;, a Berlin based startup that builds a SaaS enabled marketplace for short term retails space. If you like what we are posting you might wanna check out &lt;a href="https://tech.store2be.com"&gt;the store2be tech page&lt;/a&gt; or follow our &lt;a href="https://medium.com/store2be-tech"&gt;Medium channel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>kops</category>
    </item>
  </channel>
</rss>
