<?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: Jacqueline Lam</title>
    <description>The latest articles on Forem by Jacqueline Lam (@jacquelinelam).</description>
    <link>https://forem.com/jacquelinelam</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%2F263134%2Fbc36e954-2499-4bde-894d-5659d1028e66.JPG</url>
      <title>Forem: Jacqueline Lam</title>
      <link>https://forem.com/jacquelinelam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jacquelinelam"/>
    <language>en</language>
    <item>
      <title>My Flatiron School Experience + 5 lessons I Learned 👩‍💻💡</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Wed, 29 Jul 2020 14:32:52 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/my-flatiron-school-experience-5-lessons-i-learned-2d07</link>
      <guid>https://forem.com/jacquelinelam/my-flatiron-school-experience-5-lessons-i-learned-2d07</guid>
      <description>&lt;p&gt;I completed my Software Engineering Bootcamp last week and I am thrilled to share my wonderful experience with our &lt;em&gt;dev.to&lt;/em&gt; community, as well as the five valuable lessons that I have learned that might also be useful to fellow bootcamp students.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It All Started 🌱
&lt;/h3&gt;

&lt;p&gt;I graduated from New York University in 2018 with a BS degree in Media and Communications and a minor in Web Programming and Applications. I started my career in the digital marketing field since I would like to explore a wide range of responsibilities within the field, such as social media marketing and web design. &lt;/p&gt;

&lt;p&gt;While working as a Marketing Coordinator in a hospitality firm, I sought new software for building more effective marketing strategies, create beautiful websites,  measure campaign results accurately, and streamline different operations. &lt;/p&gt;

&lt;p&gt;With that in mind, in the Summer of 2019, I attended a Flatiron School event about Emerging Tech in the restaurant industry and was really amazed to see how programming intertwines with our daily lives. I had a glimpse of who was driving these restaurant tech innovations and I was intrigued by how such software worked.  &lt;/p&gt;

&lt;p&gt;During the bootcamp prep, I really enjoyed learning Ruby and understanding more about how applications are built. Therefore, I decided to enroll in the part-time software engineering program in Fall 2019.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Learning Experience
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Curriculum
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgcslxlxzd5enupwengdx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgcslxlxzd5enupwengdx.png" alt="Flatiron School Software Engineering Cirriculum"&gt;&lt;/a&gt;&lt;br&gt;
The curriculum was divided into five major modules, which was designed for students to first learn the concepts of a programming language, followed by the application of a popular framework. At the end of each module, there was a two-week project, where I developed a web application using the newly acquired knowledge.&lt;/p&gt;

