<?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: Risa</title>
    <description>The latest articles on Forem by Risa (@risafj).</description>
    <link>https://forem.com/risafj</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%2F132190%2F0e4202a0-a59b-4911-aee7-ac666a2d3f87.jpg</url>
      <title>Forem: Risa</title>
      <link>https://forem.com/risafj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/risafj"/>
    <language>en</language>
    <item>
      <title>4 Design Challenges as an Indie App Developer and Helpful Tools: Color Schemes, App Screenshots, etc.</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Sun, 22 Aug 2021 08:57:12 +0000</pubDate>
      <link>https://forem.com/risafj/4-design-challenges-as-an-indie-app-developer-and-helpful-tools-color-schemes-app-screenshots-etc-3e0</link>
      <guid>https://forem.com/risafj/4-design-challenges-as-an-indie-app-developer-and-helpful-tools-color-schemes-app-screenshots-etc-3e0</guid>
      <description>&lt;p&gt;I recently released an iOS/Android app called &lt;a href="https://warikani.page.link/dev_to_indie_design" rel="noopener noreferrer"&gt;Warikani&lt;/a&gt;. It's an intuitive, simple-to-use expense tracker for couples, built on React Native. Creating the app involved a lot of non-programming tasks, from designing the UI to writing the app store descriptions.&lt;/p&gt;

&lt;p&gt;In this post, I will talk about 4 challenges related to design that I faced along the way, and tools that I found essential in solving them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I am not discounting the work of designers. If you are a professional designer, or you can ask one to design your app, that is undoubtedly better. This is an article for developers who want to/have to design everything themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. How should I decide the color scheme for my app?&lt;/li&gt;
&lt;li&gt;2. How do I design the app UI?&lt;/li&gt;
&lt;li&gt;3. How do I make app icons for each size?&lt;/li&gt;
&lt;li&gt;4. How do I create tasteful App Store screenshots?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. How should I decide the color scheme for my app? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solution: Use Eva Design System's deep learning color generator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had a primary theme color that I wanted to use, but I didn't know what colors would go well with it. Eva Design System's color generator is great because it recommends colors for each situation (like success or error) that would go well with your main color. If you don't like their suggestions, you can also hit regenerate until you see a color scheme that you like.&lt;/p&gt;

&lt;p&gt;Another perk is that it supports multiple export methods, including JPEG and JSON.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;a href="https://colors.eva.design" rel="noopener noreferrer"&gt;https://colors.eva.design&lt;/a&gt;&lt;/p&gt;

&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%2Fn9a5xufifnab3ilzcoz2.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%2Fn9a5xufifnab3ilzcoz2.png" alt="Eva_Design_System__Deep_learning_color_generator" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How do I design the app UI? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solution: Learn the fundamentals of UI design from YouTube. Also, consider using a component library.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're an engineer with no professional design experience (like me), you would probably benefit from learning the fundamentals of UI design. I recommend these videos by the Youtube channel &lt;a href="https://www.youtube.com/channel/UCVyRiMvfUNMA1UPlDPzG5Ow" rel="noopener noreferrer"&gt;DesignCourse&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_Hp_dI0DzY4" rel="noopener noreferrer"&gt;The 2019 UI Design Crash Course for Beginners
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=tRpoI6vkqLs" rel="noopener noreferrer"&gt;The 2020 UI Design Fundamentals Crash Course (INTERACTIVE)
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of the topics covered in these videos may seem obvious  (e.g. use enough color contrast, provide adequate whitespace). But it was useful to understand why you should or shouldn't do something.&lt;/p&gt;

&lt;p&gt;Another piece of advice is to consider using a UI component library. Initially, I was creating most of my components like buttons and text inputs by styling the base React Native components. I didn't want to use a component library, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's one more dependency that you have to manage&lt;/li&gt;
&lt;li&gt;It adds a layer of complexity and makes it harder to fine-tune your components&lt;/li&gt;
&lt;li&gt;What if the library stops being maintained?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was also concerned that using a library would make my app look "generic", for lack of a better term. But after a while, I changed my mind. I wanted to incorporate the look of &lt;a href="https://material.io/components/text-fields" rel="noopener noreferrer"&gt;Material Design text inputs&lt;/a&gt; in my app - specifically, how they change color and display a label when focused.&lt;/p&gt;

&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%2Fgbb79iejvu1fhpq4cbzb.gif" 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%2Fgbb79iejvu1fhpq4cbzb.gif" alt="text-field" width="282" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I installed &lt;a href="https://callstack.github.io/react-native-paper/" rel="noopener noreferrer"&gt;react-native-paper&lt;/a&gt;, which is a Material Design component library, and replaced some of my components. It's a subtle change, but the screen feels more polished overall.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fp9u3ikids6wxfbkulein.png" alt="Simulator Screen Shot - iPhone 8 - 2021-08-15 at 16.41.43" width="750" height="1334"&gt;&lt;/td&gt;
&lt;td&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%2Fl9gogbg3visi0e8vmfdi.png" alt="Simulator Screen Shot - iPhone 8 - 2021-08-15 at 16.40.49" width="750" height="1334"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As for the concern that the app could look more generic somehow, I don't think it's worth worrying about most of the time. After all, users prefer UI that looks familiar (&lt;a href="https://www.nngroup.com/videos/jakobs-law-internet-ux/" rel="noopener noreferrer"&gt;Jakob's Law&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. How do I make app icons for each size? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solution: Use an online app icon generator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't know this until I became an app developer, but you have to provide your app icon in 10+ different sizes. This would be extremely tedious to do by hand, but thankfully, there are online app icon generators where you can drop an image and they will resize it for you. I can't remember the exact service I used, but just google "online app icon generator".&lt;/p&gt;

&lt;h2&gt;
  
  
  4. How do I create tasteful App Store screenshots? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Solution: Bite the bullet, learn a bit of Figma. There are some helpful templates out there.&lt;/strong&gt;&lt;/p&gt;

&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%2F94qcr438pew7t04pi7sw.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%2F94qcr438pew7t04pi7sw.png" alt="FIGMA" width="500" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Okay, I won't pretend like my screenshots are perfect - there's still room for improvement. Advice from designers is welcome.)&lt;/p&gt;