&lt;p&gt;The modules and the projects that I worked on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  1. Command Line Interface + Procedural Ruby
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concepts:&lt;/strong&gt; &lt;em&gt;Procedural Ruby (CLI, data types and structures, Object-Oriented Ruby, Metaprogramming, Scraping, Separation of Concerns, etc.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; I built a &lt;a href="https://github.com/jacqueline-lam/rain_jackets" rel="noopener noreferrer"&gt;Command Line Interface&lt;/a&gt; application that uses &lt;a href="https://nokogiri.org/" rel="noopener noreferrer"&gt;Nokogiri&lt;/a&gt; to scrape data from the &lt;a href="https://www.outdoorgearlab.com/topics/clothing-womens/best-rain-jacket-womens" rel="noopener noreferrer"&gt;Outdoor Gear Lab’s webpage&lt;/a&gt; for Best Rain Jackets. It provides different product information to the user according to the user’s inquiries, such as pros/cons and feature ratings.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  2. SQL + Sinatra
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concepts:&lt;/strong&gt; &lt;em&gt;HTML, CSS, Rack, Object Relational Mapping (ORMs) and ActiveRecord, CRUD, Model-View-Controller (MVC), Content Management System, and SQL&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/jacqueline-lam/bolderer_sinatra_app" rel="noopener noreferrer"&gt;Bolderer v1&lt;/a&gt; is a web app built using the web framework Sinatra. It is designed for aspiring boulderers to log the problems that they have climbed in the gym.
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  3. Ruby on Rails
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concepts:&lt;/strong&gt; &lt;em&gt;ActiveRecord Associations, Validation and Forms, User Authentication with Omniauth, Layouts and Partials, Helper Methods, and Nested Routing&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; Based on the idea of Bolderer v1, I created &lt;a href="https://github.com/jacqueline-lam/rails-bolderer-app" rel="noopener noreferrer"&gt;Bolderer v2&lt;/a&gt; with Rails and built a more complex domain with improved user authentication and features. Users can browse problems in the bouldering gym by different filters, keep track of their sends, and check out other climbers who have crushed the same problems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  4. JavaScript and Rails
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concepts:&lt;/strong&gt; &lt;em&gt;Vanilla JavaScript, JSON, and Fetch API&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/jacqueline-lam/umami-pantry" rel="noopener noreferrer"&gt;Umami Pantry&lt;/a&gt; is my favorite project since it is the first single page application I have ever built and it is more interactive than my previous web applications, thanks to the use of asynchronous Javascript. In addition, the project is timely and unique since it allows users to find recipes for limited ingredients available in their apartment during a lockdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  5. React and Redux
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concepts:&lt;/strong&gt; &lt;em&gt;React, Redux, JSON, and Client-side Routing&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/jacqueline-lam/portfolio" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt; is composed of a React &amp;amp; Redux front-end and Rails API backend. I learned to use Redux as a state manager and Rails as a back-end JSON API. By using this set up, I can maintain my portfolio efficiently since it allows me to dynamically render updated project data and blog posts by updating my database directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;p&gt;I have learned so much in the past 10 months and I am glad that I was taught not only programming concepts and application of frameworks, but also the habit of continuous learning and mentality of problem-solving. Below are some valuable lessons that I have learned throughout the course.&lt;/p&gt;

&lt;h4&gt;
  
  
  Five Valuable Lessons I Learned 💡
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Don’t Be Afraid to Ask Both 'Dumb' and Critical Questions&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is normal to experience imposter syndrome and wonder if you are the only person who does not understand something. Share the question with your cohort mates and cohort lead and you will find that your question may also benefit others.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc3rv398ndm66zuq3t3dm.jpg" alt="Emotional Journey of Creating Anything Great"&gt;
&lt;/li&gt;
&lt;li&gt;The bootcamp is structured to teach you programming concepts and some design and architecture patterns. Whilst you’re a student and might think you should adopt whatever you have been taught, it is important to challenge the material and research for other solutions. There will always be new patterns or frameworks so you should keep an open mind. &lt;/li&gt;
&lt;li&gt;For instance, the curriculum teaches an object-oriented design approach to JavaScript, but it is NOT the only approach and JavaScript is becoming more functional. It is important to remember that the approach or pattern that you were taught was not the only way to do things and you should always explore discourses about different approaches.&lt;/li&gt;
&lt;li&gt;Technology moves quickly, so you should not get stuck on a single way of doing things because preferred patterns change rapidly. I learned to use my extra hours to research on concepts beyond existing resources provided by Flatiron School's program.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Start a Note-taking System 📝&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the lectures and completing the labs are not enough to help you retain the knowledge. I found it especially useful to use Dropbox Paper to take notes as I go through the lectures and labs. I could include code snippets as well since Dropbox paper supports Markdown syntax. Some of my cohort mates also recommend Microsoft OneNote.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Project Building: How to Plan Your Project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Map out your model relationships, routes, frameworks, and APIs.&lt;/li&gt;
&lt;li&gt;Brainstorm your User story: Who is your user? What are their pain points? How do they use our solution to mitigate or overcome this problem?&lt;/li&gt;
&lt;li&gt;Domain Modeling: I used &lt;a href="https://app.diagrams.net/" rel="noopener noreferrer"&gt;draw.io&lt;/a&gt; to map out my model relations diagram and plan out my routes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;4. Perfect is the Enemy of Good&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Whether it is a small debug, a feature added, or one query method solved, you should celebrate these small wins. Especially during project week, I had often started with ambitious goals with how I wanted certain features to work and look. However, it is important to learn to first build a Minimum Viable Product (MVP) before adding any of the extra flares. I realized that I had to breakdown my features into tiers of importance and slowly add features and styling after I created the MVP.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe3pot9ou28n4hhc0jftg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fe3pot9ou28n4hhc0jftg.png" alt="Minimum Viable Product"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Building out the project and flushing out an idea does not have to be mutually exclusive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validation of ideas comes from hitting the ground and starting to code. You will get stuck if you try to plan out all the details of the project from scratch. Meanwhile, you can test the theory of what makes an idea good or bad by testing the committed code and MVP.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Next Steps 🚀
&lt;/h3&gt;

&lt;p&gt;I will be starting my career counseling and job hunting soon, so please stay tuned for updates on technical challenges, job hunting, and interview experience, and more. &lt;/p&gt;

&lt;p&gt;Thank you for reading! Please feel free to head over to my profile to check out my blog posts for each project. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>codenewbie</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building My Personal Portfolio with React &amp; Redux (pt.2)</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Wed, 15 Jul 2020 13:08:59 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/building-my-personal-portfolio-with-react-redux-pt-2-3f7o</link>
      <guid>https://forem.com/jacquelinelam/building-my-personal-portfolio-with-react-redux-pt-2-3f7o</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/jacquelinelam/building-my-personal-portfolio-with-react-redux-pt-1-522k"&gt;part 1 of my article&lt;/a&gt;, I discussed why I decided to rebuild my personal portfolio with a Rails API and React &amp;amp; Redux front-end, and touched on the set up of my application.&lt;/p&gt;

&lt;p&gt;In part 2, we will take a look at the features that I built and how they work based on the &lt;a href="https://www.tutorialspoint.com/redux/redux_data_flow.htm"&gt;Redux Flow&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature Highlights
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Filtering Projects By Stacks
&lt;/h4&gt;

&lt;p&gt;Some research show that &lt;a href="https://www.hrdive.com/news/eye-tracking-study-shows-recruiters-look-at-resumes-for-7-seconds/541582/#:~:text=Dive%20Brief%3A,an%20average%20of%207.4%20seconds."&gt;“6 seconds is the average time recruiters spent reading a resume”&lt;/a&gt;. With that in mind, I tried to design a portfolio website with a simple UI and features that will keep users engaged, and focused on the most important visual elements.&lt;/p&gt;

&lt;p&gt;For a full-stack software engineer role, one of the most important things recruiters ask is “does the candidate have any experience using ‘xyz’ language or frameworks?” What that in mind, I designed the portfolio website with a simple filter bar so any visitor can see exactly which projects correspond to which sets of selected technologies.&lt;/p&gt;

&lt;p&gt;When the user presses a filter button, it will trigger an onClick event, calling the &lt;code&gt;addFilter&lt;/code&gt; or &lt;code&gt;removeFilter&lt;/code&gt; callback prop (line 34 and line 39), based on the current state of the button (the button state is handled in my local React state.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; 
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;FilterButton&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;  &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;    &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;  &lt;span class="nx"&gt;componentDidMount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myStackId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="mi"&gt;11&lt;/span&gt;
&lt;span class="mi"&gt;12&lt;/span&gt;    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;13&lt;/span&gt;      &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myStackId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="mi"&gt;14&lt;/span&gt;    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;15&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="mi"&gt;17&lt;/span&gt;  &lt;span class="nx"&gt;getButtonClassnames&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="mi"&gt;18&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;selected&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;
&lt;span class="mi"&gt;19&lt;/span&gt;
&lt;span class="mi"&gt;20&lt;/span&gt;    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;renderClasses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn btn-outline-info btn-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="mi"&gt;21&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;selected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;22&lt;/span&gt;      &lt;span class="nx"&gt;renderClasses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn btn-outline-info btn-sm active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;24&lt;/span&gt;
&lt;span class="mi"&gt;25&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;renderClasses&lt;/span&gt;
&lt;span class="mi"&gt;26&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;27&lt;/span&gt;
&lt;span class="mi"&gt;28&lt;/span&gt;  &lt;span class="nx"&gt;handleOnClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;29&lt;/span&gt;    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;
&lt;span class="mi"&gt;30&lt;/span&gt;    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button was active: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;31&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stackClicked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="mi"&gt;32&lt;/span&gt;
&lt;span class="mi"&gt;33&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pressed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;34&lt;/span&gt;      &lt;span class="k"&gt;this&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;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stackClicked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;35&lt;/span&gt;      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;36&lt;/span&gt;        &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="mi"&gt;37&lt;/span&gt;      &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;38&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="mi"&gt;39&lt;/span&gt;      &lt;span class="k"&gt;this&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;removeFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stackClicked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;40&lt;/span&gt;      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="mi"&gt;41&lt;/span&gt;        &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="mi"&gt;42&lt;/span&gt;      &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="mi"&gt;43&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;44&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;45&lt;/span&gt;
&lt;span class="mi"&gt;46&lt;/span&gt;  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;47&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="mi"&gt;48&lt;/span&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderClasses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getButtonClassnames&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mi"&gt;49&lt;/span&gt;
&lt;span class="mi"&gt;50&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="mi"&gt;51&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
&lt;span class="mi"&gt;52&lt;/span&gt;        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;53&lt;/span&gt;        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="mi"&gt;54&lt;/span&gt;        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;renderClasses&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;55&lt;/span&gt;        &lt;span class="nx"&gt;aria&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;pressed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;56&lt;/span&gt;        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;57&lt;/span&gt;        &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleOnClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="mi"&gt;58&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;59&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;61&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;62&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="mi"&gt;63&lt;/span&gt;
&lt;span class="mi"&gt;64&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;FilterButton&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the  &lt;code&gt;addFilter&lt;/code&gt; or &lt;code&gt;removeFilter&lt;/code&gt; function in the &lt;code&gt;ProjectsContainer&lt;/code&gt; is invoked, it will execute the action creator below, which will return an action object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio-frontend/src/actions/filterProjects.js&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;addFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stackId&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_FILTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;stackId&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;removeFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stackId&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REMOVE_FILTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;stackId&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 returned action object will then be dispatched to &lt;code&gt;projectsReducer&lt;/code&gt;, which will modify copies of the &lt;code&gt;selectedStackIds&lt;/code&gt; and &lt;code&gt;filteredProjects&lt;/code&gt; state in the Redux store. The reducer will then return the new version of our global state based on the sent action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// portfolio-frontend/src/reducers/projectsReducer.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectsReducer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;allProjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;loading&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="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;action&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stackIds&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filteredProjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_FILTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;filteredProjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proj&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="nx"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="nx"&gt;stackIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stackId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;// Set store unique stackIds&lt;/span&gt;
      &lt;span class="nx"&gt;stackIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stackIds&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;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stackIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REMOVE_FILTER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;stackIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedStackIds&lt;/span&gt;
      &lt;span class="nx"&gt;stackIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stackIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stackId&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="nx"&gt;filteredProjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allProjects&lt;/span&gt;
      &lt;span class="c1"&gt;// only include projects that have all the selected stacks&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;stackIds&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;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;filteredProjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allProjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectStacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;toString&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;includesSelectedStacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stackIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedStack&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;projectStacks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedStack&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;includesSelectedStacks&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stackIds&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;The project components subscribed to Redux store will re-render when state changes, displaying not only the toggled button update but also the filtered project results. This all happens on the client-side without ever needing to communicate with the Rails server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3mc_P0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pyoprxy9pv5a2ny8oh7k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3mc_P0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pyoprxy9pv5a2ny8oh7k.gif" alt="Filter Projects Demo" width="880" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Adding Comments to a Project
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;addComment&lt;/code&gt; action works similarly to the &lt;code&gt;addFilter&lt;/code&gt; action. However, instead of just updating the local state, store, and re-rendering the component, it also sends an asynchronous POST request to the Rails API using Javascript’s Fetch API. This is necessary for persisting the new comment record into our Postgres database.&lt;/p&gt;

&lt;p&gt;Upon submission of the form, the &lt;code&gt;addComment()&lt;/code&gt; function will dispatch the following action to the store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// portfolio-frontend/src/actions/addComment.js&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;addComment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;comment&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="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`http://localhost:3000/api/v1/projects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/comments`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// data content sent to backend will be json&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// what content types will be accepted on the return of data&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// tell server to expect data as a JSON string&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="c1"&gt;//immediately render the new data&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newComment&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_COMMENT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newComment&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;Here, I am using a middleware &lt;strong&gt;&lt;a href="https://daveceddia.com/what-is-a-thunk/#:~:text=thunk%2C%20n.,function%20that's%20returned%20by%20another."&gt;Redux Thunk&lt;/a&gt;&lt;/strong&gt;. It allows the action creator to take the dispatch function as an argument, giving us access to dispatch function. Next, we send the action returned by &lt;code&gt;addComment&lt;/code&gt; action creator to the &lt;code&gt;projectsReducer&lt;/code&gt; immediately after the asynchronous fetch request is resolved.&lt;/p&gt;

&lt;p&gt;Lastly, &lt;code&gt;projectsReducer&lt;/code&gt; will update our store with the remote data that has just been persisted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="c1"&gt;//portfolio-frontend/src/reducers/projectsReducer.js&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ADD_COMMENT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;filteredProjects&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comment&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;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 new &lt;code&gt;comment&lt;/code&gt; component will be rendered in the browser:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sUGfHrlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6pdoqf6a6typ90ykpotv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sUGfHrlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6pdoqf6a6typ90ykpotv.gif" alt="New Comment" width="880" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;With this portfolio website, I hope it adds additional color beyond the paper resume. It tells a story of a full stack web developer who can hit the ground running and contribute not only robust code, but also keen design principles. &lt;/p&gt;

&lt;p&gt;In addition to what exists now, I also plan on adding a contact page (with a contact form and social media links), a "featured project" button on the homepage to bring the user directly to my latest project showcase, and possibly a dark mode toggle.&lt;/p&gt;

&lt;p&gt;I would love to hear your suggestions for any other features that you think might be a great addition to my portfolio. Thank you for reading and stay tuned for the deployed website.&lt;/p&gt;

</description>
      <category>react</category>
      <category>redux</category>
      <category>rails</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building My Personal Portfolio with React &amp; Redux (pt.1)</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Wed, 15 Jul 2020 12:36:47 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/building-my-personal-portfolio-with-react-redux-pt-1-522k</link>
      <guid>https://forem.com/jacquelinelam/building-my-personal-portfolio-with-react-redux-pt-1-522k</guid>
      <description>&lt;p&gt;As my final project for Flatiron School, I chose to create my own &lt;strong&gt;personal portfolio&lt;/strong&gt; to kickstart my new career.&lt;/p&gt;

&lt;p&gt;After months of hard work, I have created applications dear to my heart, which are climbing and cooking related, such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jacquelinelam/ruby-on-rails-app-domain-with-many-to-many-relationships-3f5i"&gt;Bolderer&lt;/a&gt; — a digital logbook that allows indoor climbers to keep track of their climbs; and&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/jacquelinelam/building-a-single-page-cooking-app-for-covid-19-lockdown-28ke"&gt;Umami Pantry&lt;/a&gt; — a beautiful recipe application that allows users to find recipes made with selected pantry items&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My initial thought was to add these new projects to my exisiting&lt;a href="https://jacquelinelam.neocities.org"&gt; personal website&lt;/a&gt;, which was built with HTML, CSS, and bootstrap before the bootcamp.&lt;/p&gt;

&lt;p&gt;Nevertheless, in order to best showcase my hard-learned skills, I decided to produce a new personal portfolio website, leveraging cutting edge web development technologies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WhH60T0L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o5pxarz1rukc6s3lpexv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WhH60T0L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/o5pxarz1rukc6s3lpexv.png" alt="Personal Portfolio Homepage" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application is composed of a Rails-powered API and a React &amp;amp; Redux front-end. &lt;/p&gt;

&lt;h3&gt;
  
  
  Rails API Setup
&lt;/h3&gt;

&lt;p&gt;Once I set up the routes, domain model, ActiveRecord associations, database, and Cross-origin resource sharing (CORs) settings, I seeded all my project data: attributes including &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;image_url&lt;/code&gt;, &lt;code&gt;github_url&lt;/code&gt;, &lt;code&gt;blog_url&lt;/code&gt;, &lt;code&gt;demo_vid&lt;/code&gt;, &lt;code&gt;reason&lt;/code&gt; and &lt;code&gt;features&lt;/code&gt;, and the attributes of its &lt;code&gt;comments&lt;/code&gt; and &lt;code&gt;stacks&lt;/code&gt; objects.&lt;/p&gt;

&lt;p&gt;All the project data were then rendered to JSON format in the controller actions, which would be sent to the front-end upon requests.&lt;/p&gt;

&lt;p&gt;This is great for maintenance purposes because whenever I create a new project, I can just update my database and the new project data will be dynamically rendered to the projects page on the front-end. &lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of using React &amp;amp; Redux
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MTL20SdR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j55njmhmhaito8oba822.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MTL20SdR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/j55njmhmhaito8oba822.jpeg" alt="React-Redux" width="730" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;By configuring the client-side with &lt;code&gt;create-react-app&lt;/code&gt;, I was able to jump straight into building components &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redux&lt;/strong&gt;: a centralized way to manage the application data&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application's global state is stored in the store &lt;/li&gt;
&lt;li&gt;By connecting the container components to the store, I could get data from the redux store and map them to a component’s props via the method &lt;code&gt;mapStateToProps&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Similarly, I could pass dispatch functions down as callback props to the presentational components via the &lt;code&gt;mapDispatchToProps&lt;/code&gt; method, giving the children components the ability to send actions to the reducer, in order to update the global state
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ProjectsContainer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;// passing in the state from the Redux store&lt;/span&gt;
&lt;span class="c1"&gt;// so we can access values in our stores as props&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mapStateToProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&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="na"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedStackIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filteredProjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// gives us ability to dispatch new actions to our store directly from this component&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mapDispatchToProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dispatch&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="na"&gt;fetchStacks&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="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchStacks&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="na"&gt;fetchProjects&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="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchProjects&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="na"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stackId&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stackId&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;removeFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stackId&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stackId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// connect redux store to this component&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;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mapStateToProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mapDispatchToProps&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;ProjectsContainer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. Separating Concerns &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Container components&lt;/strong&gt; are primarily concerned with managing state and actions that change the global state of an app. I created three containers — &lt;code&gt;ProjectsContainer&lt;/code&gt;, &lt;code&gt;CommentsContainer&lt;/code&gt;, and &lt;code&gt;BlogPostContainers&lt;/code&gt;, with each of them connected to the redux store. With the use of these container components, I can compartmentalize the business logic for managing each major web page. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presentational components&lt;/strong&gt; are mostly stateless and are responsible for composing UI elements by providing DOM markup and styling.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1r4wtaar--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w3iuwkt02x834gdsy2on.png" alt="Presentational components" width="194" height="310"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4. Improved User Experience&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;React allows us to create a &lt;strong&gt;Single-Page Application&lt;/strong&gt; — a "web application that interacts with the user by dynamically rewriting the current web page, instead of the default method of the browser loading entire new pages [from the server]." (&lt;a href="https://en.wikipedia.org/wiki/Single-page_application"&gt;Wikipedia&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For example, when the user writes a new comment, a new &lt;code&gt;Comment&lt;/code&gt; component will be rendered dynamically without having to refresh the whole webpage. This leads to a more fluid and cohesive overall user experience:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L9fzCYD8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c2mc41tedc96u1n10xx2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L9fzCYD8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c2mc41tedc96u1n10xx2.gif" alt="Comment Form Demo" width="880" height="559"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://dev.to/jacquelinelam/building-my-personal-portfolio-with-react-redux-pt-2-3f7o"&gt;part 2 of my article&lt;/a&gt;, I will discuss the highlighted features of my portfolio, built with the intention of creating user engagement. &lt;/p&gt;

&lt;p&gt;Thank you for reading! Please feel free to check out my project on &lt;a href="https://github.com/jacqueline-lam/portfolio"&gt;GitHub&lt;/a&gt; or leave a comment below. I will gladly answer any questions you may have.&lt;/p&gt;

</description>
      <category>react</category>
      <category>redux</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Single Page App for COVID-19 Lockdown👩‍🍳</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Thu, 14 May 2020 07:40:33 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/building-a-single-page-cooking-app-for-covid-19-lockdown-28ke</link>
      <guid>https://forem.com/jacquelinelam/building-a-single-page-cooking-app-for-covid-19-lockdown-28ke</guid>
      <description>&lt;h2&gt;
  
  
  Umami Pantry
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Single Page App with Javascript/ Rails API
&lt;/h3&gt;

&lt;p&gt;Since we are all in lockdown and the grocery stores are packed with people these days, I have created a single page application called &lt;strong&gt;&lt;a href="https://github.com/jacqueline-lam/umami-pantry" rel="noopener noreferrer"&gt;Umami Pantry&lt;/a&gt;&lt;/strong&gt; to help users find matching recipes for available ingredients in their kitchen. It is designed to encourage freestyle cooking with easy-to-substitute ingredients.&lt;/p&gt;

&lt;p&gt;The app is composed of backend Rails API and front-end modular JS clients, which use asynchronous Javascript to make HTTP requests to the API to get/ post data and render them to the user interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-Server Communication
&lt;/h3&gt;

&lt;p&gt;All the interactions between the client and the server are handled asynchronously with the &lt;code&gt;fetch()&lt;/code&gt; method provided by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;. &lt;/p&gt;

&lt;h4&gt;
  
  
  Get Matching Recipes Data with Fetch
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Adapter class&lt;/span&gt;
  &lt;span class="nf"&gt;getMatchingRecipes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;matchingRecipes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
    &lt;span class="c1"&gt;// 2, 3, 4&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`http:localhost3000/get_recipes/?selected_ingredients=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedIngredients&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipesData&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="nx"&gt;recipesData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipe&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// 5&lt;/span&gt;
          &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;matchingRecipes&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="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;renderMatchingRecipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matchingRecipes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 7&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;To fetch all the matching recipes: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an empty array to hold the unique &lt;code&gt;matchingRecipes&lt;/code&gt; objects&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;fetch()&lt;/code&gt; and pass in a URL string to the desired data source as an argument. I'm passing in an array of &lt;code&gt;ingredientIds&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch()&lt;/code&gt; returns an object representing the data source sent back (not the actual JSON). We then call &lt;code&gt;.then()&lt;/code&gt; on this object, which accepts the callback function, receiving the response as its argument and call the &lt;code&gt;.json()&lt;/code&gt; method to return the content from the response.
&lt;/li&gt;
&lt;li&gt;In the second &lt;code&gt;.then()&lt;/code&gt; we receive a JSON string which holds the &lt;code&gt;matchingRecipesData&lt;/code&gt;, which we then iterate over the collection to access each recipe object.&lt;/li&gt;
&lt;li&gt;Search for the recipe in the Recipe class, if the recipe object does not exist, instantiate a new Recipe object. Push the recipe object into the &lt;code&gt;matchingRecipes&lt;/code&gt; array.&lt;/li&gt;
&lt;li&gt;If the fetch request is successful, the adapter method &lt;code&gt;renderMatchingRecipes(matchingRecipes)&lt;/code&gt; will render all the matching recipes into the DOM.&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;.catch()&lt;/code&gt; after the two &lt;code&gt;.then()&lt;/code&gt; calls, appending an error message to the console if &lt;code&gt;.catch()&lt;/code&gt; is called.&lt;/li&gt;
&lt;/ol&gt;


&lt;h4&gt;
  
  
  Render JSON from a Rails Controller
&lt;/h4&gt;

&lt;p&gt;Between step 2 and 3, we use the &lt;code&gt;/get_recipes&lt;/code&gt; endpoint to access the matching pieces of recipe data. We get the matching instances in the Recipe model and render them into JSON in the recipes controller:&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;# Step 2.5&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RecipesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_recipes&lt;/span&gt;
    &lt;span class="n"&gt;selected_ingredients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:selected_ingredients&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;recipes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_by_ingredients&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selected_ingredients&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="no"&gt;RecipeSerializer&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="n"&gt;recipes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;instances_to_serialized_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We first extract the the string of &lt;code&gt;ingredientIds&lt;/code&gt; from the params and convert them into a string of intergers. We then filter out the Recipe instances that include the specific set of ingredients.&lt;/p&gt;

&lt;p&gt;We call &lt;code&gt;render json:&lt;/code&gt; followed by the customized data that would be converted to JSON. The customized data is handled by the &lt;code&gt;RecipeSerializer&lt;/code&gt; service class, which handles the logic of extracting and arranging the JSON data that we want to send back to the client. &lt;/p&gt;


&lt;h4&gt;
  
  
  Results
&lt;/h4&gt;

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


&lt;h3&gt;
  
  
  Iterations in JavaScript
&lt;/h3&gt;

&lt;p&gt;There are a lot of ways to traverse a collection in Javascript. However, it can get quite confusing especially when you want to iterate through Array-like DOM objects. There are &lt;code&gt;.map&lt;/code&gt;, &lt;code&gt;for..in&lt;/code&gt;, &lt;code&gt;for...of&lt;/code&gt; and &lt;code&gt;.forEach&lt;/code&gt; but they are all slightly different.&lt;/p&gt;

&lt;p&gt;For example, using a &lt;code&gt;.forEach&lt;/code&gt; method on an &lt;code&gt;HTMLcollection&lt;/code&gt; would cause a TypeError:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5pobvfxbvqxrn47i5m40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F5pobvfxbvqxrn47i5m40.png" alt="TypeError in attempt to iterate through HTMLColelction"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to note that there are two ways to select multiple DOM nodes: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;document.getElementsByClassName()&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;returns an &lt;code&gt;HTMLCollection&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;contains same DOM elements&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;document.querySelectorAll()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;returns a &lt;code&gt;nodeList&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;can contain different DOM elements.&lt;/li&gt;
&lt;li&gt;can use &lt;code&gt;forEach&lt;/code&gt; for iteration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To iterate over the &lt;code&gt;HTMLcollection&lt;/code&gt;, we can use &lt;code&gt;Array.from()&lt;/code&gt; to convert the HTML collection into an array and then traverse the collection like an array with the &lt;code&gt;.forEach&lt;/code&gt; method:&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;ingredientCards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ingredientCard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ingredientCards&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;style&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="s2"&gt;background-color: white;&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;h4&gt;
  
  
  Resources
&lt;/h4&gt;

&lt;p&gt;Here are a few additional articles that are very helpful: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@larry.sassainsworth/iterating-over-an-html-collection-in-javascript-5071f58fad6b" rel="noopener noreferrer"&gt;Iterating over an HTML Collection in JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a&gt;Traversing the DOM with filter(), map(), and arrow functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This is my second full-stack project (after my &lt;a href="https://github.com/jacqueline-lam/rails-bolderer-app" rel="noopener noreferrer"&gt;Rails Bolderer CMS App&lt;/a&gt;), and I am glad that I am able to focus more on the front-end for this project. Learning JavaScript is a breath of fresh air, and I am looking forward to learning more efficient ways to manipulate the DOM, make better use of &lt;code&gt;eventListeners&lt;/code&gt; to create more interactive and responsive sites, and to communicate with the server asynchronously.&lt;/p&gt;

&lt;p&gt;Please feel free to check out my project and leave any feedback below:&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://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jacqueline-lam" rel="noopener noreferrer"&gt;
        jacqueline-lam
      &lt;/a&gt; / &lt;a href="https://github.com/jacqueline-lam/umami-pantry" rel="noopener noreferrer"&gt;
        umami-pantry
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A single page app built to help homecooks find matching recipes for limited pantry ingredients.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>rails</category>
      <category>ruby</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ruby on Rails App: Domain With Many-to-many Relationships</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Sat, 14 Mar 2020 18:56:47 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/ruby-on-rails-app-domain-with-many-to-many-relationships-3f5i</link>
      <guid>https://forem.com/jacquelinelam/ruby-on-rails-app-domain-with-many-to-many-relationships-3f5i</guid>
      <description>&lt;h4&gt;
  
  
  Creating a domain with Many-to-many relationships: — using Active Record Associations, Rails Nested Resources and Helper Methods
&lt;/h4&gt;

&lt;p&gt;In my last Sinatra portfolio project, I created a climbing web application, &lt;strong&gt;&lt;a href="https://github.com/jacqueline-lam/bolderer_sinatra_app"&gt;Bolderer&lt;/a&gt;&lt;/strong&gt;, which allows &lt;code&gt;User&lt;/code&gt; to log bouldering &lt;code&gt;Problems&lt;/code&gt; and track their climbing progress. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My article on building a simple domain with Sinatra:&lt;/strong&gt; &lt;a href="https://dev.to/jacquelinelam/sinatra-web-app-mvc-sessions-and-routes-52on"&gt;Sinatra Web App: MVC, Sessions, and Routes&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Association Diagram
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hp8vwzXc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v52io2rt7is5yqrntfb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hp8vwzXc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v52io2rt7is5yqrntfb4.png" alt="Sinatra App" width="711" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal for my Ruby on Rails project is to adopt the &lt;a href="https://airbrake.io/blog/software-design/domain-driven-design"&gt;Domain-driven Design (DDD)&lt;/a&gt; and continue to modify my domain: stretching beyond just &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Problem&lt;/code&gt;, and &lt;code&gt;Style&lt;/code&gt; models. I would also like to improve my domain logic with more complex database queries and data relationships.&lt;/p&gt;

&lt;p&gt;I want a &lt;code&gt;user&lt;/code&gt; to not only be able to track their individual climbing progress (by logging problems that they have climbed), but also share and compare their 'sends' with others. &lt;/p&gt;

&lt;p&gt;With this in mind, I have created a new &lt;strong&gt;Bolderer Association Diagram&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KDkSqcmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rqzxvxrqeid4le1a0u4u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KDkSqcmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/rqzxvxrqeid4le1a0u4u.png" alt="Rails App" width="861" height="642"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Note: comments and rewards will be implemented in the near future&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;To allow &lt;code&gt;users&lt;/code&gt; to share the same &lt;code&gt;problems&lt;/code&gt; that they have climbed, I have created a Many-to-many connection between the two models.&lt;/p&gt;


&lt;h2&gt;
  
  
  How to declare a Many-to-many association between Active Record models
&lt;/h2&gt;

&lt;p&gt;A many-to-many connection is set up between the &lt;code&gt;User&lt;/code&gt; model and &lt;code&gt;Problem&lt;/code&gt; models, by implementing a &lt;strong&gt;&lt;a href="https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association"&gt;&lt;code&gt;has_many :through&lt;/code&gt; association&lt;/a&gt;&lt;/strong&gt;. The join table &lt;code&gt;Send&lt;/code&gt; belongs to both the &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Problem&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;class User &amp;lt; ApplicationRecord
  has_many :sends
  has_many :problems, through: :sends
end

class Send &amp;lt; ApplicationRecord
  belongs_to :user
  belongs_to :problem
end

class Problem &amp;lt; ApplicationRecord
  belongs_to :wall
  has_many :sends
  has_many :users, through: :sends
  has_many :problem_styles
  has_many :styles, through: :problem_styles
end

class Wall &amp;lt; ApplicationRecord
  has_many :problems
end

class Style &amp;lt; ApplicationRecord
  has_many :problem_styles
  has_many :problems, through: :problem_styles
end

class ProblemStyle &amp;lt; ApplicationRecord
  belongs_to :problem
  belongs_to :style
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the help of Active Record &lt;code&gt;has_many :through&lt;/code&gt; association, I can now use methods provided by Rails, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@user.sends&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@user.problems&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;@send.user&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@send.problem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@problem.users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@problem.sends&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following the set up of my model associations, I encounter the next problem: with so many models present, how can I maintain a separation of concerns and organize my routes in a relatively DRY manner? &lt;/p&gt;




&lt;h2&gt;
  
  
  How to Use &lt;a href="https://guides.rubyonrails.org/routing.html#nested-resource"&gt;Nested Resources&lt;/a&gt; with appropriate RESTful URLs
&lt;/h2&gt;

&lt;h4&gt;
  
  
  ⚠️ Routes Previously Used in my Sinatra Application
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;HTTP Verb&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;CRUD Action&lt;/th&gt;
&lt;th&gt;Used for/ result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;index page to welcome user - login/ signup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/problems&lt;/td&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;displays all problem (all problems are rendered)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/problems&lt;/td&gt;
&lt;td&gt;create&lt;/td&gt;
&lt;td&gt;creates a problem; save to db&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/problems/:id&lt;/td&gt;
&lt;td&gt;show&lt;/td&gt;
&lt;td&gt;displays one problem based on ID in the url&lt;br&gt;(just one problem is rendered)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/problems/:id/edit&lt;/td&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;displays edit form based on ID in the URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;/problems/:id&lt;/td&gt;
&lt;td&gt;update&lt;/td&gt;
&lt;td&gt;modifies an existing problem based on ID in the url&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/problems/:id&lt;/td&gt;
&lt;td&gt;delete&lt;/td&gt;
&lt;td&gt;deletes one article based on ID in the URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/users/:username&lt;/td&gt;
&lt;td&gt;show&lt;/td&gt;
&lt;td&gt;display one user’s problems based on :username in the url&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: excluding login/ create account routes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you can see, all of the &lt;code&gt;problems&lt;/code&gt; belonging to a &lt;code&gt;user&lt;/code&gt; were just displayed under their show page in my Sinatra Bolderer application. A &lt;code&gt;user&lt;/code&gt; may have sent a &lt;code&gt;problem&lt;/code&gt; without knowing that their friend has also sent it. Meanwhile, the &lt;code&gt;problem&lt;/code&gt; index view was displaying multiple duplicated &lt;code&gt;problems&lt;/code&gt; logged by different &lt;code&gt;users.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In our newly drawn association diagram, a &lt;code&gt;send&lt;/code&gt; can logically be considered a &lt;em&gt;child&lt;/em&gt; object to a &lt;code&gt;user&lt;/code&gt;, so it can also be considered a &lt;strong&gt;nested resource&lt;/strong&gt; of a &lt;code&gt;user&lt;/code&gt; for routing purposes. I am now able to document this parent/child relationship in my routes and URLs. &lt;/p&gt;

&lt;h4&gt;
  
  
  💡 Nested Resource Routes Used in my Rails Application
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources :users, only: [:index, :new, :create, :show] do 
  resources :sends
  get 'sends/sort/easiest', to: 'sends#easiest'
  get '/sends/sort/hardest', to: 'sends#hardest'
end 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LEtXkq1c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3uvmdr7ezvujsg3i4k8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LEtXkq1c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3uvmdr7ezvujsg3i4k8s.png" alt="Routes" width="880" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to the routes for &lt;code&gt;Sends&lt;/code&gt;, this declaration will also route &lt;code&gt;Sends&lt;/code&gt; to a &lt;code&gt;SendsController&lt;/code&gt;, where I will be defining all the actions used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display all sends (sends#index)&lt;/li&gt;
&lt;li&gt;Display a form for creating a new send belonging to a user (sends#new)&lt;/li&gt;
&lt;li&gt;Create a new send belonging to a user (sends#create)&lt;/li&gt;
&lt;li&gt;Display a specific send by a user (sends#show)&lt;/li&gt;
&lt;li&gt;Display a form for editing a send belonging to a user (sends#edit)&lt;/li&gt;
&lt;li&gt;Update a specific send by a user (sends#update)&lt;/li&gt;
&lt;li&gt;Delete a specific send belonging to a specific user (sends#delete)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Nested Route URL Helpers
&lt;/h2&gt;

&lt;p&gt;Rails has also magically generated a set of nested route URL Helpers for my nested resource routes. &lt;/p&gt;

&lt;p&gt;Some examples of the routing helpers created include &lt;code&gt;user_sends_path&lt;/code&gt; and &lt;code&gt;edit_user_sends_path&lt;/code&gt;. These helpers take an instance of &lt;code&gt;User&lt;/code&gt; as the first parameter (user_sends_url(@user)).&lt;/p&gt;

&lt;h3&gt;
  
  
  Combining Nested Route URL helpers with &lt;code&gt;link_to&lt;/code&gt; helper
&lt;/h3&gt;

&lt;p&gt;I can now easily use Rails &lt;code&gt;link_to&lt;/code&gt; helper method and the named helpers for our nested routes to create a link to a specific &lt;code&gt;User&lt;/code&gt;'s &lt;code&gt;Send&lt;/code&gt; show page:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🔗 Creating a link in plain HTML:&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;a href="/users/#{@send.user.id}/sends/#{@send.id}"&amp;gt;See this send&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🔗 V.S. Using Rails helper method &lt;code&gt;link_to&lt;/code&gt; + named helpers for nested routes:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;%= link_to 'See this send',  user_send_path(@send.user, @send) %&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: Rails is able to extract the &lt;code&gt;id&lt;/code&gt;s of the &lt;code&gt;@send.user&lt;/code&gt; and &lt;code&gt;@send&lt;/code&gt; passed into the &lt;code&gt;user_send_path&lt;/code&gt;, and it will redirect us to the show page of this specific &lt;code&gt;send&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔎 Class-level Active Record &lt;a href="https://guides.rubyonrails.org/active_record_querying.html#scopes"&gt;Scope methods&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Lastly, I find it immensely helpful to use Rails scope methods for improving my domain logic, as it allows me to perform complex database queries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem model&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  scope :sort_by_date, -&amp;gt; { order('created_at desc') }
  scope :sort_by_grade, -&amp;gt; { order('grade desc') } 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Send model&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  scope :sort_by_date, -&amp;gt; { order('date_sent desc') }
  scope :sort_by_grade_desc, -&amp;gt; (user) {
    where(user_id: user).joins(:problem).order('grade desc')
  } 
  scope :sort_by_grade_asc, -&amp;gt; (user) {
    where(user_id: user).joins(:problem).order('grade asc')
  } 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;User model&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  # query user table for user who climbed the hardest graded problem 
  scope :best_climber, -&amp;gt; { joins(:problems).order('grade desc').distinct.limit(1).first } 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;user&lt;/code&gt; can now easily browse &lt;code&gt;problems&lt;/code&gt; and &lt;code&gt;sends&lt;/code&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;User&lt;/code&gt;'s &lt;code&gt;sends&lt;/code&gt;&lt;/strong&gt;: sort by most recent sends, easiest to hardest sends, and hardest to easiest sends&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Problems&lt;/code&gt;&lt;/strong&gt;: sort by most recently created problems, easiest to hardest problems, and hardest to easiest problems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;User&lt;/code&gt;&lt;/strong&gt;: Display the crusher who climbed the hardest problem
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;With the help of Rails, I can easily create Active Record associations between different models while developing my domain.&lt;/li&gt;
&lt;li&gt;Implement nested resources, a powerful tool, to represent the parent/child relationships in my domain (A &lt;code&gt;Send&lt;/code&gt; belongs to a &lt;code&gt;User&lt;/code&gt;) and to keep my routes tidy.&lt;/li&gt;
&lt;li&gt;Use the 🔗nested route URL helpers to easily link to different views&lt;/li&gt;
&lt;li&gt;Use 🔎scope methods to perform complex database queries.
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>oop</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sinatra Web App: MVC, Sessions, and Routes</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Mon, 20 Jan 2020 03:53:48 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/sinatra-web-app-mvc-sessions-and-routes-52on</link>
      <guid>https://forem.com/jacquelinelam/sinatra-web-app-mvc-sessions-and-routes-52on</guid>
      <description>&lt;h1&gt;
  
  
  A Sinatra Web App for Boulderers
&lt;/h1&gt;

&lt;p&gt;My Sinatra-powered web app — &lt;strong&gt;&lt;a href="https://github.com/jacqueline-lam/bolderer_sinatra_app"&gt;Bolderer&lt;/a&gt;&lt;/strong&gt;, is designed for boulderers who are eager to track their climbing progress, strengths and weaknesses by logging routes/ problems that they have climbed.  They can also see others' profiles since bouldering is a personal yet social sport, where the climbing community is supportive and would always cheer for your 'sends'.&lt;/p&gt;

&lt;p&gt;I started my project by mapping out the models and their relationships with one another. This helped me plan the models, database tables, and the &lt;code&gt;has_many&lt;/code&gt;, &lt;code&gt;belongs_to&lt;/code&gt; and &lt;code&gt;many-to-many&lt;/code&gt; relationships on the models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bolderer Association Diagram:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hp8vwzXc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v52io2rt7is5yqrntfb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hp8vwzXc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/v52io2rt7is5yqrntfb4.png" alt="Bolderer Association Diagram" width="711" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Key Concepts
&lt;/h1&gt;

&lt;p&gt;I would like to highlight some key building blocks of my Sinatra project — &lt;strong&gt;MVC&lt;/strong&gt;, &lt;strong&gt;Sessions&lt;/strong&gt;, &lt;strong&gt;CRUD Actions&lt;/strong&gt;, and &lt;strong&gt;RESTful Routes&lt;/strong&gt;, which have allowed me to create a functional app and taught me the base concepts of a Content Management System (&lt;a href="http://www.businessdictionary.com/definition/content-management-system-CMS.html"&gt;CMS&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.codecademy.com/articles/mvc"&gt;Model-View-Controller (MVC)&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;An organized way to build frameworks for web apps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I found this immensely useful in helping me structure my code. It provided a separation of concerns by grouping my files by functionalities -- &lt;strong&gt;Models&lt;/strong&gt; (logic, where data is manipulated and saved), &lt;strong&gt;Views&lt;/strong&gt; (front-end), and &lt;strong&gt;Controllers&lt;/strong&gt; (middleman - relays data from browser to app, and from app to the browser). &lt;/p&gt;

&lt;h3&gt;
  
  
  My MVC directories:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XvmzqyU3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/v2d57wwptzdxaw8phedu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XvmzqyU3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/v2d57wwptzdxaw8phedu.png" alt="Model-View-Controller" width="234" height="557"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  User Password Encryption with BCrypt
&lt;/h2&gt;

&lt;p&gt;I used the Ruby Gem &lt;a href="https://rubygems.org/gems/bcrypt/versions/3.1.12"&gt;BCrypt&lt;/a&gt; to secure the users' data by encrypting their passwords. It allowed my app to sign up and log-in a user with a secure password.&lt;/p&gt;

&lt;p&gt;Implementing Bcrypt by adding the password column as &lt;code&gt;pasword_digest&lt;/code&gt; in my &lt;code&gt;users&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CreateUsers &amp;lt; ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :username
      t.string :password_digest
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added &lt;a href="https://guides.rubyonrails.org/active_record_basics.html"&gt;ActiveRecord&lt;/a&gt;'s macro &lt;code&gt;has_secure_password&lt;/code&gt; to my User model, which let me access the user's &lt;code&gt;password&lt;/code&gt; attribute even though the &lt;em&gt;users&lt;/em&gt; database only had the column called &lt;code&gt;password_digest&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The macro also provided another useful method, &lt;code&gt;authenticate&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;post '/login' do
    user = User.find_by(username: params[:username])

    if user &amp;amp;&amp;amp; user.authenticate(params[:password])
      #storing user_id key in session hash
      session[:user_id] = user.id
      redirect "/users/#{user.id}"
    else
      @error = true
      erb :"/users/login"
    end
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;User&lt;/em&gt;'s &lt;code&gt;authenticate&lt;/code&gt; method turned the user's password input into a hash that would get compared with the hashed password stored in the database. If the user authenticated, the method would return the &lt;em&gt;User&lt;/em&gt; instance; otherwise, it would return &lt;code&gt;false&lt;/code&gt;. This was helpful for logging in users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sessions and User Authentication and Authorization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Sessions were used to verify a user's identity, as the users were requested to verify their identity by logging in with valid credentials&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hence, I used sessions to filter and dictate what a user could see or edit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The users could only view the Problems index page if they were logged in&lt;/li&gt;
&lt;li&gt;The users could only edit/ delete the resources (problems) that they created&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To use sessions in Sinatra, I enabled my application to use the &lt;code&gt;sessions&lt;/code&gt; keyword to access the session hash:&lt;br&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 plaintext"&gt;&lt;code&gt;  configure do
    set :public_folder, 'public'
    set :views, 'app/views'
    enable :sessions
    set :session_secret, "session_encryption"
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;When a user signs in successfuly, the &lt;code&gt;user_id&lt;/code&gt; key would get stored in the session hash: &lt;code&gt;session[:user_id] = user.id&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Defining RESTful routes
&lt;/h2&gt;

&lt;p&gt;Another key lesson was learning to use restful routes to implement CRUD actions. I realized how important it was to plan my routes in order to handle the HTTP verbs and URLs in an organized and standardized way.&lt;/p&gt;

&lt;p&gt;I designed my &lt;code&gt;ProblemsController&lt;/code&gt;, &lt;code&gt;UsersController&lt;/code&gt; and &lt;code&gt;SessionsController&lt;/code&gt; controller actions to follow restful conventions and to map the HTTP verbs (&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, &lt;code&gt;patch&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;) to the controller CRUD actions. &lt;/p&gt;

&lt;p&gt;For my app, I wanted a logged-in user to be able to access all CRUD actions: the ability to create sessions (login)/ problems, read, update or delete their own problems.&lt;/p&gt;

&lt;p&gt;My Problems Controller:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;HTTP Verb&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;CRUD Action&lt;/th&gt;
&lt;th&gt;Used for/ result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;index page to welcome user - login/ signup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/problems&lt;/td&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;displays all problem (all problems are rendered)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/problems&lt;/td&gt;
&lt;td&gt;create&lt;/td&gt;
&lt;td&gt;creates a problem; save to db&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/problems/:id&lt;/td&gt;
&lt;td&gt;show&lt;/td&gt;
&lt;td&gt;displays one problem based on ID in the url&lt;br&gt;(just one problem is rendered)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/problems/:id/edit&lt;/td&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;displays edit form based on ID in the URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PATCH&lt;/td&gt;
&lt;td&gt;/problems/:id&lt;/td&gt;
&lt;td&gt;update&lt;/td&gt;
&lt;td&gt;modifies an existing problem based on ID in the url&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/problems/:id&lt;/td&gt;
&lt;td&gt;delete&lt;/td&gt;
&lt;td&gt;deletes one article based on ID in the URL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My Users Controller and Sessions Controller:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;HTTP Verb&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;CRUD Action&lt;/th&gt;
&lt;th&gt;Used for/ result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/signup&lt;/td&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;display signup form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/signup&lt;/td&gt;
&lt;td&gt;create&lt;/td&gt;
&lt;td&gt;creates a user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/login&lt;/td&gt;
&lt;td&gt;index&lt;/td&gt;
&lt;td&gt;displays login form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/login&lt;/td&gt;
&lt;td&gt;create&lt;/td&gt;
&lt;td&gt;create a session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/logout&lt;/td&gt;
&lt;td&gt;delete&lt;/td&gt;
&lt;td&gt;delete session/ log out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/users/:username&lt;/td&gt;
&lt;td&gt;show&lt;/td&gt;
&lt;td&gt;display one user’s problems based on :username in the url&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Other resources:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seeding Data&lt;/strong&gt;: The dummy data was very important as it helped to demonstrate to the first user(s) how this web app could be used. There was the option of using APIs to seed data (especially for general topics like recipes), but I decided to create my own instances of Users, Problems, and Styles and their associations to make my data more personal (as it reflected my actual bouldering logs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bootstrap&lt;/strong&gt;: This allowed me to add styling to my forms, buttons, etc. without having to build the CSS from scratch&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>sql</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building my first project: CLI Data Gem 💎</title>
      <dc:creator>Jacqueline Lam</dc:creator>
      <pubDate>Sun, 03 Nov 2019 18:35:52 +0000</pubDate>
      <link>https://forem.com/jacquelinelam/building-my-first-project-cli-data-gem-50m4</link>
      <guid>https://forem.com/jacquelinelam/building-my-first-project-cli-data-gem-50m4</guid>
      <description>&lt;h2&gt;
  
  
  A CLI App for Indecisive Shoppers
&lt;/h2&gt;

&lt;p&gt;As an indecisive yet frugal shopper, I struggle a lot in making my purchase decisions. I love to do extensive research to find the product with the best features for its cost. &lt;/p&gt;

&lt;p&gt;Hence, I decided to create a gem that will provide the user with concise information about a type of product and gives the user the option to learn about the best products based on a chosen feature! — Almost like a product rater of some sort.&lt;/p&gt;

&lt;p&gt;I visited &lt;a href="https://www.outdoorgearlab.com/topics/clothing-womens/best-rain-jacket-womens"&gt;Outdoor Gear Lab's Best Women's Rain Jackets of 2019 website&lt;/a&gt; dozens of times this year before I finally decided on the perfect jacket for me. Once I decided to scrape from this website, I started to imagine my CLI app interface according to features that a user might be interested in, and then reverse-engineer the data that I would need to scrape from the website:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Planning a CLI for Best Rain Jackets:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Shows a list of best female rain jackets #name - price - overall rating&lt;/li&gt;
&lt;li&gt;Shows details of a chosen jacket&lt;/li&gt;
&lt;li&gt;Shows the best products based on a chosen rating category&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  So, what are the properties of a rain jacket? It has a...
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Price&lt;/li&gt;
&lt;li&gt;Description &lt;/li&gt;
&lt;li&gt;Pros &amp;amp; Cons&lt;/li&gt;
&lt;li&gt;URL to full product review &lt;/li&gt;
&lt;li&gt;Overall rating &lt;/li&gt;
&lt;li&gt;Other rating categories (water resistance, breathability, comfort, weight, durability, and packed size)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once I set up the basic structure of my gem and created the executable files, I started working on my Scraper class. &lt;/p&gt;

&lt;h3&gt;
  
  
  Scraping data
&lt;/h3&gt;

&lt;p&gt;First, I used &lt;a href="https://ruby-doc.org/stdlib-2.6.3/libdoc/open-uri/rdoc/OpenURI.html"&gt;Open-URI&lt;/a&gt; to scrape the website's HTML and turned it into a string. Then, I used &lt;a href="https://nokogiri.org/tutorials/parsing_an_html_xml_document.html"&gt;&lt;code&gt;Nokogiri::HTML&lt;/code&gt;&lt;/a&gt; to parse the HTML string into a &lt;a href="https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/NodeSet"&gt;NodeSet&lt;/a&gt;. I identified the jacket properties that I wanted to assign to each jacket instance, and I began using &lt;a href="https://nokogiri.org/tutorials/searching_a_xml_html_document.html"&gt;Nokogiri's CSS method&lt;/a&gt; to retrieve selected data from the jacket table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A-bNqwsh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cbvut0t9zshh4uf9r59h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A-bNqwsh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cbvut0t9zshh4uf9r59h.png" alt="Alt Text" width="880" height="535"&gt;&lt;/a&gt;&lt;br&gt;
In the screenshot above, each &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; tag (table row) contains a collection of &lt;strong&gt;one&lt;/strong&gt; property for &lt;strong&gt;all&lt;/strong&gt; jackets, while each &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; (field) contains the property element of &lt;strong&gt;one&lt;/strong&gt; jacket instance.&lt;/p&gt;

&lt;p&gt;As I iterated through each &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; tags /table row:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Row #1: Instantiated a new jacket instance at each &lt;code&gt;&amp;lt;td&amp;gt;&lt;/code&gt; (field) &lt;/li&gt;
&lt;li&gt;If the row contained a property that I needed for the jacket object, I would scrape the values of respective attributes and assign them to each jacket object&lt;/li&gt;
&lt;li&gt;Saved all the jacket instances (with assigned attributes) in an &lt;code&gt;all_jackets&lt;/code&gt; array &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Converting Scraped Data to Objects
&lt;/h3&gt;

&lt;p&gt;At this stage, I had mapped out my domain and the decoupled my namespaced classes: &lt;code&gt;RainJackets::CLI&lt;/code&gt;, &lt;code&gt;RainJackets::Jacket&lt;/code&gt;, and &lt;code&gt;RainJackets::Scraper&lt;/code&gt;. The CLI object will interact with the Scraper class, which will be responsible for creating Jacket instances. &lt;/p&gt;

&lt;p&gt;When the user runs the gem, an instance of the CLI class would be created -&amp;gt;  The CLI controller would call the Scraper class method &lt;code&gt;initialize_jacket_objects&lt;/code&gt; -&amp;gt; where the Scraper would parse the info for all jackets -&amp;gt; and lastly, create the Jacket instances and assign different attributes to the jackets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/launch-school/the-basics-of-oop-ruby-26eaa97d2e98"&gt;Ruby Object-Oriented Programming&lt;/a&gt; was so powerful that it allowed me to encapsulate all the logic for my CLI interface into classes. It also encapsulated the abstraction of real-world objects in a class (i.e. a jacket with name, price, description, etc.)&lt;/p&gt;

&lt;p&gt;Instead of using hashes and arrays to give the jacket object information, I used the Jacket class to store a jacket's attributes and methods. The &lt;code&gt;attr_acessor&lt;/code&gt; macro allowed me to read and write to the attributes, and a class variable &lt;code&gt;@@all&lt;/code&gt; allowed me to save all the instances of my Jacket class in an array.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI Application
&lt;/h3&gt;

&lt;p&gt;With a masterplan for my app's functionality, I couldn't wait to complete the rain jackets controller. The CLI class was responsible for getting the user's input, matching them to the available commands and displaying the results derived from the scraped data. I was a bit too stoked and made the mistake of writing everything in a &lt;code&gt;#call&lt;/code&gt; method in my CLI class (Spaghetti code alert 🍝❗). This caused a lot of bugs as the code was hard to follow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ERkfKAmN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i2vgmf70dy3fgdghsupt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ERkfKAmN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/i2vgmf70dy3fgdghsupt.jpeg" alt="Alt Text" width="400" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, I took a deep breath and a step back. I broke down the &lt;code&gt;#call&lt;/code&gt; method into individual methods based on each of its functions — i.e. &lt;code&gt;#prompt_input&lt;/code&gt;, &lt;code&gt;#get_input&lt;/code&gt;, &lt;code&gt;#handle_input&lt;/code&gt;, &lt;code&gt;#read_rating_input&lt;/code&gt;, &lt;code&gt;#print_ratings&lt;/code&gt;, &lt;code&gt;#print_list_all&lt;/code&gt;, &lt;code&gt;#print_menu&lt;/code&gt;, etc. The last hurdles were to ensure that the &lt;code&gt;#handle_input&lt;/code&gt; logic would prevent errors stemming from user inputs and ensure that the &lt;code&gt;#print&lt;/code&gt; methods would successfully retrieve desired scraped data, reformat them and display meaningful results to the user. &lt;/p&gt;

&lt;p&gt;And...Wa~la! The app is now in business! &lt;/p&gt;




&lt;h3&gt;
  
  
  Recap of My Process 📝
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Chose a website with abundant and useful information &lt;/li&gt;
&lt;li&gt; Imagined my interface and planned my gem&lt;/li&gt;
&lt;li&gt; Started with my project structure — created my GitHub repo and built a gem with bundler&lt;/li&gt;
&lt;li&gt; Created my bin file executable and set up my environment&lt;/li&gt;
&lt;li&gt; Understood the website's HTML structures and patterns, then developed algorithms to scrape data efficiently &lt;/li&gt;
&lt;li&gt; Converted the scraped data to objects &lt;/li&gt;
&lt;li&gt; Built the CLI interface &lt;/li&gt;
&lt;li&gt; Refactored my code and add comments &lt;/li&gt;
&lt;li&gt; Published the gem onto &lt;a href="//www.rubygems.org"&gt;RubyGems.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  My Key Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Planning&lt;/strong&gt;: Think about how my gem would work before starting to code and break down my goals into smaller tasks in order to avoid feeling overwhelmed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing my code with bin/console &amp;amp; &lt;code&gt;binding.pry&lt;/code&gt;&lt;/strong&gt;: A great way to replicate project-environment. Once I scraped the table from the rain jackets website, I used the console to test the CSS selectors that will allow me to retrieve desired pieces of info out of the HTML document and I used &lt;code&gt;binding.pry&lt;/code&gt; for debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging"&gt;Rubber duck debugging&lt;/a&gt;&lt;/strong&gt;: Revisit my code line-by-line and explain what it does often leads me to the bug (often a silly syntax error like a missing &lt;code&gt;end&lt;/code&gt; or closing parenthesis🙄)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Program architecture and design patterns&lt;/strong&gt;: This can be very challenging since it comes with experience. Although my code runs without errors, there is always a more organized, abstract and efficient alternative to mine. I found it helpful to watch walkthroughs and video demos (especially by Flatiron School's founder, Avi Flombaum) to learn how professionals would approach problems. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DRY-ing up my code&lt;/strong&gt;: Writing long methods with multiple functions (a.k.a spaghetti code) makes debugging really hard. A great solution is to remind myself to limit each method to one (of few) functionality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring&lt;/strong&gt;: As much as I would love to write abstract code, it is okay to write long hard-code first, and then refactor my code to become more &lt;em&gt;dynamic&lt;/em&gt; and &lt;em&gt;abstract&lt;/em&gt;. Some useful methods include &lt;code&gt;#send&lt;/code&gt;, &lt;code&gt;#include?&lt;/code&gt;, and enumerators like &lt;code&gt;#map&lt;/code&gt; and &lt;code&gt;#each_with_index&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bczayvAF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2e1ojwzmd1all4qsn1tn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bczayvAF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2e1ojwzmd1all4qsn1tn.png" alt="Alt Text" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;Now that I have built the basis of my CLI app, I am excited to add additional features, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scraping Outdoor Gear Lab's "Best Men's Rain Jackets of 2019" webpage

&lt;ul&gt;
&lt;li&gt;Provide the options for reviewing men's or women's rain jackets in CLI&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Providing a more customized user-experience:

&lt;ul&gt;
&lt;li&gt; Allow user to compare two jackets by selecting multiple properties and rating categories that they are interested in, then display the results side-by-side.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to explore my project on &lt;a href="https://github.com/jacqueline-lam/rain_jackets"&gt;Github&lt;/a&gt; or on &lt;a href="https://rubygems.org/gems/rain_jackets"&gt;rubygems.org&lt;/a&gt; and share your feedback with me! Thanks for reading!&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>oop</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