&lt;p&gt;When you submit your app on the App Store or Google Play Store, you're required to upload screenshots in specific sizes. The easiest solution is to take screenshots on your device emulator, and upload them as-is... but that's not exactly eye-catching. You'll notice that the screenshots for well-known apps aren't plain screenshots taken from the emulator (&lt;a href="https://apppresser.com/app-store-screenshots/" rel="noopener noreferrer"&gt;examples&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I initially tried to improve my screenshots by using a tool like &lt;a href="https://www.appstorescreenshot.com/" rel="noopener noreferrer"&gt;this one&lt;/a&gt;. It's straightforward - drop in an image, select a background color, and write some text. But I couldn't find a template that allowed for enough customization and provided all the dimensions I needed. I eventually ended up creating the files on Figma.&lt;/p&gt;

&lt;p&gt;While doing so, I found these drag-and-drop tools that let you wrap the device screenshots in device frames to be useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Android screenshots, use the official device art generator: &lt;a href="https://developer.android.com/distribute/marketing-tools/device-art-generator" rel="noopener noreferrer"&gt;https://developer.android.com/distribute/marketing-tools/device-art-generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For iOS screenshots, use community-provided Figma templates like this one: &lt;a href="https://www.figma.com/community/file/882254519102673494" rel="noopener noreferrer"&gt;https://www.figma.com/community/file/882254519102673494&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Good luck to the indie app developers out there, and let me know about any helpful tools that you found. Thanks for reading!&lt;/p&gt;

&lt;p&gt;P.S. Have a partner? Use &lt;a href="https://warikani.page.link/dev_to_indie_design" rel="noopener noreferrer"&gt;Warikani&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>design</category>
      <category>ui</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to set up ESLint with StandardJS for a new React Native/TypeScript project</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Sun, 31 Jan 2021 12:36:53 +0000</pubDate>
      <link>https://forem.com/risafj/how-to-set-up-eslint-with-standardjs-for-a-new-react-native-typescript-project-ebl</link>
      <guid>https://forem.com/risafj/how-to-set-up-eslint-with-standardjs-for-a-new-react-native-typescript-project-ebl</guid>
      <description>&lt;p&gt;If you have a situation like below, this walkthrough is for you!&lt;/p&gt;

&lt;p&gt;✔ You have a React Native project&lt;br&gt;
✔ It uses TypeScript&lt;br&gt;
✔ You want to set up ESLint&lt;br&gt;
✔ You want to use Standard JS &lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: If you don't know about Standard JS, check out their rules &lt;a href="https://standardjs.com/rules.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Notable rules include using two spaces for indentation and not using semicolons at the end of lines.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This tutorial walks you through installing and configuring ESLint, and setting up Github Actions so ESLint runs every time you push your code.&lt;/p&gt;

&lt;p&gt;Step-by-step commit list: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/risafj/StorybookExampleReactNativeTS/pull/1" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Add ESLint
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#1&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/risafj" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F43341670%3Fv%3D4" alt="risafj avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/risafj" rel="noopener noreferrer"&gt;risafj&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/risafj/StorybookExampleReactNativeTS/pull/1" rel="noopener noreferrer"&gt;&lt;time&gt;Jan 06, 2021&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;ul&gt;
&lt;li&gt;StandardJS&lt;/li&gt;
&lt;li&gt;Remove prettier (not compatible)&lt;/li&gt;
&lt;li&gt;Modify tsconfig&lt;/li&gt;
&lt;li&gt;Github actions to run ESLint and TypeScript checks&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/risafj/StorybookExampleReactNativeTS/pull/1" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Steps
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Install ESLint, and its plugins for React and React Native
&lt;/h2&gt;

&lt;p&gt;Simply run below!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D eslint eslint-plugin-react eslint-plugin-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docs: &lt;a href="https://github.com/intellicode/eslint-plugin-react-native#installation" rel="noopener noreferrer"&gt;https://github.com/intellicode/eslint-plugin-react-native#installation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Install ESLint plugins for Standard and TypeScript
&lt;/h2&gt;

&lt;p&gt;Now run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D eslint@7 eslint-plugin-promise@4 eslint-plugin-import@2 eslint-plugin-node@11 @typescript-eslint/eslint-plugin@4 eslint-config-standard-with-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why do we need so many dependencies? Good question, the docs mention why.&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://github.com/standard/eslint-config-standard-with-typescript#usage" rel="noopener noreferrer"&gt;https://github.com/standard/eslint-config-standard-with-typescript#usage&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This long list of dependencies includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Peer dependencies of eslint-config-standard&lt;/li&gt;
&lt;li&gt;@typescript-eslint/eslint-plugin; ESLint rules for TypeScript.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Disable Prettier's rules that conflict with ESLint
&lt;/h2&gt;

&lt;p&gt;If you tried configuring and running ESLint at this point, you will probably find that some of its rules conflict with Prettier (which comes pre-installed in React Native projects). We can use a package called &lt;a href="https://github.com/prettier/eslint-config-prettier#readme" rel="noopener noreferrer"&gt;eslint-config-prettier&lt;/a&gt; to turn off conflicting Prettier rules (I found this solution in this helpful &lt;a href="https://github.com/facebook/react-native/issues/26903#issuecomment-581074476" rel="noopener noreferrer"&gt;Github comment&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Install as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D eslint-config-prettier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will configure ESLint to use this package in step 5. Ok, we're finally done installing the necessary packages!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Configure .eslintignore
&lt;/h2&gt;

&lt;p&gt;Create a file called &lt;code&gt;.eslintignore&lt;/code&gt; in your project root. This file is like &lt;code&gt;.gitignore&lt;/code&gt; for ESLint - you should list anything that you don't want checked by ESLint. As an example, here is my &lt;code&gt;.eslintignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules/
babel.config.js
metro.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Configure .eslintrc.js
&lt;/h2&gt;

&lt;p&gt;There are a few file extensions that you can use for ESLint config, like &lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.json&lt;/code&gt;. We will go with &lt;code&gt;.js&lt;/code&gt; here.&lt;br&gt;
Find or create a file called &lt;code&gt;.eslintrc.js&lt;/code&gt; in your project root. Then, configure as below (minus the comments):&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;root&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;extends&lt;/span&gt;&lt;span class="p"&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;@react-native-community/eslint-config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Default RN config&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;standard-with-typescript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Installed in step 2&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-config-prettier&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Installed in step 3&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@typescript-eslint/parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Installed in step 2&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&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;@typescript-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Installed in step 2&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="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Installed in step 1&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Installed in step 1&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parserOptions&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ecmaFeatures&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;project&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;./tsconfig.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// Required for Standard plugin&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;env&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native/react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rules&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prettier/prettier&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;off&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Turn off prettier&lt;/span&gt;
    &lt;span class="c1"&gt;// These are the rules that I use&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native/no-unused-styles&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;warn&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;react-native/no-inline-styles&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;error&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;react-native/no-raw-text&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warn&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="na"&gt;skip&lt;/span&gt;&lt;span class="p"&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;CustomText&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native/no-single-element-style-arrays&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;warn&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;object-curly-spacing&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;error&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;@typescript-eslint/explicit-function-return-type&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;off&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;@typescript-eslint/strict-boolean-expressions&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;off&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;@typescript-eslint/no-floating-promises&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;off&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;@typescript-eslint/no-unused-vars&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;off&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;@typescript-eslint/require-array-sort-compare&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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="na"&gt;ignoreStringArrays&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react/jsx-curly-spacing&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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="na"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;always&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;allowMultiline&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;children&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eol-last&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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;always&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;no-multiple-empty-lines&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;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;semi&lt;/span&gt;&lt;span class="p"&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;error&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;never&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// Indent with 2 spaces&lt;/span&gt;
    &lt;span class="na"&gt;indent&lt;/span&gt;&lt;span class="p"&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;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// Indent JSX with 2 spaces&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react/jsx-indent&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// Indent props with 2 spaces&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react/jsx-indent-props&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for specific ESLint rules, it depends on personal preference so I'll leave it up to you. If you want to see my config as a reference, you can find it here:&lt;br&gt;
&lt;a href="https://github.com/risafj/StorybookExampleReactNativeTS/blob/main/.eslintrc.js" rel="noopener noreferrer"&gt;https://github.com/risafj/StorybookExampleReactNativeTS/blob/main/.eslintrc.js&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Edit tsconfig.json
&lt;/h2&gt;

&lt;p&gt;If you tried running ESLint at this point, you will probably get an error like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Users/risa/projects/project-name/.eslintrc.js
  0:0  error  Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser.
The file does not match your project config: .eslintrc.js.
The file must be included in at least one of the projects provided
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find a discussion about this error in this &lt;a href="https://github.com/typescript-eslint/typescript-eslint/issues/967" rel="noopener noreferrer"&gt;Github issue&lt;/a&gt;. Basically, you have to include the files you want to lint in &lt;code&gt;tsconfig.json&lt;/code&gt;. Create a file like &lt;a href="https://github.com/risafj/StorybookExampleReactNativeTS/blob/main/tsconfig.json" rel="noopener noreferrer"&gt;this&lt;/a&gt; if it doesn't exist yet, or add the lines below if it does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tsconfig.json&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;"compilerOptions"&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="c1"&gt;// Various options...&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="c1"&gt;// Add the "include" array below&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="c1"&gt;// Inside the array, specify files you want ESLint to check&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&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="s2"&gt;"./**/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"./*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;".eslintrc.js"&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;h2&gt;
  
  
  7. Add npm scripts
&lt;/h2&gt;

&lt;p&gt;At this point, you're ready to run ESLint! But before that, let's add some npm scripts to your &lt;code&gt;package.json&lt;/code&gt; to save you a few keystrokes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// package.json&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;"scripts"&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="c1"&gt;// Add the two scripts below&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint . --ext .js,.jsx,.ts,.tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint:fix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eslint . --fix --ext .js,.jsx,.ts,.tsx"&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;



&lt;p&gt;Now, you can run ESLint with &lt;code&gt;yarn lint&lt;/code&gt; or &lt;code&gt;yarn lint:fix&lt;/code&gt; (for auto-correcting errors).&lt;/p&gt;

&lt;h2&gt;
  
  
  8. [Optional] Run ESLint in Github Actions
&lt;/h2&gt;

&lt;p&gt;You can configure Github Actions to run ESLint every time you push your code. This is convenient because it's easy to forget to run lint locally. Setup is simple - just copy the configuration below.&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="c1"&gt;# .github/workflows/lint.yml&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&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;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install modules&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run ESLint&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn lint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;That's all! Thanks for reading, and I'd like to hear if it worked :)&lt;/p&gt;

</description>
      <category>eslint</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Setting up Storybook for React Native/TypeScript (server, loader, iOS, Android)</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Sun, 10 Jan 2021 12:59:34 +0000</pubDate>
      <link>https://forem.com/risafj/setting-up-storybook-for-react-native-typescript-server-loader-ios-android-3b0i</link>
      <guid>https://forem.com/risafj/setting-up-storybook-for-react-native-typescript-server-loader-ios-android-3b0i</guid>
      <description>&lt;p&gt;I recently set up Storybook for a React Native/TypeScript project. It wasn't so different from using Storybook for web dev, but there were a few catches. I found their &lt;a href="https://www.learnstorybook.com/intro-to-storybook/react-native/en/get-started/" rel="noopener noreferrer"&gt;official tutorial&lt;/a&gt; to be outdated sometimes, and their &lt;a href="https://github.com/storybookjs/react-native" rel="noopener noreferrer"&gt;Github README&lt;/a&gt; was up-to-date but didn't have all the information I wanted.&lt;/p&gt;

&lt;p&gt;This is a step-by-step walkthrough of how to set up Storybook for React Native, complete with the the web UI (&lt;a href="https://www.npmjs.com/package/@storybook/react-native-server" rel="noopener noreferrer"&gt;&lt;code&gt;@storybook/react-native-server&lt;/code&gt;&lt;/a&gt;), dynamic story loading (&lt;a href="https://github.com/elderfo/react-native-storybook-loader" rel="noopener noreferrer"&gt;&lt;code&gt;react-native-storybook-loader&lt;/code&gt;&lt;/a&gt;), and TypeScript.&lt;/p&gt;

&lt;p&gt;Finished repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/risafj" rel="noopener noreferrer"&gt;
        risafj
      &lt;/a&gt; / &lt;a href="https://github.com/risafj/StorybookExampleReactNativeTS" rel="noopener noreferrer"&gt;
        StorybookExampleReactNativeTS
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Example repo for setting up Storybook for a React Native/TypeScript project.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Storybook Example React Native TypeScript&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This repo showcases some components that I made for a personal React Native project (contained in a separate private repo).&lt;/p&gt;
&lt;p&gt;I wrote a blog post about the Storybook setup process here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dev.to/risafj/setting-up-storybook-for-react-native-typescript-server-loader-ios-android-3b0i" rel="nofollow"&gt;https://dev.to/risafj/setting-up-storybook-for-react-native-typescript-server-loader-ios-android-3b0i&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Technical stack&lt;/h1&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Storybook
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@storybook/react-native-server&lt;/code&gt; for using the web interface&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-native-storybook-loader&lt;/code&gt; for dynamic story loading&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Lint: ESLint (Standard JS format)&lt;/li&gt;
&lt;li&gt;CI: Github Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;How to use&lt;/h1&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Clone this repo&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yarn install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you're using an iOS emulator, &lt;code&gt;npx pod-install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Copy the contents of &lt;code&gt;.env.sample&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;yarn storybook&lt;/code&gt; to start the Storybook Server (localhost:7007)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;yarn ios&lt;/code&gt; or &lt;code&gt;yarn android&lt;/code&gt; to start Storybook in the emulator&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;NOTE: In order to boot up Storybook, it's necessary to set the environment variable &lt;code&gt;LOAD_STORYBOOK&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; (steps 4 and 5 above). Otherwise, it will boot up the default app created by React Native CLI.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/risafj/StorybookExampleReactNativeTS" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Since you're reading this post, I'm going to assume you already have a project set up using the React Native CLI.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: I can't guarantee that the same steps will work for Expo projects.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Storybook
&lt;/h2&gt;

&lt;p&gt;Run the command below from your project root. This will install the necessary packages and add boilerplate code for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx -p @storybook/cli sb init --type react_native
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When it prompts you whether to install &lt;code&gt;@storybook/react-native-server&lt;/code&gt;, hit yes.&lt;/strong&gt;&lt;br&gt;
Basically, the Storybook Server package lets you use the web interface for switching between components and manipulating knobs, instead of having to do everything from your emulator (it's the same UI as Storybook for other frameworks).&lt;/p&gt;

&lt;p&gt;This is what it will look like with the web interface:&lt;/p&gt;

&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%2Fi%2F6m03cu04fbrd15gi0ywc.gif" 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%2Fi%2F6m03cu04fbrd15gi0ywc.gif" alt="storybook-server-gif" width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're developing for iOS, you should also run a &lt;code&gt;pod install&lt;/code&gt; from the &lt;code&gt;ios/&lt;/code&gt; directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Conditionally render Storybook
&lt;/h2&gt;

&lt;p&gt;Now, let's add the enviroment variable &lt;code&gt;LOAD_STORYBOOK&lt;/code&gt;, and use this flag to determine whether we should load your actual app or Storybook.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: This is one of the ways recommended in Storybook's &lt;a href="https://github.com/storybookjs/react-native#other-ways-to-render-storybook" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Add &lt;code&gt;LOAD_STORYBOOK&lt;/code&gt; flag
&lt;/h3&gt;

&lt;p&gt;There are a few ways to use environment variables in React Native, but I used &lt;a href="https://github.com/luggit/react-native-config" rel="noopener noreferrer"&gt;&lt;code&gt;react-native-config&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
Setup is very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;yarn add react-native-config&lt;/code&gt;, then &lt;code&gt;pod install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you're developing for Android, add an import line as described &lt;a href="https://github.com/luggit/react-native-config#extra-step-for-android" rel="noopener noreferrer"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;In your project root, create an &lt;code&gt;.env&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Any environment variables you add to &lt;code&gt;.env&lt;/code&gt; can be accessed as &lt;code&gt;Config.YOUR_ENVIRONMENT_VARIABLE&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, add the environment variable &lt;code&gt;LOAD_STORYBOOK=true&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Render Storybook if &lt;code&gt;LOAD_STORYBOOK=true&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;App.tsx&lt;/code&gt;, change your code like below so that Storybook is rendered conditionally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;StorybookUI&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;./storybook&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;Config&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-native-config&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;App&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// Your actual app&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;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOAD_STORYBOOK&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;StorybookUI&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Boot Storybook
&lt;/h2&gt;

&lt;p&gt;There are a some other things to configure, but at this point, you should be able to boot Storybook and make sure it works.&lt;/p&gt;

&lt;p&gt;First, boot Storybook Server (the web interface) with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn storybook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it's done booting, a browser window should open that looks like this (if not, you can just access &lt;code&gt;localhost:7007&lt;/code&gt;):&lt;/p&gt;

&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%2Fi%2Fpxbhbnu0x5il4qbqwoyg.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%2Fi%2Fpxbhbnu0x5il4qbqwoyg.png" alt="empty-storybook-server" width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, the sidebar menu should show a loading animation.&lt;/p&gt;

&lt;p&gt;Next, run &lt;code&gt;yarn ios&lt;/code&gt; or &lt;code&gt;yarn android&lt;/code&gt;. Since we set &lt;code&gt;LOAD_STORYBOOK=true&lt;/code&gt;, this runs Storybook instead of your actual app.&lt;/p&gt;

&lt;p&gt;Once it boots, the Storybook Server's sidebar should be populated with the stories you have, allowing you to navigate via the web UI (like the GIF from step 1).&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to take if Storybook Server doesn't work with the Android emulator
&lt;/h3&gt;

&lt;p&gt;For Android, you may find that the sidebar doesn't get populated even when Storybook has booted in your emulator. There were some Github issues (like &lt;a href="https://github.com/storybookjs/storybook/issues/6674" rel="noopener noreferrer"&gt;this one&lt;/a&gt;) discussing this phenomenon. The advice I found online was to run &lt;code&gt;adb reverse tcp:7007 tcp:7007&lt;/code&gt;, but ultimately, what solved it for me was to specify the host parameter to be the Android localhost.&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;// storybook/index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StorybookUIRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getStorybookUI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="c1"&gt;// Add the line below&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;android&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;10.0.2.2&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;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Take care of asyncStorage warning
&lt;/h2&gt;

&lt;p&gt;You'll probably see a warning in the metro server logs that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WARN Starting Storybook v5.3.0, we require to manually pass an asyncStorage prop. Pass null to disable or use one from @react-native-community or react-native itself.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-native-async-storage" rel="noopener noreferrer"&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The benefit of using Async Storage is so that when users refresh the app, Storybook can open their last visited story.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Configuring this is simple; just pass it in as below.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: If you don't have the &lt;code&gt;@react-native-community/async-storage&lt;/code&gt; package, you'll have to install it first.&lt;/em&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="c1"&gt;// storybook/index.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;StorybookUIRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getStorybookUI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;android&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;10.0.2.2&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;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Add the line below&lt;/span&gt;
  &lt;span class="na"&gt;asyncStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&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;@react-native-community/async-storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Add Storybook Loader
&lt;/h2&gt;

&lt;p&gt;React Native Storybook Loader is technically not necessary for using Storybook, but it's a convenient package that frees you from the task of having to write an import statement for every story file. Storybook Loader executes a script when you boot up Storybook, which auto-generates a file with the necessary import statemnents.&lt;/p&gt;

&lt;p&gt;Their official README's quick start section covers everything you need, so I will link it here: &lt;a href="https://github.com/elderfo/react-native-storybook-loader#quick-start" rel="noopener noreferrer"&gt;https://github.com/elderfo/react-native-storybook-loader#quick-start&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Write your own stories (in TypeScript!)
&lt;/h2&gt;

&lt;p&gt;Now, all that's left is to add your own stories (and remove unnecessary boilerplate code). You don't have to add any packages or configuration for writing your stories in TypeScript; just use the &lt;code&gt;.stories.tsx&lt;/code&gt; extension.&lt;br&gt;
Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/atoms/CustomButton.stories.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;storiesOf&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;@storybook/react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CenterView&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;../../../storybook/stories/CenterView&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;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CustomButton&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;./CustomButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;storiesOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Atoms/CustomButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addDecorator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;getStory&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CenterView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;getStory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;CenterView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CustomButton&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Confirm"&lt;/span&gt; &lt;span class="na"&gt;colorModifier&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"confirm"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Caveat
&lt;/h3&gt;

&lt;p&gt;If you plan to reuse &lt;code&gt;CenterView&lt;/code&gt;, the boilerplate component that places your story code at the center of the emulator screen, you need to rewrite it in TypeScript or you will encounter some type errors. This is fairly straightforward - just rewrite it like so (the exact changes can be seen in &lt;a href="https://github.com/risafj/StorybookExampleReactNativeTS/commit/f81ed67bb23b9a522429eeb0d213dd08754ecb1b" rel="noopener noreferrer"&gt;this commit&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// storybook/stories/CenterView/index.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&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;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CenterView&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;Props&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&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;children&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&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="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#F5FCFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you for reading!&lt;/p&gt;




&lt;p&gt;Credits:&lt;br&gt;
Many thanks to &lt;a class="mentioned-user" href="https://dev.to/rob117"&gt;@rob117&lt;/a&gt;, who helped with the solution to the compatibility issue between the Storybook Server and Android emulator.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>storybook</category>
    </item>
    <item>
      <title>The Basics of Rails I18n - Translate errors, models, and attributes</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Tue, 05 Jan 2021 06:02:57 +0000</pubDate>
      <link>https://forem.com/risafj/the-basics-of-rails-i18n-translate-errors-models-and-attributes-84d</link>
      <guid>https://forem.com/risafj/the-basics-of-rails-i18n-translate-errors-models-and-attributes-84d</guid>
      <description>&lt;p&gt;&lt;em&gt;Header photo by &lt;a href="https://unsplash.com/@joaosilas" rel="noopener noreferrer"&gt;@joaosilas&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post has also been translated into Spanish by &lt;a href="//www.ibidemgroup.com"&gt;Ibidem Group&lt;/a&gt;. Traducción a Español aquí: &lt;a href="https://www.ibidemgroup.com/edu/traducir-rails-i18n/" rel="noopener noreferrer"&gt;https://www.ibidemgroup.com/edu/traducir-rails-i18n/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The i18n (internationalization) API is the standard way to support localization in Rails. The official &lt;a href="https://guides.rubyonrails.org/i18n.html" rel="noopener noreferrer"&gt;guide&lt;/a&gt; has all the information you need, but it's also very long. This post is based on the notes I took when I was first learning how to set up i18n, and my goal here is to provide a more approachable walkthrough. This post covers the YAML file setup for error messages and model names/attribute names, and lookup using the &lt;code&gt;I18n.t&lt;/code&gt; method.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why I wrote this post
&lt;/h4&gt;

&lt;p&gt;My first exposure to i18n was the &lt;a href="https://github.com/svenfuchs/rails-i18n" rel="noopener noreferrer"&gt;rails-i18n&lt;/a&gt; gem, which provides default translations for commonly used error messages, days of the week, etc. While you can install this gem and call it a day, knowing how to set up i18n yourself is necessary if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you want to use translations that are different from those provided by rails-i18n&lt;/li&gt;
&lt;li&gt;you want to include languages not covered by rails-i18n&lt;/li&gt;
&lt;li&gt;you only need translations for a few languages, and don't want to install the whole gem including dozens of languages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case, it was the third reason - I only needed translations for Japanese.&lt;/p&gt;

&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;0. Setting your default locale&lt;/li&gt;
&lt;li&gt;
1. Translating model and attribute names

&lt;ul&gt;
&lt;li&gt;&lt;a href="//#1.1"&gt;1.1 Defining your translations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="//#1.2"&gt;1.2 Accessing model and attribute translations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

2. Translating ActiveRecord errors

&lt;ul&gt;
&lt;li&gt;&lt;a href="//#2.1"&gt;2.1 Error message breakdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="//#2.2"&gt;2.2 Defining your translations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  0. Setting your default locale&lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;I set mine to Japanese.&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;# config/application.rb&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;i18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:ja&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  1. Translating model and attribute names&lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Docs: &lt;a href="https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models" rel="noopener noreferrer"&gt;https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1.1 Defining your translations&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;First, define translations for your model names and attributes in a YAML file like below. This example is for a &lt;code&gt;User&lt;/code&gt; model with two attributes.&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="c1"&gt;# config/locales/ja.yml&lt;/span&gt;
&lt;span class="na"&gt;ja&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;activerecord&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ユーザ'&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;locale&amp;gt;.activerecord.models.&amp;lt;model name&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;名前'&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;locale&amp;gt;.activerecord.attributes.&amp;lt;model name&amp;gt;.&amp;lt;attribute name&amp;gt;&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;パスワード'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1.2 Accessing model and attribute translations&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# How to look up translations for model names&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;human&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"ユーザ"&lt;/span&gt;

&lt;span class="c1"&gt;# How to look up translations for attributes (you can use symbols or strings)&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;human_attribute_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"名前"&lt;/span&gt;

&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;human_attribute_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"名前"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  2. Translating ActiveRecord errors&lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Docs: &lt;a href="https://guides.rubyonrails.org/i18n.html#error-message-scopes" rel="noopener noreferrer"&gt;https://guides.rubyonrails.org/i18n.html#error-message-scopes&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2.1 Error message breakdown&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Translating error messages is a bit more complicated than models. Let's talk about the error message structure first. ActiveRecord has some built-in validation errors that are raised if your record is invalid. Consider this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;presence: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your locale is &lt;code&gt;:en&lt;/code&gt;, this error message is returned when you try to create an invalid record.&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;User&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;name: &lt;/span&gt;&lt;span class="s1"&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="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordInvalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Validation&lt;/span&gt; &lt;span class="ss"&gt;failed: &lt;/span&gt;&lt;span class="no"&gt;Name&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This message actually consists of a few parts.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;code&gt;ActiveRecord::RecordInvalid:&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The error type, which is the same regardless of your locale. No translation is required.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;code&gt;Validation failed:&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The description for this &lt;code&gt;RecordInvalid&lt;/code&gt; error in English, defined in the Rails gem's &lt;code&gt;en.yml&lt;/code&gt; (&lt;a href="https://github.com/rails/rails/blob/968de9cc6d48ca026303566d7d1f437d40360820/activerecord/lib/active_record/locale/en.yml#L17" rel="noopener noreferrer"&gt;source code&lt;/a&gt;). &lt;strong&gt;Translation is required for your locale(s).&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. &lt;code&gt;Name can't be blank&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is the error message for records that violate &lt;code&gt;presence: true&lt;/code&gt;. It consists of two parts - the attribute &lt;code&gt;Name&lt;/code&gt; and  message &lt;code&gt;can't be blank&lt;/code&gt; (&lt;a href="https://github.com/rails/rails/blob/968de9cc6d48ca026303566d7d1f437d40360820/activemodel/lib/active_model/locale/en.yml#L16" rel="noopener noreferrer"&gt;source code&lt;/a&gt;). It turns out, the default error message format is an interpolation of two elements: &lt;code&gt;"%{attribute} %{message}"&lt;/code&gt; (&lt;a href="https://github.com/rails/rails/blob/968de9cc6d48ca026303566d7d1f437d40360820/activemodel/lib/active_model/locale/en.yml#L4" rel="noopener noreferrer"&gt;source code&lt;/a&gt;). &lt;strong&gt;Translation is required for your locale(s).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note: The translation for &lt;code&gt;attribute&lt;/code&gt; will be taken from the model attribute translations defined in Section 1. If there's no translation, Rails will print the attribute name in English.&lt;/p&gt;

&lt;p&gt;What happens if you change the locale from &lt;code&gt;:en&lt;/code&gt; to &lt;code&gt;:ja&lt;/code&gt; without defining the corresponding translations? The error message is returned as &lt;code&gt;translation missing&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&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;name: &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; ActiveRecord::RecordInvalid: translation missing: ja.activerecord.errors.messages.record_invalid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's look at how to provide translations next.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.2 Defining your translations&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;According to the official &lt;a href="https://guides.rubyonrails.org/i18n.html#error-message-scopes" rel="noopener noreferrer"&gt;guide&lt;/a&gt;, there are a few places that Rails will look in order to find a translation for your error, in this order.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;activerecord.errors.models.[model_name].attributes.[attribute_name]&lt;/li&gt;
&lt;li&gt;activerecord.errors.models.[model_name]&lt;/li&gt;
&lt;li&gt;activerecord.errors.messages&lt;/li&gt;
&lt;li&gt;errors.attributes.[attribute_name]&lt;/li&gt;
&lt;li&gt;errors.messages&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that if you want to set model-specific or attribute-specific error messages, you can do so too. But in this case, let's say we want the &lt;code&gt;record_invalid&lt;/code&gt; and &lt;code&gt;blank&lt;/code&gt; to be translated the same way regardless of the model. Here is a sample configuration:&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="c1"&gt;# config/locales/ja.yml&lt;/span&gt;

&lt;span class="na"&gt;ja&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;activerecord&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;record_invalid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;バリデーションに失敗しました:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%{errors}'&lt;/span&gt;
  &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%{attribute}%{message}'&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# You should also include translations for other ActiveRecord errors such as "empty", "taken", etc.&lt;/span&gt;
      &lt;span class="na"&gt;blank&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;を入力してください'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  About the &lt;code&gt;rails-i18n&lt;/code&gt; gem
&lt;/h4&gt;

&lt;p&gt;The configuration above is taken from the &lt;a href="https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml" rel="noopener noreferrer"&gt;&lt;code&gt;ja.yml&lt;/code&gt;&lt;/a&gt; file in the &lt;code&gt;rails-i18n&lt;/code&gt; gem which I mentioned in the intro. Installing this gem is a quick way to set up default translations. It does not come pre-installed in your Rails project, so check the &lt;a href="https://github.com/svenfuchs/rails-i18n" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more details on installation and usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.3 Accessing your translations with &lt;code&gt;I18n.t&lt;/code&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now that you've provided translations for error messages, Rails will actually print the error messages instead of &lt;code&gt;translation missing&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next question is, how can you look up the translations you defined? For example, what if you want to &lt;code&gt;assert&lt;/code&gt; that some message is being raised in a test?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s1"&gt;'user is invalid if name is blank'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;invalid_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;invalid_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt; &lt;span class="n"&gt;message&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;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the very convenient &lt;code&gt;I18n.t&lt;/code&gt; method comes in. The &lt;code&gt;t&lt;/code&gt; stands for "translate", and it allows you to access any translation defined in your YAML files. For this example, we want to access the &lt;code&gt;errors.messages.blank&lt;/code&gt; message (refer to 2.2 for the YAML file). There are two ways to do 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;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'errors.messages.blank'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "を入力してください"&lt;/span&gt;

&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blank'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'errors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'messages'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "を入力してください"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that, you can look up any translation you've defined!&lt;/p&gt;

&lt;p&gt;Note: You can look up model names and attribute names without using the &lt;code&gt;human&lt;/code&gt; method too, like &lt;code&gt;I18n.t('activerecord.models.user')&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s1"&gt;'user is invalid if name is blank'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;invalid_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&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;name: &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expected_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'errors.messages.blank'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;invalid_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_error&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;h2&gt;
  
  
  2.4 Looking up errors with string interpolation&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://guides.rubyonrails.org/i18n.html#error-message-interpolation" rel="noopener noreferrer"&gt;https://guides.rubyonrails.org/i18n.html#error-message-interpolation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you take a look at any of the YAML files in the &lt;code&gt;rails-i18n&lt;/code&gt; gem, you may notice that some messages use string interpolation. For example, if your validation error message is for &lt;code&gt;greater_than&lt;/code&gt;, you would want to say &lt;code&gt;must be greater than %{count}&lt;/code&gt;, and fill in the number for &lt;code&gt;count&lt;/code&gt;. Rails will fill it in for you when the actual error is raised, but how can we fill in the &lt;code&gt;count&lt;/code&gt; when you look up the error message using &lt;code&gt;I18n.t&lt;/code&gt;?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'errors.messages.greater_than'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "は%{count}より大きい値にしてください"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can just pass it in as an argument:&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;I18n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'errors.messages.greater_than'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; "は5より大きい値にしてください"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I know this doesn't come close to covering everything you can do with i18n in Rails, but I hope it provides a useful introduction. Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>i18n</category>
      <category>internationalization</category>
    </item>
    <item>
      <title>Running RuboCop only on modified files, in a project with no RuboCop</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Thu, 22 Oct 2020 09:39:30 +0000</pubDate>
      <link>https://forem.com/risafj/running-rubocop-only-on-modified-files-in-a-project-with-no-rubocop-2ioe</link>
      <guid>https://forem.com/risafj/running-rubocop-only-on-modified-files-in-a-project-with-no-rubocop-2ioe</guid>
      <description>&lt;p&gt;I like using RuboCop. I think using a linter is a good idea, especially for team projects.&lt;/p&gt;

&lt;p&gt;However, I ran into a situation where the people I was working with had opinions against installing RuboCop in the Rails project, and asked me to use it locally for my own code if I wanted.&lt;/p&gt;

&lt;p&gt;I couldn't run RuboCop on the entire project, because it would try to fix a mountain of errors for existing unlinted code. So I ended up installing the relevant gems into my system, and running a command that only checks modified files. Here are the steps involved.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Install gems with &lt;code&gt;gem install&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Since we can't add the gems to the &lt;code&gt;Gemfile&lt;/code&gt;, install the necessary gems into your system with &lt;code&gt;gem install&lt;/code&gt; instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem install rubocop rubocop-performance rubocop-rails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  2. Create &lt;code&gt;.rubocop.yml&lt;/code&gt; in the home directory
&lt;/h1&gt;

&lt;p&gt;Usually, the config file is placed in the project root folder to be shared by everyone in the dev team. In our case, we can create one in the home directory instead: &lt;code&gt;~/.rubocop.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://docs.rubocop.org/rubocop/configuration.html" rel="noopener noreferrer"&gt;https://docs.rubocop.org/rubocop/configuration.html&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The behavior of RuboCop can be controlled via the .rubocop.yml configuration file. It makes it possible to enable/disable certain cops (checks) and to alter their behavior if they accept any parameters. The file can be placed in your home directory, XDG config directory, or in some project directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Go ahead and configure the YML file as you normally would. Configuration instructions can be found in the docs linked above.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Run RuboCop on modified files
&lt;/h1&gt;

&lt;p&gt;We can run the following command to run RuboCop on unstaged changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;-N&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; | xargs rubocop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git add -N .&lt;/code&gt; is necessary for including new files that are not tracked by git&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git diff --name-only&lt;/code&gt; gives you a list of files with changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;xargs rubocop&lt;/code&gt; runs RuboCop on those files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[Optional]&lt;br&gt;
Since the line above is a lot to type, I added it to my git aliases. Now, I can just run &lt;code&gt;cop&lt;/code&gt; in my command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.gitconfig&lt;/span&gt;
&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;cop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"git add -N .; git diff --name-only | xargs rubocop"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>rubocop</category>
    </item>
    <item>
      <title>Why I recommend Harvard's CS50x online course to every self-taught developer</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Mon, 04 May 2020 06:32:54 +0000</pubDate>
      <link>https://forem.com/risafj/why-i-recommend-harvard-s-cs50x-online-course-to-every-self-taught-developer-4a1m</link>
      <guid>https://forem.com/risafj/why-i-recommend-harvard-s-cs50x-online-course-to-every-self-taught-developer-4a1m</guid>
      <description>&lt;p&gt;&lt;a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" rel="noopener noreferrer"&gt;CS50's Introduction to Computer Science&lt;/a&gt; is a free online course based on one of the most popular on-campus courses at Harvard University. In this course, you can learn about core computer science concepts, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Algorithms - binary search, bubble sort, merge sort etc.&lt;/li&gt;
&lt;li&gt;Data structures - linked lists, hash tables, etc.&lt;/li&gt;
&lt;li&gt;Memory - stack and heap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The languages used are C, Python, SQL, plus some others based on what you choose for the final track (UPDATE: It seems like they got rid of tracks for the 2021 version). There are a myriad of free CS courses available online, but this is the most enjoyable and engaging one I've found so far.&lt;/p&gt;

&lt;p&gt;I wrote "self-taught developer" in the title because I figured that those with no formal CS education are less likely to have had a chance to be exposed to these topics in depth. But of course, I would recommend it to anyone who's interested in these topics.&lt;/p&gt;

&lt;p&gt;Here are three points that I love about this course, and two potential cons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Their philosophy&lt;/li&gt;
&lt;li&gt;The lectures are a joy to watch&lt;/li&gt;
&lt;li&gt;The assignments are creative&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;(Potential) Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;May not be useful for your day-to-day work&lt;/li&gt;
&lt;li&gt;You don't get to see staff-implemented solutions to assignments&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Pros
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Their philosophy
&lt;/h3&gt;

&lt;p&gt;This course is geared towards people of all programming levels. They tell you in &lt;a href="https://cs50.harvard.edu/x/2020/notes/0/" rel="noopener noreferrer"&gt;the first week&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;what ultimately matters in this course is not so much where you end up relative to your classmates but where you end up relative to yourself when you began.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just for reference, I have 1 year of professional experience, though none in C or Python. Some of the assignments were still very tough for me, and one week's worth of coursework would take me about 10 hours to complete, depending on the material (As a Ruby dev, the Python stuff was a lot more intuitive than C!).&lt;/p&gt;

&lt;p&gt;The course does a great job of being beginner-friendly and challenging at the same time, because the lectures walk you through the content well but the assignments require you to really &lt;em&gt;think&lt;/em&gt; and do more research. Also, often they give you a choice of two assignments, depending on whether you're "more comfortable" or "less comfortable".&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The lectures are a joy to watch
&lt;/h3&gt;

&lt;p&gt;In addition to being informative, Professor David Malan's lectures are well-paced, animated, and creative. Watch any of them, and you'll probably agree (available on &lt;a href="https://www.youtube.com/playlist?list=PLhQjrBD2T381L3iZyDTxRwOBuUt6m1FnW" rel="noopener noreferrer"&gt;Youtube&lt;/a&gt; too).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The assignments are creative
&lt;/h3&gt;

&lt;p&gt;Rather than boring tasks like "iterate over this array in C", you get to implement programs like photo filters, spell checkers, voting systems, and Hogwarts student databases, using concepts that you learned along the way.&lt;/p&gt;

&lt;p&gt;Another thing I like is their testing system called Check50, which gives you instant feedback when you submit your code. It's gratifying when you finally get all the tests to return a green smiley face :)&lt;/p&gt;

&lt;p&gt;Example:&lt;br&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%2Fi%2Faadl2kk7bfckrofox2xn.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%2Fi%2Faadl2kk7bfckrofox2xn.png" alt="Alt Text" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  (Potential) Cons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. May not be useful for your day-to-day work
&lt;/h3&gt;

&lt;p&gt;For better or for worse, this is not a crash course. If you want to learn a specific topic quickly, like React for example, there are better tutorials online (complete digression, but I highly recommend &lt;a href="https://scrimba.com/" rel="noopener noreferrer"&gt;Scrimba&lt;/a&gt; for learning front-end languages/frameworks including React).&lt;/p&gt;

&lt;p&gt;That being said, I think learning these core concepts of computer science will be beneficial to any developer in some way.&lt;br&gt;
For example, I'd been using Ruby hashes (dictionaries) for a long time, and I knew that finding a value from a hash by its key was much faster than finding a value from an array. But before I took this course, I didn't understand how hashes were implemented under the hood. Learning about hash tables and actually implementing one allowed me to also understand Ruby hashes better.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. You don't get to see staff-implemented solutions to assignments
&lt;/h3&gt;

&lt;p&gt;As I mentioned, when you submit an assignment online, your code gets tested via a system called Check50. You don't get to see the solution that the staff had in mind or do a side-by-side comparison with yours.&lt;/p&gt;

&lt;p&gt;This wasn't always a problem and I understand their reasons, but there was one particular occasion when I wished I could see staff-implemented solutions: when I was writing SQL queries. My queries worked, and passed the Check50 tests, but there was no way to tell if they could've been optimized further. Seeing staff-implemented answers would've helped there.&lt;/p&gt;




&lt;p&gt;If this course sounds interesting to you, I highly recommend that you check it out on &lt;a href="https://www.edx.org/course/cs50s-introduction-to-computer-science" rel="noopener noreferrer"&gt;edX&lt;/a&gt;. And if you do decide to take it, best of luck to you.&lt;/p&gt;

&lt;p&gt;I'm also still enrolled, about to start the iOS track. Wish me luck!&lt;/p&gt;




&lt;h3&gt;
  
  
  Update - 2020-12-28
&lt;/h3&gt;

&lt;p&gt;The final project took me some time, but I've completed the course :)&lt;/p&gt;

&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%2Fcertificates.cs50.io%2Fa34cc20c-349b-4393-bdd0-40d455f45a4d.png%3Fsize%3Dletter" 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%2Fcertificates.cs50.io%2Fa34cc20c-349b-4393-bdd0-40d455f45a4d.png%3Fsize%3Dletter" width="800" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>computerscience</category>
      <category>cs50</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to use MySQL on Docker for Rails local development (switching MySQL versions made easy!)</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Wed, 22 Apr 2020 12:42:37 +0000</pubDate>
      <link>https://forem.com/risafj/how-to-use-mysql-on-docker-for-rails-local-development-switching-mysql-versions-made-easy-4ihf</link>
      <guid>https://forem.com/risafj/how-to-use-mysql-on-docker-for-rails-local-development-switching-mysql-versions-made-easy-4ihf</guid>
      <description>&lt;p&gt;&lt;em&gt;Header image by &lt;a href="https://unsplash.com/@emerald_?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Noah Boyer&lt;/a&gt; on Unsplash&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, I joined a Rails project that requires MySQL 5.6. But I'd already been using MySQL 8 (the latest version as of this writing) for a different project, installed locally via Homebrew.&lt;/p&gt;

&lt;p&gt;Installing both versions using Homebrew and doing &lt;code&gt;brew switch mysql@&amp;lt;version&amp;gt;&lt;/code&gt; turned out to be a nightmare, so I decided to use MySQL on Docker.&lt;br&gt;
I am no Docker expert, and it took some time to figure out. But it's actually very straightforward and easy to use, so I highly recommend it if you need multiple versions of MySQL.&lt;/p&gt;

&lt;p&gt;This guide walks you through setting up Docker containers for different versions of MySQL, and the necessary Rails configuration (only the last part is Rails-specific). Also, no worries if you're not familiar with Docker commands :) I provided explanations for the command options to make this as beginner-friendly as I could.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This guide is for MacOS.&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Steps
&lt;/h1&gt;

&lt;p&gt;For the purpose of this guide, let's say you're trying to use the latest version of MySQL and version 5.6. Here are the steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Docker Desktop&lt;/li&gt;
&lt;li&gt;Start a Docker container (MySQL version: latest)&lt;/li&gt;
&lt;li&gt;Stop the container to free up port 3306&lt;/li&gt;
&lt;li&gt;Start another Docker container (MySQL version: 5.6)&lt;/li&gt;
&lt;li&gt;Configure Rails to use MySQL on Docker&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  1. Install Docker Desktop
&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://hub.docker.com/editions/community/docker-ce-desktop-mac/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; if you haven't yet. After it's installed, you can find it under &lt;code&gt;~/Applications&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Start a Docker container (MySQL version: latest)
&lt;/h2&gt;

&lt;p&gt;Let's boot up a container with the latest version of MySQL first.&lt;br&gt;
For any Docker beginners out there, &lt;code&gt;docker run&lt;/code&gt; creates a new container with the specified image (&lt;code&gt;mysql:latest&lt;/code&gt; in this case) and runs it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-p&lt;/span&gt; 3306:3306 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# Maps the container's port 3306 (default port for MySQL) to your machine's port 3306.&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; test-mysql-8 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# Name your container whatever you like, as long as it's unique.&lt;/span&gt;
&lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# MySQL requires that you set this. Will error out otherwise.&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# Stands for "detach"; keeps the container running in the background.&lt;/span&gt;
mysql:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Warning: &lt;code&gt;docker run&lt;/code&gt; requires that you enter any options before the image name. For example, &lt;code&gt;docker run mysql:latest -p 3306:3306 ...&lt;/code&gt; does not work.&lt;/p&gt;

&lt;p&gt;Next, let's check that it's actually running with &lt;code&gt;docker ps&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
f89e544f49d5        mysql:latest        &lt;span class="s2"&gt;"docker-entrypoint.s…"&lt;/span&gt;   5 minutes ago      Up 5 minutes       0.0.0.0:3306-&amp;gt;3306/tcp, 33060/tcp   test-mysql-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also check from the Docker Desktop dashboard:&lt;br&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%2Fi%2Fbv77r8elswkfiesqbmod.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%2Fi%2Fbv77r8elswkfiesqbmod.png" alt="Alt Text" width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Stop the container to free up port 3306
&lt;/h2&gt;

&lt;p&gt;Using the command in step 2, now you can boot up any other MySQL containers you need. But you can't run two containers mapped to port 3306 at the same time. So first, let's stop the currently running container to free up the port (you can restart this container later).&lt;/p&gt;

&lt;p&gt;There are two ways to do this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;From the command line: &lt;code&gt;docker stop &amp;lt;container name&amp;gt;&lt;/code&gt; (use the name you specified under &lt;code&gt;--name&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From the Docker Desktop dashboard: Click the stop button&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&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%2Fi%2F7mi4vb6vq5kls008qb62.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%2Fi%2F7mi4vb6vq5kls008qb62.png" alt="Alt Text" width="800" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: If you try to start another container mapped to port 3306 without stopping the current one, you will get this error: &lt;code&gt;Bind for 0.0.0.0:3306 failed: port is already allocated.&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Start another Docker container (MySQL version: 5.6)
&lt;/h2&gt;

&lt;p&gt;You can use the same command as step 2 to boot up containers for other versions of MySQL. Just make sure to specify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tag (version) you need, like &lt;code&gt;mysql:5.6&lt;/code&gt; (list of available tags &lt;a href="https://hub.docker.com/_/mysql" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A different name under &lt;code&gt;--name&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And voila! You have two versions of MySQL now (you can check via &lt;code&gt;docker ps&lt;/code&gt;/dashboard).&lt;/p&gt;

&lt;p&gt;Whenever you need to switch versions, you can just stop the running container as shown in step 3, and start the necessary container by &lt;code&gt;docker start &amp;lt;container name&amp;gt;&lt;/code&gt; or the start button in the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Configure Rails to use MySQL on Docker
&lt;/h2&gt;

&lt;p&gt;When you try to create the MySQL databases for your Rails project by running commands like &lt;code&gt;rails db:setup&lt;/code&gt;, you may still get an error like this: &lt;code&gt;Mysql2::Error: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It turns out, MySQL has a unique definition of &lt;code&gt;localhost&lt;/code&gt; (&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/connecting.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On Unix, MySQL programs treat the host name &lt;code&gt;localhost&lt;/code&gt; specially, in a way that is likely different from what you expect compared to other network-based programs: the client connects using a Unix socket file. The &lt;code&gt;--socket&lt;/code&gt; option or the &lt;code&gt;MYSQL_UNIX_PORT&lt;/code&gt; environment variable may be used to specify the socket name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Long story short, &lt;code&gt;127.0.0.1&lt;/code&gt; and &lt;code&gt;localhost&lt;/code&gt; are not the same thing for MySQL. So you need to specify in the database settings that you're using &lt;code&gt;127.0.0.1&lt;/code&gt; (not &lt;code&gt;localhost&lt;/code&gt;). There are two ways to do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure &lt;code&gt;config/database.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql2&lt;/span&gt;
  &lt;span class="c1"&gt;# Other settings...&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt; &lt;span class="c1"&gt;# The MYSQL_ROOT_PASSWORD you specified&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Specify the host&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Configure &lt;code&gt;DATABASE_URL&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;export DATABASE_URL=mysql2://root:@127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Either of these should do the trick. The second option is useful if you're joining a team project and can't edit &lt;code&gt;config/database.yml&lt;/code&gt; as you please.&lt;/p&gt;

&lt;p&gt;Thank you for reading and please let me know if I missed anything!&lt;/p&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/reference/run/" rel="noopener noreferrer"&gt;Docker run reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/mysql" rel="noopener noreferrer"&gt;MySQL Docker images&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>mysql</category>
      <category>rails</category>
    </item>
    <item>
      <title>Get DynamoDB Local up and running in 3 minutes with Docker</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Tue, 25 Feb 2020 04:49:43 +0000</pubDate>
      <link>https://forem.com/risafj/get-dynamodb-local-up-and-running-in-3-minutes-with-docker-5ec6</link>
      <guid>https://forem.com/risafj/get-dynamodb-local-up-and-running-in-3-minutes-with-docker-5ec6</guid>
      <description>&lt;p&gt;While using DynamoDB for a side project, I was surprised to learn that there's a version you can run locally. I was doubly pleasantly surprised to find it useful and easy to set up! There's even a GUI available, so you don't have to do everything from the command line. Here is a quick guide for setup and usage.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Guide is for MacOS.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Advantages of using DynamoDB Local&lt;/li&gt;
&lt;li&gt;Setup&lt;/li&gt;
&lt;li&gt;GUI&lt;/li&gt;
&lt;li&gt;Creating tables and items from the command line&lt;/li&gt;
&lt;li&gt;Accessing DynamoDB Local from the SAM CLI&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  1. Advantages of using DynamoDB Local &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Using a local instance means you don't have to hit the actual DynamoDB tied to your AWS account, which in turn means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your production DB can be kept clean for actual production data&lt;/li&gt;
&lt;li&gt;Your AWS costs will probably be cheaper&lt;/li&gt;
&lt;li&gt;You don't need an internet connection (except for initial setup)&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  2. Setup &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/docker-for-mac/install/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Launch Docker image
&lt;/h2&gt;

&lt;p&gt;From the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 amazon/dynamodb-local

&lt;span class="c"&gt;# You should see something like this:&lt;/span&gt;
Initializing DynamoDB Local with the following configuration:
Port:   8000
InMemory:   &lt;span class="nb"&gt;true
&lt;/span&gt;DbPath: null
SharedDb:   &lt;span class="nb"&gt;false
&lt;/span&gt;shouldDelayTransientStatuses:   &lt;span class="nb"&gt;false
&lt;/span&gt;CorsParams: &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...And that's it! You can now create tables and put data inside!&lt;br&gt;
Reference: &lt;a href="https://hub.docker.com/r/amazon/dynamodb-local/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; All data in the local database(s) are cleared every time the container is shut down. If you want the data to persist, it looks like you can use the &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html" rel="noopener noreferrer"&gt;&lt;code&gt;sharedDB&lt;/code&gt; option&lt;/a&gt;. So far I've found it easy to simply create tables/data from the command line each time (I don't have much initial data).&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  GUI &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;There's an unofficial but user-friendly GUI for DynamoDB Local, called &lt;a href="https://github.com/aaronshaf/dynamodb-admin" rel="noopener noreferrer"&gt;dynamodb-admin&lt;/a&gt; (check the link for more detailed instructions).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install: &lt;code&gt;npm install -g dynamodb-admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use:

&lt;ol&gt;
&lt;li&gt;Open a new terminal window, and execute &lt;code&gt;dynamodb-admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;localhost:8001&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using it is very straightforward so I won’t walk you through it here, but to give you an idea, here are what the screens look like.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a new item in a table
&lt;/h3&gt;

&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%2Fi%2F6mb686e07xtqieptyndv.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%2Fi%2F6mb686e07xtqieptyndv.png" alt="Alt Text" width="485" height="306"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  See items inside a table
&lt;/h3&gt;

&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%2Fi%2Fosndwi09754gbs4omms5.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%2Fi%2Fosndwi09754gbs4omms5.png" alt="Alt Text" width="587" height="195"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  4. Creating tables and items from the command line &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;If you're like me, and you want to create data by copy/pasting code in the command line instead of doing it manually from the GUI, here are some examples.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create table
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb create-table &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:8000 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--table-name&lt;/span&gt; &lt;span class="s1"&gt;'Beverages'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--attribute-definitions&lt;/span&gt; &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Name,AttributeType&lt;span class="o"&gt;=&lt;/span&gt;S &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--key-schema&lt;/span&gt; &lt;span class="nv"&gt;AttributeName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Name,KeyType&lt;span class="o"&gt;=&lt;/span&gt;HASH &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--provisioned-throughput&lt;/span&gt; &lt;span class="nv"&gt;ReadCapacityUnits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5,WriteCapacityUnits&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure the &lt;code&gt;endpoint-url&lt;/code&gt; points to &lt;code&gt;localhost:8000&lt;/code&gt;, or it will try to create a table in your actual (remote) DynamoDB.&lt;/li&gt;
&lt;li&gt;Notice how I'm only creating the &lt;code&gt;Name&lt;/code&gt; column, which is my primary key, and not the &lt;code&gt;Pros&lt;/code&gt; or &lt;code&gt;Cons&lt;/code&gt; columns. This is because the only options for &lt;code&gt;AttributeType&lt;/code&gt; when creating a table are &lt;code&gt;S&lt;/code&gt;/&lt;code&gt;N&lt;/code&gt;/&lt;code&gt;B&lt;/code&gt; (String, Number or Binary). You can create other types of columns later when you populate the table with data (&lt;a href="https://stackoverflow.com/a/48809570/11249670" rel="noopener noreferrer"&gt;further explanation&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/dynamodb/create-table.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create item
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws dynamodb put-item &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--endpoint-url&lt;/span&gt; http://localhost:8000 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--table-name&lt;/span&gt; &lt;span class="s1"&gt;'Beverages'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--item&lt;/span&gt; &lt;span class="s1"&gt;'{
  "Name": {"S": "Green tea"},
  "Pros": {"SS": ["Delicious", "Supposedly healthy"]},
  "Cons": {"SS": ["Sometimes too bitter"]}
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SS&lt;/code&gt; here means “string set”, or in other words, an array of strings. You can read about the data types allowed for DynamoDB attributes &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.DataTypes.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reference: &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/dynamodb/put-item.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also perform other actions using the CLI like deleting items or tables, but I’ll leave it here for now.&lt;/p&gt;
&lt;h1&gt;
  
  
  5. Accessing DynamoDB Local from the SAM CLI  &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;If you’re not using the AWS SAM CLI, you can skip this section entirely. But if you &lt;em&gt;are&lt;/em&gt; using the SAM CLI to develop an AWS lambda and want to access your local instance of DynamoDB from it (like me!), this is for you.&lt;br&gt;
When you execute the lambda with a command like &lt;code&gt;sam local invoke&lt;/code&gt;, you’ll want to configure the endpoint so it accesses the local instance. Here’s what you can do:&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;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Use the local Docker endpoint if executed locally&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;process&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="nx"&gt;AWS_SAM_LOCAL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://docker.for.mac.localhost:8000&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;dynamo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the configuration above doesn't work for you, or you want to know what's happening, check out this &lt;a href="https://stackoverflow.com/q/48926260/11249670" rel="noopener noreferrer"&gt;Stack Overflow thread&lt;/a&gt;. The accepted answer is for Linux, and it's more involved - you set up a local network to get SAM and DynamoDB to connect. But thankfully there is an easier solution for Mac, which is the code snippet I wrote above.&lt;/p&gt;




&lt;p&gt;Thank you for reading! Please let me know if you have any tips to make DynamoDB Local even easier to use :)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Evaluate all values in an array in Javascript</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Sat, 19 Oct 2019 11:08:56 +0000</pubDate>
      <link>https://forem.com/risafj/evaluate-all-values-in-an-array-in-javascript-using-every-8b0</link>
      <guid>https://forem.com/risafj/evaluate-all-values-in-an-array-in-javascript-using-every-8b0</guid>
      <description>&lt;p&gt;This post summarizes a neat solution to a problem I ran into recently. Hope you find this useful!&lt;/p&gt;




&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; is a well-known, convenient operator for checking if all conditions are true. For example, imagine we're trying to perform validations on a input form before saving a user profile:&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;function&lt;/span&gt; &lt;span class="nf"&gt;saveProfileIfValid&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="nf"&gt;firstNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jerry&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;lastNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Smith&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;emailIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js@email.com&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;saveProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// save only if all validations pass&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;The problem was, the code above would stop executing once it found a condition that returned false. I didn't want that, because my validations would add error messages to be displayed on the screen like below. How can we make all validations run regardless of the results?&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;let&lt;/span&gt; &lt;span class="nx"&gt;errorMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;firstNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstName&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="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// add error messages, if any&lt;/span&gt;
    &lt;span class="nx"&gt;errorMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;First name must be 1-10 characters&lt;/span&gt;&lt;span class="dl"&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;The solution I found is below - it took some time and googling to get to it, so I'm leaving this for future reference.&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;function&lt;/span&gt; &lt;span class="nf"&gt;validInput&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;firstNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jerry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;lastNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Smith&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;emailIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js@email.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveProfileIfValid&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="nf"&gt;validInput&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;saveProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// save only if all validations pass&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;What's happening here?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every" rel="noopener noreferrer"&gt;&lt;code&gt;.every&lt;/code&gt;&lt;/a&gt; is a method that tests every element in an array against a condition and returns a boolean. It returns &lt;code&gt;true&lt;/code&gt; only if all elements pass the test.
(&lt;em&gt;Update: I had a misconception about &lt;code&gt;.every&lt;/code&gt; - please see the update below for details.&lt;/em&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isTen&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;10&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isTen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// returns false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean" rel="noopener noreferrer"&gt;&lt;code&gt;Boolean&lt;/code&gt;&lt;/a&gt; is a function that converts a value into a boolean, like the double bang (&lt;code&gt;!!&lt;/code&gt;). Examples below:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&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="kc"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Boolean&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="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, all validations in the array get executed, and then finally a boolean value is returned based on whether &lt;strong&gt;all&lt;/strong&gt; validations returned true or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  UPDATE
&lt;/h3&gt;

&lt;p&gt;According to a helpful comment I received, &lt;code&gt;.every&lt;/code&gt; actually exits once it finds a falsy value, just like &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;. The reason why the code above works is because all the items in the array get evaluated before &lt;code&gt;.every&lt;/code&gt; is executed. This can also be achieved by the code below, in a simpler manner. (Thank you Kevin!)&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;function&lt;/span&gt; &lt;span class="nf"&gt;saveProfileIfValid&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;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;firstNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jerry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- executes before if( ... )&lt;/span&gt;
    &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lastNameIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Smith&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;- executes before if( ... )&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;emailIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js@email.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;- executes before if( ... )&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// all the validations have already happened&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;isValid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isValid&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;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="nf"&gt;saveProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// save only if all validations pass&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;Thank you for reading, and please let me know if you have a better solution!&lt;/p&gt;

&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/35024490/how-to-return-true-if-all-values-of-array-are-true-otherwise-return-false" rel="noopener noreferrer"&gt;How to return true if all values of array are true otherwise return false?
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/53897673/check-if-all-values-in-array-are-true-then-return-a-true-boolean-statement-jav" rel="noopener noreferrer"&gt;Check if all values in array are true, then return a true boolean statement
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>array</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Beginner’s guide to OAuth: Understanding access tokens and authorization codes using Google API</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Fri, 16 Aug 2019 04:13:35 +0000</pubDate>
      <link>https://forem.com/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988</link>
      <guid>https://forem.com/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988</guid>
      <description>&lt;p&gt;As a user, it’s easy and convenient to use your Google account (or Facebook, Twitter, etc.) to sign into other services.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You click the "Sign in with Google" button&lt;/li&gt;
&lt;li&gt;You get redirected to a consent screen&lt;/li&gt;
&lt;li&gt;You click "Allow"&lt;/li&gt;
&lt;li&gt;The page redirects to the actual application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But there's actually a lot going on under the hood, and it can be useful as a developer to understand it. When I implemented OAuth in Rails for the first time, I was using an external library (the &lt;a href="https://github.com/Sorcery/sorcery" rel="noopener noreferrer"&gt;&lt;code&gt;sorcery&lt;/code&gt;&lt;/a&gt; gem’s external module), which made it easy for me to gloss over the process. Yet as soon as I wanted to customize something, I realized it was necessary to have a better understanding of it. I decided to re-implement the flow without the help of any authentication helper gems.&lt;/p&gt;

&lt;p&gt;Based on what I learned, I wrote this basic explanation of the OAuth flow and what authorization codes and access/refresh tokens are. I’m using Google as the OAuth provider.&lt;/p&gt;

&lt;p&gt;This post is meant for people who are not very familiar with OAuth. In other words, if you already know how it works and can understand Google's OAuth &lt;a href="https://developers.google.com/identity/protocols/OAuth2WebServer" rel="noopener noreferrer"&gt;guide&lt;/a&gt;, this post may be too elementary for you.&lt;/p&gt;

&lt;h1&gt;
  
  
  What are authorization codes and access tokens?
&lt;/h1&gt;

&lt;p&gt;In the OAuth flow, your app needs to send two requests to Google. The first request is to get an &lt;strong&gt;authorization code&lt;/strong&gt;, the second is to get an &lt;strong&gt;access token&lt;/strong&gt;. They both take the form of &lt;a href="https://stackoverflow.com/a/10299106/11249670" rel="noopener noreferrer"&gt;long strings&lt;/a&gt;, but have different purposes.&lt;br&gt;
This kind of similar terminology can be tricky at first, so let's first briefly cover what they are.&lt;/p&gt;

&lt;p&gt;I bet this screen looks familiar. The &lt;strong&gt;authorization code&lt;/strong&gt; is a code that Google sends back to your app once the user consents on this screen.&lt;/p&gt;

&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftxuse5twhl8flhukvj5l.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ftxuse5twhl8flhukvj5l.png" width="800" height="1040"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This code can be used to get an &lt;strong&gt;access token&lt;/strong&gt;. Once you've received the authorization code, you put it in the params and send a second request to Google, essentially saying "Give me an access token so I can send requests on behalf of this user?"&lt;br&gt;
Google's response to that should include an access token. By putting this token in your request headers, you can do things like create a new event in the user's Google Calendar or access a user’s Gmail.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key things to note:
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Authorization Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Only valid for one-time use, since its only usage is to exchange it for an access token&lt;/li&gt;
&lt;li&gt;Expires very quickly (according to this &lt;a href="https://www.oauth.com/oauth2-servers/authorization/the-authorization-response/" rel="noopener noreferrer"&gt;article&lt;/a&gt;, the OAuth protocol's recommended maximum is 10 minutes, and many services' authorization codes expire even earlier)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Access Token
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Can be obtained using the authorization code&lt;/li&gt;
&lt;li&gt;Put in the headers of any API requests to Google on behalf of the user&lt;/li&gt;
&lt;li&gt;Expires after one hour (the expiration time may vary if you're using something besides Google)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What do you do after an hour when the access token expires? Do you have to make the user re-login to get a new one?&lt;br&gt;
No. You can get something called a &lt;strong&gt;refresh token&lt;/strong&gt;, which allows you to get new access tokens. More on this in the &lt;a href="https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988#use-refresh-tokens-to-get-new-access-tokens"&gt;last section&lt;/a&gt; of this article.&lt;/p&gt;
&lt;h1&gt;
  
  
  OAuth step-by-step
&lt;/h1&gt;

&lt;p&gt;Let's go over the basic OAuth flow again, except this time, we're looking at it from the developer's point of view, not the user. I've included Ruby code snippets as well.&lt;/p&gt;

&lt;p&gt;Note: I am skipping over the very first configuration you have to do, which is to create your &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; in the &lt;a href="https://console.developers.google.com/apis/dashboard" rel="noopener noreferrer"&gt;Google API Console&lt;/a&gt;. This &lt;a href="https://developers.google.com/adwords/api/docs/guides/authentication#create_a_client_id_and_client_secret" rel="noopener noreferrer"&gt;guide&lt;/a&gt; walks you through the steps.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1. User clicks the "Sign In With Google" button in your app
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Step 2. Redirect to the Google consent screen
&lt;/h3&gt;

&lt;p&gt;In my case, clicking the button calls the &lt;code&gt;oauths_controller&lt;/code&gt;'s &lt;code&gt;oauth&lt;/code&gt; method, which then redirects the page.&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;# oauths_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;oauth&lt;/span&gt;
  &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;client_id: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;response_type: &lt;/span&gt;&lt;span class="s1"&gt;'code'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="s1"&gt;'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;redirect_uri: &lt;/span&gt;&lt;span class="s1"&gt;'http://my-app.com/oauth/callback?provider=google'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;access_type: &lt;/span&gt;&lt;span class="s1"&gt;'offline'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="s1"&gt;'https://accounts.google.com/o/oauth2/v2/auth?'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_query&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;A quick explanation of the query parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;client_id&lt;/code&gt; is the one you created in the Google API Console. I’ve just stored it in an environment variable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;response_type: 'code'&lt;/code&gt; signals that you'd like an authorization code for obtaining an access token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scope&lt;/code&gt; defines what kinds of permissions you need. I needed access to the user's Google Calendar in addition to the user's name and email address, which is why I have the three scopes above. The full list of scopes can be found in the &lt;a href="https://developers.google.com/identity/protocols/googlescopes" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redirect_uri&lt;/code&gt; is the URI that Google redirects to once the user hits "Allow". You can't put any random URI here; it needs to match one of the URIs you added in the Google API Console.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;access_type: 'offline'&lt;/code&gt; has to do with the refresh tokens I mentioned above, and we will cover this &lt;a href="https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988#use-refresh-tokens-to-get-new-access-tokens"&gt;later&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3. The user clicks "Allow" on the consent screen
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Step 4. The page redirects to your &lt;code&gt;callback_uri&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Google handles this part. In my case, the &lt;code&gt;oauths_controller&lt;/code&gt;'s &lt;code&gt;callback&lt;/code&gt; method gets called.&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;# routes.rb&lt;/span&gt;
&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s1"&gt;'oauth/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s1"&gt;'oauths#callback'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parameters of this incoming request from Google includes an authorization code (in &lt;code&gt;params[:code]&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5. Exchange the authorization code for an access token
&lt;/h3&gt;

&lt;p&gt;Next, you need to make an HTTP POST request to Google's token endpoint (&lt;code&gt;/oauth2/v4/token&lt;/code&gt;) to get an access token in exchange for the authorization code you just received.&lt;/p&gt;

&lt;p&gt;Note: I’m using the &lt;a href="https://github.com/jnunemaker/httparty" rel="noopener noreferrer"&gt;&lt;code&gt;HTTParty&lt;/code&gt;&lt;/a&gt; gem to make HTTP requests, but of course this isn’t mandatory.&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;# oauths_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;
  &lt;span class="c1"&gt;# Exchange the authorization code for an access token (step 5)&lt;/span&gt;
  &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;code: &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;:code&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;client_id: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;client_secret: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_SECRET'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;redirect_uri: &lt;/span&gt;&lt;span class="s1"&gt;'http://my-app.com/oauth/callback?provider=google'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;grant_type: &lt;/span&gt;&lt;span class="s1"&gt;'authorization_code'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://www.googleapis.com/oauth2/v4/token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Save the access token (step 6)&lt;/span&gt;
  &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:access_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'access_token'&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;As for the params in this POST request, &lt;a href="https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type#exchange-the-authorization-code-for-an-access-token" rel="noopener noreferrer"&gt;this article&lt;/a&gt; provides a good explanation if you're interested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6. Save the access token
&lt;/h3&gt;

&lt;p&gt;As seen in the final part of the snippet above, I'm saving the returned &lt;code&gt;access_token&lt;/code&gt; in the session. Now, whenever I want to make a request to the Google API on behalf of the user, I can do so using this token.&lt;/p&gt;

&lt;p&gt;For example, to get a list of the user's Google Calendar events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:access_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'https://www.googleapis.com/calendar/v3/calendars/primary/events'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;There we go! Now we've successfully implemented the OAuth flow using authorization tokens.&lt;/p&gt;

&lt;h1&gt;
  
  
  Use refresh tokens to get new access tokens
&lt;/h1&gt;

&lt;p&gt;As mentioned above, access tokens expire after a certain amount of time (e.g. 1 hour). If your app's login also expires at the same time or earlier, you have nothing to worry about - the user would have to re-login anyway.&lt;/p&gt;

&lt;p&gt;But what if your app allowed users to be logged in for longer (after all, in many cases, being kicked out of an app after an hour could be obnoxious)? Google's access token would still expire, so any requests to the Google API would be rejected.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;refresh tokens&lt;/strong&gt; come in. You can get one in the same response as the one that returns an access token (Step 5), as long as you specified &lt;code&gt;access_type: 'offline'&lt;/code&gt; in the initial redirect (Step 2). &lt;/p&gt;

&lt;p&gt;Unlike access tokens, refresh tokens have no set expiration time. If your access token has expired, you can get a new one using a refresh token with an HTTP POST request like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;'client_id'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="s1"&gt;'client_secret'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'GOOGLE_CLIENT_SECRET'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;# Assuming we've saved the refresh_token in the DB along with the user info&lt;/span&gt;
  &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'grant_type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'https://www.googleapis.com/oauth2/v4/token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;query: &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:access_token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'access_token'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Refresh tokens gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. You should reuse them
&lt;/h3&gt;

&lt;p&gt;You’re generally expected to keep using the same refresh token each time you request a new access token. For this reason, Google only gives you a refresh token the first time the user consents and logs in to your app (&lt;a href="https://developers.google.com/identity/protocols/OAuth2WebServer#offline" rel="noopener noreferrer"&gt;docs&lt;/a&gt; on offline access):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This value instructs the Google authorization server to return a refresh token and an access token the first time that your application exchanges an authorization code for tokens. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that it’s important to store refresh tokens in long-term storage, like the DB.&lt;/p&gt;

&lt;p&gt;Note: If you really need to get a new refresh token every time the user logs in, you can add &lt;code&gt;prompt=consent&lt;/code&gt; to the parameters in the authorization request in &lt;a href="https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988#step-2-redirect-to-the-google-consent-screen"&gt;Step 2&lt;/a&gt;. This will require the user to consent every time they log in, but the response will always include a new refresh token.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. They can become invalid too
&lt;/h3&gt;

&lt;p&gt;While refresh tokens don’t expire after a set amount of time, they can become invalid in some cases (&lt;a href="https://developers.google.com/identity/protocols/OAuth2#token-expiration" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You must write your code to anticipate the possibility that a granted refresh token might no longer work. A refresh token might stop working for one of these reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user has revoked your app's access.&lt;/li&gt;
&lt;li&gt;The refresh token has not been used for six months.&lt;/li&gt;
&lt;li&gt;The user changed passwords and the refresh token contains Gmail scopes.&lt;/li&gt;
&lt;li&gt;The user account has exceeded a maximum number of granted (live) refresh tokens.
There is currently a limit of 50 refresh tokens per user account per client. If the limit is reached, creating a new refresh token automatically invalidates the oldest refresh token without warning.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, if the user revoked your app’s access, any requests to obtain a new access token using the existing refresh token would cease to work. In such cases, you would need to get the user to log out and re-login in order to get a new refresh token.  &lt;/p&gt;




&lt;p&gt;In this post, I handled everything manually without relying on gems like &lt;code&gt;devise&lt;/code&gt;, &lt;code&gt;sorcery&lt;/code&gt; or &lt;code&gt;omniauth-google-oauth2&lt;/code&gt;. I'm not saying that's the best approach here; realistically, it might be easier to use those gems. The goal of this post was to walk through what is actually happening, in order not to blindly let the gems handle everything. Thanks for reading!&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>authentication</category>
      <category>ruby</category>
    </item>
    <item>
      <title>SSH Key Authentication for Absolute Beginners (in Plain English!)</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Wed, 19 Jun 2019 14:23:30 +0000</pubDate>
      <link>https://forem.com/risafj/ssh-key-authentication-for-absolute-beginners-in-plain-english-2m3f</link>
      <guid>https://forem.com/risafj/ssh-key-authentication-for-absolute-beginners-in-plain-english-2m3f</guid>
      <description>&lt;p&gt;There are so many articles on SSH online, but when I was learning it felt like very few were beginner-friendly. My goals here are to provide you with a basic understanding of SSH, and enough information to create your SSH keys and connect to a server. I've tried to keep technical terms to a minimum and stick to the basics. &lt;/p&gt;

&lt;h1&gt;
  
  
  What exactly is SSH?
&lt;/h1&gt;

&lt;p&gt;According to &lt;a href="https://searchsecurity.techtarget.com/definition/Secure-Shell" rel="noopener noreferrer"&gt;Techopedia&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Secure Shell (SSH) is a cryptographic protocol and interface for executing network services, shell services and secure network communication with a remote computer. Secure Shell enables two remotely connected users to perform network communication and other services on top of an unsecured network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what is it really?&lt;br&gt;
Basically, SSH is a way to connect your computer to different computers or services. There are a wide range of authentication methods (&lt;a href="https://www.secureblackbox.com/kb/articles/SSH-Authentication-methods.rst" rel="noopener noreferrer"&gt;source&lt;/a&gt;), but usually, when people talk about connecting to something via SSH, they're referring to public key authentication.&lt;/p&gt;

&lt;p&gt;Using SSH's public key authentication is like using a username and password to log in and connect to some service, but more convenient (no need to enter your credentials every time) and secure.&lt;br&gt;
For example, if you register your credentials (called SSH keys) with a remote computer, you can access that computer from yours using the terminal. When you connect, you can use the terminal as if you're using the terminal in that remote computer, and execute the same commands (like &lt;code&gt;cd&lt;/code&gt; or &lt;code&gt;ls&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So what are SSH keys, and how do they work?&lt;/p&gt;
&lt;h1&gt;
  
  
  What are SSH Keys?
&lt;/h1&gt;

&lt;p&gt;SSH keys consist of a pair of keys, called a public and private key.&lt;br&gt;
This &lt;a href="http://blakesmith.me/2010/02/08/understanding-public-key-private-key-concepts.html" rel="noopener noreferrer"&gt;article&lt;/a&gt; has the most intuitive explanation on this topic that I've seen, so I highly recommend it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Think of a public key as being the lock. It’s not actually a key, it’s a padlock you can make lots of copies of and distribute wherever you want. For example, if you want to put your ‘padlock’ on an ssh account on another machine, you would copy it to ‘authorized_keys’ in the ~/.ssh folder. You’ve setup the padlock.&lt;/li&gt;
&lt;li&gt;Think of a private key as being the actual key. This is what you use to open the padlock that is stored on the other machine. Just like a regular key you keep it secret, safe, and out of the wrong hands.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The most important takeaway here is: NEVER share your private key.&lt;br&gt;
If you do, anyone can pretend to be you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One common usage of SSH is to connect to Github. Most people probably run into this situation when they're starting out. Until you add your SSH key to Github, you have to enter your username and password each time you want to push some code. But once you add your public key (the padlock) to Github, and have your private key (the key) registered in the &lt;a href="https://www.ssh.com/ssh/agent" rel="noopener noreferrer"&gt;ssh-agent&lt;/a&gt; (basically a key manager that tries each of your keys one by one), you're authenticated automatically and no longer have to enter your credentials.&lt;br&gt;
If you haven't done this yet, Github's &lt;a href="https://help.github.com/en/articles/connecting-to-github-with-ssh" rel="noopener noreferrer"&gt;guide&lt;/a&gt; walks you through it.&lt;/p&gt;

&lt;p&gt;How do the keys actually work? We won't go into details here, but I think this &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server#how-do-ssh-keys-work" rel="noopener noreferrer"&gt;article&lt;/a&gt; explains it well (the "client" is your computer and the "server" is the remote computer/service):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a client attempts to authenticate using SSH keys, the server can test the client on whether they are in possession of the private key. If the client can prove that it owns the private key, a shell session is spawned or the requested command is executed. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;
  
  
  Generating your keys
&lt;/h1&gt;

&lt;p&gt;Computers don't come with auto-generated keys; you have to make them using a command called &lt;code&gt;ssh-keygen&lt;/code&gt;. For details, you can refer to Github's &lt;a href="https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key" rel="noopener noreferrer"&gt;guide&lt;/a&gt; that I mentioned earlier.&lt;/p&gt;

&lt;p&gt;A few questions that come to mind regarding key generation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Q. Do I need to &lt;code&gt;ssh-keygen&lt;/code&gt; a different key pair for each server/service, or can I use the same keys everywhere?&lt;/strong&gt;&lt;br&gt;
A. You can use the same keys. Having separate keys for separate destinations doesn't make it safer from an authentication perspective. (&lt;a href="https://security.stackexchange.com/a/40061" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Q. I have a multiple computers. Should I copy the keys from one device to another and share them, or generate new ones for each computer?&lt;/strong&gt;&lt;br&gt;
A. Technically you can do either, but I recommend the latter approach, and it also seems to be the more common one (&lt;a href="https://unix.stackexchange.com/a/208498" rel="noopener noreferrer"&gt;source&lt;/a&gt;). I have a work computer and a personal one, but they have different keys.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharing the same keys:

&lt;ul&gt;
&lt;li&gt;[Pro] More convenient because you only have to register one key for all your devices to access a service.&lt;/li&gt;
&lt;li&gt;[Con] If one of your devices gets stolen and the key gets compromised, the keys for all your devices would be compromised.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Using different keys for each computer:

&lt;ul&gt;
&lt;li&gt;[Pros] Even if one of your devices gets stolen, you can simply delete that particular key from the list of authorized keys and your other devices would stay safe. Also, you can control which of your computers have access to what.&lt;/li&gt;
&lt;li&gt;[Con] You have to manually register multiple SSH keys if you want multiple devices to be able to connect to a service.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Q. Where are my keys stored and how can I see them?&lt;/strong&gt;&lt;br&gt;
A. If you've used the default settings, your keys are probably saved in &lt;code&gt;~/.ssh&lt;/code&gt;. Try the first line of the commands below in your terminal and see if the result is a long string that starts with &lt;code&gt;ssh-rsa&lt;/code&gt;. If so, that file, called &lt;code&gt;id_rsa.pub&lt;/code&gt;, is your public key. Your private key should be saved in the same directory as &lt;code&gt;id_rsa&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_rsa.pub &lt;span class="c"&gt;# public key&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_rsa &lt;span class="c"&gt;# private key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  How to connect to a server via SSH
&lt;/h1&gt;

&lt;p&gt;Once you've generated your SSH keys, you need to copy your &lt;em&gt;public&lt;/em&gt; key to the server(s) you want to access. This can be done with a command called &lt;code&gt;ssh-copy-id&lt;/code&gt;, which looks like below. If you need more details, SSH's official &lt;a href="https://www.ssh.com/ssh/copy-id" rel="noopener noreferrer"&gt;guide&lt;/a&gt; covers the specifics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-copy-id -i ~/.ssh/id_rsa.pub user@server.address.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, if you want to access the remote server, you can do so with this  command from the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh user@server.address.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, my company has a server. Let's say it's called &lt;code&gt;companyserver.com&lt;/code&gt;. And my username is &lt;code&gt;risa&lt;/code&gt;. Then, the command would look like this: &lt;code&gt;ssh risa@companyserver.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you're connecting to that server for the first time, you will most likely see a prompt like &lt;code&gt;The authenticity of host 'companyserver.com' can't be established. ECDSA key fingerprint is &amp;lt;long string&amp;gt;. Are you sure you want to continue connecting (yes/no)?&lt;/code&gt; Enter &lt;code&gt;yes&lt;/code&gt; to continue connecting.&lt;/p&gt;

&lt;p&gt;The default port for SSH is &lt;code&gt;22&lt;/code&gt;. If you need to connect to a different port, you can do so by adding the &lt;code&gt;-p&lt;/code&gt; option, like &lt;code&gt;-p 1234&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Copy files between your computer and remote server using &lt;code&gt;scp&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Let me tell you about a handy command that uses SSH. You may be familiar with the terminal's &lt;code&gt;cp&lt;/code&gt; command, which lets you copy a file inside your computer and rename it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp original_folder/file_name new_folder/new_file_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.ssh.com/ssh/scp/" rel="noopener noreferrer"&gt;&lt;code&gt;scp&lt;/code&gt;&lt;/a&gt; is a similar command that lets you do this to/from a remote server. In other words, it lets you upload and download files remotely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp file user@server.address.com:path &lt;span class="c"&gt;# upload to remote server&lt;/span&gt;
scp user@server.address.com:file path &lt;span class="c"&gt;# download from remote server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if I wanted to upload a file to my company server, it would look like this:&lt;br&gt;
&lt;code&gt;scp file_to_upload.txt risa@companyserver.com:&lt;/code&gt;&lt;br&gt;
If no path is specified after the colon, the file should be uploaded to the home directory with the same name. Do remember to include the colon though!&lt;br&gt;
On the flip side, if I wanted to download a file from my company server, it should look like this:&lt;br&gt;
&lt;code&gt;scp risa@companyserver.com:file_to_download.txt local_path&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Remember, the order is always &lt;code&gt;original_location&lt;/code&gt; then &lt;code&gt;new_location&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Hopefully this post has served as a starting point for anyone learning about SSH. Thank you for reading!&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Rails Seeds Made Simple with Seed-Fu: A Step-by-Step Guide</title>
      <dc:creator>Risa</dc:creator>
      <pubDate>Thu, 23 May 2019 23:54:05 +0000</pubDate>
      <link>https://forem.com/risafj/rails-seeds-made-simple-with-seed-fu-a-step-by-step-guide-ff2</link>
      <guid>https://forem.com/risafj/rails-seeds-made-simple-with-seed-fu-a-step-by-step-guide-ff2</guid>
      <description>&lt;p&gt;I was seeding some default data in a Rails app, but finding it increasingly difficult to maintain the data without generating duplicates or failing to delete some unnecessary records.&lt;br&gt;
Then I learned about the &lt;a href="https://github.com/mbleigh/seed-fu" rel="noopener noreferrer"&gt;&lt;code&gt;seed-fu&lt;/code&gt;&lt;/a&gt; gem, which helped me avoid such pitfalls and make the syntax look clean.&lt;/p&gt;

&lt;p&gt;In this post, I'll describe the issue I had with my original &lt;code&gt;seeds&lt;/code&gt;, and walk you through how to use &lt;code&gt;seed-fu&lt;/code&gt;. If you're using lots of &lt;code&gt;find_or_intialize_by&lt;/code&gt; / &lt;code&gt;find_or_create_by&lt;/code&gt; in your seeds, or finding it difficult to maintain your data integrity, this might be for you.&lt;/p&gt;
&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Let me give you a rough idea of what I was doing.&lt;br&gt;
I wanted to seed data for &lt;code&gt;product_types&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt;. Each &lt;code&gt;product&lt;/code&gt; belongs to a &lt;code&gt;product_type&lt;/code&gt;. My &lt;code&gt;seeds.rb&lt;/code&gt; looked like this (just longer and not as silly):&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;# db/seeds.rb&lt;/span&gt;
&lt;span class="c1"&gt;# Seed product types&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'fruit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Juicy'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'vegetable'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Get your vitamins'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'snack'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Cheat day!'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&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;attributes&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_or_initialize_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Seed products&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'fruit'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'fruit'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'ooorange'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'vegetable'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'tomato'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'snack'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'chocolate'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;each&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;attributes&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_or_create_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using &lt;code&gt;find_or_initialize_by&lt;/code&gt; for &lt;code&gt;product_types&lt;/code&gt; in order to  overwrite the record if I'm just updating a &lt;code&gt;description&lt;/code&gt;. For example, if I changed the &lt;code&gt;description&lt;/code&gt; of &lt;code&gt;fruit&lt;/code&gt; from &lt;code&gt;'Juicy'&lt;/code&gt; to &lt;code&gt;'Scrumptious'&lt;/code&gt; and ran &lt;code&gt;rails db:seed&lt;/code&gt;, Rails would update the &lt;code&gt;description&lt;/code&gt; for the existing &lt;code&gt;fruit&lt;/code&gt; record, instead of creating a new one. So far, so good.&lt;/p&gt;

&lt;p&gt;But then, I noticed an issue with &lt;code&gt;products&lt;/code&gt;. One &lt;code&gt;product_type&lt;/code&gt; can have multiple &lt;code&gt;products&lt;/code&gt;, which means I can't tell Rails to overwrite the name if there's an existing record with the same &lt;code&gt;product_type&lt;/code&gt;.&lt;br&gt;
Notice how &lt;code&gt;orange&lt;/code&gt; is misspelled? If you corrected the spelling and reloaded the seeds, the product called &lt;code&gt;ooorange&lt;/code&gt; would remain while another one called &lt;code&gt;orange&lt;/code&gt; would be created. In order to clean this up, you would have to manually go into the console and delete the unnecessary &lt;code&gt;product&lt;/code&gt;, or &lt;code&gt;reset&lt;/code&gt; your DB - both unideal.&lt;/p&gt;

&lt;p&gt;Note: I later realised that you could designate the &lt;code&gt;id&lt;/code&gt; for each record and update your records with the &lt;code&gt;id&lt;/code&gt; as the key, without using this gem at all. But I'll focus on the solution using &lt;code&gt;seed-fu&lt;/code&gt; in this post.&lt;/p&gt;
&lt;h1&gt;
  
  
  Manage your seeds with &lt;code&gt;seed-fu&lt;/code&gt;
&lt;/h1&gt;
&lt;h2&gt;
  
  
  1. Installation
&lt;/h2&gt;

&lt;p&gt;Add the gem to your &lt;code&gt;Gemfile&lt;/code&gt; and run &lt;code&gt;bundle&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'seed-fu'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Create your seed folder
&lt;/h2&gt;

&lt;p&gt;Choose where to put your files from the two options below; &lt;code&gt;seed-fu&lt;/code&gt; will know to read from them. Or you can set a custom path if you like (check the &lt;a href="https://github.com/mbleigh/seed-fu#where-to-put-seed-files" rel="noopener noreferrer"&gt;docs&lt;/a&gt;). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#{Rails.root}/db/fixtures&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#{Rails.root}/db/fixtures/#{Rails.env}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Create your seed data files
&lt;/h2&gt;

&lt;p&gt;Now for the actual data - this is what our seeds look like if rewritten &lt;code&gt;seed-fu&lt;/code&gt;-style.&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;# db/fixtures/product_types.rb&lt;/span&gt;
&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'fruit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Juicy'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'vegetable'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Get your vitamins'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'snack'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s1"&gt;'Cheat day!'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/fixtures/product_types.rb&lt;/span&gt;
&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'fruit'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'fruit'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'ooorange'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'vegetable'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'tomato'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;product_type: &lt;/span&gt;&lt;span class="no"&gt;ProductType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'snack'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'chocolate'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me briefly explain what's happening.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;product_types&lt;/code&gt;, we don't want multiple records with the same name being created, so we use &lt;code&gt;:name&lt;/code&gt; as the key (or "constraint"). If a &lt;code&gt;product_type&lt;/code&gt; with that &lt;code&gt;name&lt;/code&gt; already exists, the &lt;code&gt;description&lt;/code&gt; would be updated, instead of a whole new &lt;code&gt;product_type&lt;/code&gt; being created.&lt;/p&gt;

&lt;p&gt;As for &lt;code&gt;products&lt;/code&gt;, we designated the &lt;code&gt;id&lt;/code&gt; for each one. We use this &lt;code&gt;:id&lt;/code&gt; as the constraint, and create/update accordingly. So if we corrected the spelling from &lt;code&gt;ooorange&lt;/code&gt; to &lt;code&gt;orange&lt;/code&gt; and reloaded the seeds, the &lt;code&gt;name&lt;/code&gt; for the &lt;code&gt;product&lt;/code&gt; whose &lt;code&gt;id&lt;/code&gt; is &lt;code&gt;2&lt;/code&gt; would be updated. Like this, we can avoid the pitfalls of leaving incorrect records or accidentally creating ones.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/mbleigh/seed-fu#constraints" rel="noopener noreferrer"&gt;docs&lt;/a&gt; have more on using these constraints. I personally think it's much more reader-friendly than the original &lt;code&gt;seeds&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Load your &lt;code&gt;seed-fu&lt;/code&gt; data automatically
&lt;/h2&gt;

&lt;p&gt;If you want to load your &lt;code&gt;seed-fu&lt;/code&gt; data manually from the command line, you can do so with &lt;code&gt;rails db:seed_fu&lt;/code&gt;. But it would be more convenient if the data could be automatically loaded in situations where you'd normally expect your seeds to be loaded (like &lt;code&gt;rails db:reset&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This couldn't be simpler. Just add this line to your &lt;code&gt;seeds.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/seeds.rb&lt;/span&gt;
&lt;span class="no"&gt;SeedFu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pass it two parameters (&lt;a href="https://github.com/mbleigh/seed-fu#rake-task" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixture path: in case you're not using the default path&lt;/li&gt;
&lt;li&gt;filter: in case you only want to load certain files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope this has been helpful. Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>seed</category>
    </item>
  </channel>
</rss>
