<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Roudnitski</title>
    <description>The latest articles on DEV Community by Michael Roudnitski (@mroudnitski).</description>
    <link>https://dev.to/mroudnitski</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%2F553629%2F74b07e72-c9c0-4707-bca4-d9581fd0ecc3.jpg</url>
      <title>DEV Community: Michael Roudnitski</title>
      <link>https://dev.to/mroudnitski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mroudnitski"/>
    <language>en</language>
    <item>
      <title>Query multiple tables easily with Rails and Postgres Views</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Wed, 31 Jul 2024 18:13:39 +0000</pubDate>
      <link>https://dev.to/mroudnitski/query-multiple-tables-easily-with-rails-and-postgres-views-4lio</link>
      <guid>https://dev.to/mroudnitski/query-multiple-tables-easily-with-rails-and-postgres-views-4lio</guid>
      <description>&lt;p&gt;As your app grows in size, you'll likely find yourself having a few models with common attributes. You may eventually want to perform queries across these models as if they were one. For example,  sort all of your articles, books and videos by when they were created. It would be easy to come up with a brute force way to do this, but luckily we have powerful tools in Rails and Postgres, which we'll talk about in this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario
&lt;/h2&gt;

&lt;p&gt;Imagine you have a content management system with articles, books and videos. You implemented each of these content types as their own model backed by their own database table. Though they have some common attributes like title, description and created_at.&lt;/p&gt;

&lt;p&gt;This visual illustrates some attributes of articles, books and videos.&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%2Fuploads%2Farticles%2Fecf9iy46xsmrbajuoiyw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fecf9iy46xsmrbajuoiyw.jpg" alt="Illustration showing common attributes of example Video, Article and Book models in a venn diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see how we would naturally want to develop some features like searching all our content types in one query, or sorting them all by created_at.&lt;/p&gt;

&lt;p&gt;You could "brute force" this problem, which might involve querying each of these tables separately and combining the results in Ruby, but this is inefficient. A step up might be to create a UNION query to combine the results at the database level, but this has drawbacks as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Postgres Views in Rails
&lt;/h2&gt;

&lt;p&gt;We can create an elegant solution using Postgres &lt;a href="https://www.postgresql.org/docs/current/tutorial-views.html" rel="noopener noreferrer"&gt;Views&lt;/a&gt; and a single Rails model.&lt;/p&gt;

&lt;p&gt;Postges also offers &lt;a href="https://www.postgresql.org/docs/current/rules-materializedviews.html" rel="noopener noreferrer"&gt;Materialized Views&lt;/a&gt;. These actually store the results of their query. They can be useful if you're dealing with data that changes infrequently as they offer performance benefits. But in this tutorial, we'll only explore views.&lt;/p&gt;

&lt;p&gt;A view is like a virtual table. We can query it, but it doesn't actually store any data. It can act like a reusable subquery and that's how we'll be using it in this case.&lt;/p&gt;

&lt;p&gt;Additionally, thanks to the power of ActiveRecord, we'll only need 7 lines of code to implement the model in Rails. Let's see how it's done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the view in our database
&lt;/h3&gt;

&lt;p&gt;1) First, let's install the &lt;a href="https://github.com/scenic-views/scenic" rel="noopener noreferrer"&gt;Scenic&lt;/a&gt; gem. It's not required, but it gives us nice ways to create and manage views.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;scenic&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;2) Use scenic to generate the necessary files. We'll name our view &lt;code&gt;search_results&lt;/code&gt;. The scenic gem will create a couple files for us through this command.&lt;/p&gt;

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

rails generate scenic:view search_results
      create  db/views/search_results_v01.sql
      create  db/migrate/20240731153045_create_search_results.rb


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;search_results_v01.sql&lt;/code&gt; file defines our view, the migration will add the view to our database when we run &lt;code&gt;rails db:migrate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;3) Implement the view. Add the following to &lt;code&gt;db/views/search_results_v01.sql&lt;/code&gt;,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'Article'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;searchable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;
&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'Book'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;searchable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
&lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'Video'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;searchable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Then just run the migrations,&lt;/p&gt;

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

rails db:migrate


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

&lt;/div&gt;

&lt;p&gt;The above SQL unions our 3 tables and allows us to query against all the common columns as if we had all our articles, books and videos in a single table.&lt;/p&gt;

&lt;p&gt;The column names &lt;code&gt;searchable_type&lt;/code&gt; and &lt;code&gt;searchable_id&lt;/code&gt; are important. They follow Rails naming conventions for &lt;a href="https://guides.rubyonrails.org/association_basics.html#polymorphic-associations" rel="noopener noreferrer"&gt;polymorphic associations&lt;/a&gt; and will make it easy to go from a SearchResult to its associated Article, Book or Video. More on that in the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up the SearchResult model
&lt;/h3&gt;

&lt;p&gt;Now that we have a view in our Postgres database to simplify querying these 3 different tables, we need a way to query it with ActiveRecord in our Rails app.&lt;/p&gt;

&lt;p&gt;To do this, we can setup a really simple, but powerful model.&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;# app/models/search_result.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchResult&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:searchable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;polymorphic: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;readonly?&lt;/span&gt;
    &lt;span class="kp"&gt;true&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;The &lt;code&gt;belongs_to :searchable, polymorphic: true&lt;/code&gt; tells ActiveRecord this model is associated to a "searchable" model. Here is where the &lt;code&gt;searchable_type&lt;/code&gt; and &lt;code&gt;searchable_id&lt;/code&gt; column names are handy as they follow Rails conventions for polymorphic associations. This single line of code is the key to unlocking lots of built in Rails features.&lt;/p&gt;

&lt;p&gt;We also override the &lt;code&gt;#readonly?&lt;/code&gt; method to tell ActiveRecord this is not a model we should be expecting to write to with methods like &lt;code&gt;#save&lt;/code&gt; and &lt;code&gt;#update&lt;/code&gt;. After all, this model is backed by a view, not a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it Together
&lt;/h2&gt;

&lt;p&gt;With this setup, we have a really easy and elegant way to perform otherwise messy queries. Here are some examples&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple queries
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# find all content with 'rails' in the title&lt;/span&gt;
&lt;span class="no"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lower(title) LIKE ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="c1"&gt;#&amp;lt;SearchResult&amp;gt;, ...]&lt;/span&gt;

&lt;span class="c1"&gt;# find the 3 newest pieces of content&lt;/span&gt;
&lt;span class="no"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="c1"&gt;#&amp;lt;SearchResult&amp;gt;, #&amp;lt;SearchResult&amp;gt;, #&amp;lt;SearchResult&amp;gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;We can see that for our needs, this design behaves like any regular Rails model backed by a table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using our polymorphic belongs_to
&lt;/h3&gt;

&lt;p&gt;Lets also see how we can take advantage of the polymorphic association we set up.&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;# find and access the newest piece of content&lt;/span&gt;
&lt;span class="n"&gt;search_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&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="nf"&gt;first&lt;/span&gt;
&lt;span class="n"&gt;search_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;#&amp;lt;Video&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In this example, a Video was the most recently created piece of content. By calling &lt;code&gt;#searchable&lt;/code&gt; on our SearchResult instance, we can get an instance of the Video itself.&lt;/p&gt;

&lt;p&gt;This is incredibly useful for rendering partials, or even basic routing, as we could now add a link to the video with &lt;code&gt;#polymorphic_path&lt;/code&gt;.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="n"&gt;search_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;polymorphic_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Using Postgres Views with Rails provides an elegant solution for querying across multiple related models. This approach combines powerful features of both Postgres and Rails, resulting in clean, maintainable code. While it's not a silver bullet for all scenarios involving multiple models, it's a powerful tool to have in your Rails development toolkit. It's also worth noting this can all be achieved with MySQL as well. You can either create your view without scenic, or use the &lt;a href="https://github.com/EmpaticoOrg/scenic-mysql_adapter" rel="noopener noreferrer"&gt;MySQL adapter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>postgres</category>
      <category>tutorial</category>
      <category>backend</category>
    </item>
    <item>
      <title>Effortless Integration Testing in Rails: Mock External APIs with RSpec</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Wed, 27 Sep 2023 16:04:47 +0000</pubDate>
      <link>https://dev.to/mroudnitski/mocking-api-requests-in-rspec-rails-12ej</link>
      <guid>https://dev.to/mroudnitski/mocking-api-requests-in-rspec-rails-12ej</guid>
      <description>&lt;p&gt;It's not uncommon for our web apps to have features that make API calls to external services. In fact, if you have an application that is heavily reliant on external services, it can make integration testing a nightmare for you and your team.&lt;/p&gt;

&lt;p&gt;In this guide, I'll show you how we can use built in RSpec features like Instance Doubles and Shared Contexts to make integration testing a breeze in our Ruby on Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario
&lt;/h2&gt;

&lt;p&gt;Imagine we're building a project management web app that heavily relies on GitLab for file management. When a user creates a new project in our app, we have to create a project on gitlab.com using their API.&lt;/p&gt;

&lt;p&gt;In general, we never want to call an external API during a test. Remember, our tests are there to test that in a given scenario,  &lt;em&gt;our&lt;/em&gt; code behaves correctly. It would take longer to run, risk exposing API credentials and create unwanted data on our external services if we talked to them during our tests.&lt;/p&gt;

&lt;p&gt;Furthermore, it is safe to assume that GitLab has already thoroughly tested that their create project endpoint works. There's no need for our tests to cover that as well.&lt;/p&gt;

&lt;p&gt;So, how do we test this kind of code repeatedly and efficiently, without creating a bunch of test data on gitlab.com?&lt;/p&gt;

&lt;h2&gt;
  
  
  Instance Doubles
&lt;/h2&gt;

&lt;p&gt;The solution is to use Instance Doubles, which are also commonly referred to as Stubs or Mocks. They allow us to modify the behaviour of our code during a test.&lt;/p&gt;

&lt;p&gt;For example, let's consider GitLab's &lt;code&gt;POST https://gitlab.com/api/v1/projects&lt;/code&gt; endpoint. In our application, we would use an instance of a &lt;code&gt;Gitlab&lt;/code&gt; client to make this request&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gitlab_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hosted_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gitlab_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_project&lt;/span&gt;&lt;span class="p"&gt;({&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="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, let's tell RSpec that every time our code calls &lt;code&gt;gitlab_client.create_project({...})&lt;/code&gt;, simply return some predefined hash instead of a real response from gitlab.com.&lt;/p&gt;

&lt;p&gt;To implement this, we can simply define our expected response at the top of our spec like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# define our hardcoded expected response from GitLab&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:project&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="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&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="c1"&gt;# define an instance double&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:gitlab_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# override the return for the #project and #create_project methods on every instance of Gitlab::Client&lt;/span&gt;
  &lt;span class="n"&gt;instance_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Gitlab&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;project: &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;create_project: &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Gitlab::Client.new will return our instance double from above, instead of a real gitlab client&lt;/span&gt;
&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Gitlab&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gitlab_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reusable Instance Doubles as Shared Contexts
&lt;/h2&gt;

&lt;p&gt;We just saw how we can override our gitlab client during a test. But, it would be really tedious to write this out every time our code makes an API call to some service. Luckily, it's pretty easy to write this once and reuse it across tests, thanks to Shared Contexts.&lt;/p&gt;

&lt;p&gt;Building on our previous example, let's define a shared context that will allow us to easily reuse our instance double:&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;# spec/support/shared_contexts/gitlab_client.rb&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shared_context&lt;/span&gt; &lt;span class="s2"&gt;"with gitlab client"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# define our hardcoded expected response from GitLab&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:project&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="s2"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&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="c1"&gt;# define an instance double&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:gitlab_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# override the return for the project and create_project methods on every instance of Gitlab::Client&lt;/span&gt;
    &lt;span class="n"&gt;instance_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Gitlab&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;project: &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;create_project: &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Gitlab::Client.new will return our instance double from above, instead of a real gitlab client&lt;/span&gt;
  &lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:each&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Gitlab&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gitlab_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! To use it, we just need to remember to include it in any tests that interact with the gitlab client&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"/projects"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :request&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;include_context&lt;/span&gt; &lt;span class="s2"&gt;"with gitlab client"&lt;/span&gt; &lt;span class="c1"&gt;# important&lt;/span&gt;

  &lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s2"&gt;"POST /projects"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="s2"&gt;"with valid parameters"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"creates a new project"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="c1"&gt;# thanks to the include_context above,&lt;/span&gt;
        &lt;span class="c1"&gt;# we can test our create project endpoint&lt;/span&gt;
        &lt;span class="c1"&gt;# without worrying about GitLab&lt;/span&gt;
        &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="n"&gt;projects_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;project: &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:project&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;by&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="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>testing</category>
      <category>rails</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Infinite Scrolling with Zero JavaScript [Rails]</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Sat, 23 Sep 2023 17:50:01 +0000</pubDate>
      <link>https://dev.to/mroudnitski/achieving-smooth-infinite-scrolling-with-zero-javascript-leveraging-hotwire-and-turbo-frames-in-ruby-on-rails-41lo</link>
      <guid>https://dev.to/mroudnitski/achieving-smooth-infinite-scrolling-with-zero-javascript-leveraging-hotwire-and-turbo-frames-in-ruby-on-rails-41lo</guid>
      <description>&lt;p&gt;Infinite scrolling is a web design technique that loads content continuously as the user scrolls down the page, eliminating the need for pagination. It is a great way to improve user experience, providing a seamless way for users to consume content. In this article, we'll explore how to implement this feature in a Ruby on Rails application without writing a single line of JavaScript, thanks to the Hotwire library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8n9xwjwguufis8dyhf2.gif" 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%2Fuploads%2Farticles%2Fc8n9xwjwguufis8dyhf2.gif" alt="A GIF illustrating a thumb scrolling a smartphone infinitely"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://hotwired.dev/" rel="noopener noreferrer"&gt;Hotwire&lt;/a&gt;, we can leverage &lt;a href="https://turbo.hotwired.dev/handbook/frames" rel="noopener noreferrer"&gt;Turbo Frames&lt;/a&gt; and &lt;a href="https://turbo.hotwired.dev/handbook/streams" rel="noopener noreferrer"&gt;Streams&lt;/a&gt; to facilitate partial page updates, enhancing our user experience with very little code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario
&lt;/h2&gt;

&lt;p&gt;Imagine we are building a feature to display a list of user comments on a blog post. We want to continuously load these comments as the user scrolls, eliminating the need for them to click through pages. To achieve this, all we need is two turbo frames:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:comments_container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:comments&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="ss"&gt;src: &lt;/span&gt;&lt;span class="n"&gt;comments_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;page_size: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Skeleton Loading Indicator --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center space-x-4 animate-pulse"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rounded-full bg-gray-200 h-8 w-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 space-y-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-8 bg-gray-200 rounded w-1/2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This code sets up two turbo frames. The :comments_container is where we will be appending our lazy loaded page results, and the inner &lt;code&gt;:comments, 1&lt;/code&gt; frame is where we initially load the first page of comments. It will have an &lt;code&gt;id&lt;/code&gt; in the DOM of &lt;code&gt;comments_1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, we need to write the response from our comments controller. For each page we load, we need to render the comments for the current page. Crucially, we also need to set up the turbo_frame_tag to lazy load the next page of results (or render a message if there are no more results to fetch).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@page&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Loop through each comment and render it on the page --&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt; &lt;span class="ss"&gt;:comments_container&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Check if there are more pages to load --&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="vi"&gt;@total_pages&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Set up the next frame to load when it comes into view --&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="ss"&gt;:comments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@page&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="ss"&gt;src: &lt;/span&gt;&lt;span class="n"&gt;comments_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;page_size: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;page: &lt;/span&gt;&lt;span class="vi"&gt;@page&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="ss"&gt;loading: :lazy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- Skeleton Loading Indicator --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center space-x-4 animate-pulse"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rounded-full bg-gray-200 h-8 w-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 space-y-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-8 bg-gray-200 rounded w-1/2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Display a message if there are no more comments to load --&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@page&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"No comments yet"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"You've reached the end"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;After a bit of scrolling in the browser, if everything is working our DOM should be structured like this:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comments_container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comments_1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comments_2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"comments_3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;&lt;br&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-frame&amp;gt;&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Implementation Highlights&lt;br&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Lazy Loading:
&lt;/h3&gt;

&lt;p&gt;Imagine you're reading a book, and the next page only appears when you’re ready to read it. That's what &lt;code&gt;loading: :lazy&lt;/code&gt; does; it only loads the next set of comments when the user is ready to read them, i.e., when they come into view.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Skeleton Loading Indicator:
&lt;/h3&gt;

&lt;p&gt;You know when you load a video and see a blurry gray blob before it fully loads? That's what the skeleton loading indicator does; it provides a visual hint that more content is on its way.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Smooth Sailing with Zero JavaScript:
&lt;/h3&gt;

&lt;p&gt;The cool part? We achieved this modern user experience with just a few lines of code, without writing any JavaScript, using turbo frame tags instead.&lt;/p&gt;

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

&lt;p&gt;By leveraging the Hotwire library in Ruby on Rails, implementing infinite scrolling is like a JavaScript-free walk in the park. It's neat, efficient, and developer-friendly. I hope this technique becomes a useful tool in your developer toolbox!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Looking Back at my IBM Internship (2020-21)</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Tue, 04 Jan 2022 19:34:58 +0000</pubDate>
      <link>https://dev.to/mroudnitski/looking-back-at-my-ibm-internship-2020-21-100o</link>
      <guid>https://dev.to/mroudnitski/looking-back-at-my-ibm-internship-2020-21-100o</guid>
      <description>&lt;p&gt;Today I'm picking up where I left off and starting my first day as a full time Software Engineer at IBM so here are 5 things I learned in my 16 month internship (2020-21)👇&lt;/p&gt;

&lt;h2&gt;
  
  
  ⭐️ Show Initiative
&lt;/h2&gt;

&lt;p&gt;Volunteer to work on things and do your best to own entire projects. This means volunteering to plan, design, develop and ship. Obviously this requires a lot of trust from your team so do your best to earn this trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎰 Take Risks
&lt;/h2&gt;

&lt;p&gt;This goes hand in hand with showing initiative. Take risks and you will be rewarded. They don't have to be big risks, just being confident enough to volunteer your ideas in a call will reward you.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Learn as much as you can from your mentors, and even fellow interns
&lt;/h2&gt;

&lt;p&gt;Observing how your mentors operate day-to-day will give you a blueprint of where you need to be at. This is the target you are aiming for and you should do everything you can to help the other interns in your cohort achieve this, and vice-versa.&lt;/p&gt;

&lt;h2&gt;
  
  
  🙋‍♂️ Ask Questions... But not too many
&lt;/h2&gt;

&lt;p&gt;At the end of the day as an intern, you're there to learn and enjoy your experience. Asking questions from experienced engineers who have been there before is so valuable. But you also have to remember their time is valuable, so spend time really thinking about your question. "It's not working" won't get you anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Have fun with it
&lt;/h2&gt;

&lt;p&gt;This is the most important one. I never expected to work from home all 16 months, so I definitely missed out on lots of office ping pong and lunches. But that doesn't stop you from sending memes over Slack. If an office get-together is planned, attend it!&lt;/p&gt;

</description>
      <category>career</category>
    </item>
    <item>
      <title>Lazy Loading With Turbo [Rails]</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Mon, 26 Apr 2021 21:30:09 +0000</pubDate>
      <link>https://dev.to/mroudnitski/lazy-loading-with-turbo-rails-4018</link>
      <guid>https://dev.to/mroudnitski/lazy-loading-with-turbo-rails-4018</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The speed of a single-page web application without having to write any JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://turbo.hotwire.dev" rel="noopener noreferrer"&gt;Turbo&lt;/a&gt; allows you to add SPA functionality to your web apps. It comes with three components,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turbo Drive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Turbo Frames&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Turbo Streams&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To add lazy loading to our page, we'll be using Turbo Frames. You can &lt;a href="https://turbo.hotwire.dev/handbook/installing#in-a-ruby-on-rails-application" rel="noopener noreferrer"&gt;install Turbo&lt;/a&gt; directly into your Rails application.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Use Lazy Loading
&lt;/h1&gt;

&lt;p&gt;Let's say we have a blog and when a reader visits our blog, we want it to feel ⚡️ lightning quick; because we want them to come back someday. One way of accomplishing this is using a very common technique called lazy-loading.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Is Lazy Loading
&lt;/h1&gt;

&lt;p&gt;Our server can send out a light weight HTML page with loading indicators in place of our blog posts. Only after this light weight page fully loads, we can request our blog posts from the server.&lt;/p&gt;

&lt;p&gt;You can see examples of lazy loading everywhere in the wild, here's an example from &lt;a href="https://youtube.com" rel="noopener noreferrer"&gt;https://youtube.com&lt;/a&gt; where they lazy load their videos.&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%2Fuploads%2Farticles%2Fpzqyh19iood1rq1iu76z.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%2Fuploads%2Farticles%2Fpzqyh19iood1rq1iu76z.png" alt="Screen Shot 2021-04-26 at 3.54.01 PM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They're showing you &lt;a href="https://tailwindcss.com/docs/animation#pulse" rel="noopener noreferrer"&gt;skeleton loading indicators&lt;/a&gt; while you wait to see if Mr. Beast bought a country yet.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using Turbo Frames To Lazy Load
&lt;/h1&gt;

&lt;p&gt;So how can we use Turbo to lazy load our blog posts? It's really simple! We just need to add an action to our controller so we can separate blog post loading from our traditional index action.&lt;/p&gt;

&lt;p&gt;Let's start with our erb templates:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

# index.html.erb
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello World! Check out my blog posts below 👇&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"blog_posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="n"&gt;blog_posts_path&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Hold on a sec while I fetch my blog posts...&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;%# once blog_posts_path responds, it will replace everything inside this turbo frame tag with blog_posts.html.erb %&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;

# blog_posts.html.erb
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"blog_posts"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Done loading! Here are my blog posts.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@blog_posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And now in our controller:&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;# GET /blog&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# GET /blog_posts&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blog_posts&lt;/span&gt;
  &lt;span class="vi"&gt;@blog_posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;layout: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# Don't forget this optimization!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And of course, don't forget to add a GET route to your new action in &lt;code&gt;routes.rb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is all you need to do with Turbo Frames to accomplish lazy loading! We just had to add 1 action and 1 view.&lt;/p&gt;

&lt;p&gt;Let's go through it: when a user lands on our &lt;code&gt;index&lt;/code&gt; page, we send them a very small &lt;code&gt;index.html.erb&lt;/code&gt; file that lets them know our blog posts are loading. Once this page is loaded in the browser, Turbo will see we have a turbo frame tag on the page and it will notice our &lt;code&gt;src=&lt;/code&gt; attribute.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"blog_posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="n"&gt;blog_posts_path&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Turbo will then launch a request to &lt;code&gt;blog_posts_path&lt;/code&gt; and when a response with a matching turbo frame tag comes in,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

# notice the id matches our turbo frame tag in index.html.erb
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;turbo_frame_tag&lt;/span&gt; &lt;span class="s2"&gt;"blog_posts"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
...
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Turbo will work it's magic and replace our loading indicator with our blog posts!&lt;/p&gt;

&lt;h1&gt;
  
  
  Tip
&lt;/h1&gt;

&lt;p&gt;As of &lt;a href="https://world.hey.com/hotwired/turbo-7-0dd7a27f" rel="noopener noreferrer"&gt;Turbo 7&lt;/a&gt; you can now add the &lt;code&gt;loading="lazy"&lt;/code&gt; attribute to your turbo frame, which will only load your frame once it becomes visible to the user (when they scroll down for example).&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Turbo is super powerful and it can help you modernize your Rails app without writing JavaScript. And of course, Turbo doesn't have to be used with Rails! Everything I wrote about can even be applied to a JavaScript app if you're feeling especially crazy 😜.&lt;/p&gt;

&lt;p&gt;For more info, check out the official handbook &lt;a href="https://turbo.hotwire.dev/handbook/frames#lazily-loading-frames" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>turbo</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Extend the LTI Consumer XBlock (edX)</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Mon, 01 Mar 2021 16:46:51 +0000</pubDate>
      <link>https://dev.to/mroudnitski/how-to-extend-the-lti-consumer-xblock-edx-4g8i</link>
      <guid>https://dev.to/mroudnitski/how-to-extend-the-lti-consumer-xblock-edx-4g8i</guid>
      <description>&lt;p&gt;In this guide I will show you how to create your own XBlock based on the &lt;a href="https://github.com/edx/xblock-lti-consumer" rel="noopener noreferrer"&gt;xblock-lti-consumer&lt;/a&gt; from edX. This can be very useful if you have a ubiquitous LTI resource that requires configuration.&lt;/p&gt;

&lt;p&gt;Take for example a video conferencing app that is launched through LTI. If you want to add this app to your course, you would use the LTI Consumer XBlock and spend some time configuring it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcjzoj5tn9ilrr76n128.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcjzoj5tn9ilrr76n128.png" alt="Screen Shot 2021-02-26 at 4.38.27 PM" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Forking LTI Consumer XBlock
&lt;/h2&gt;

&lt;p&gt;An easy way to streamline this is to &lt;strong&gt;fork &lt;a href="https://github.com/edx/xblock-lti-consumer" rel="noopener noreferrer"&gt;xblock-lti-consumer&lt;/a&gt;&lt;/strong&gt; and use some &lt;a href="https://www.programiz.com/python-programming/inheritance" rel="noopener noreferrer"&gt;inheritance&lt;/a&gt; magic 🧙‍♂️ so that we can add an LTI resource to our course without configuration.&lt;/p&gt;

&lt;p&gt;In our fork, we need just make a few changes!&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/edx/xblock-lti-consumer/blob/d44cd795e577cfd3f5e807dd9b8250462bf0c180/lti_consumer/lti_xblock.py" rel="noopener noreferrer"&gt;lti_xblock.py&lt;/a&gt;, create a new class which inherits &lt;code&gt;LtiConsumerXBlock&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lti_xblock.py
&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyXBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LtiConsumerXBlock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;display_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Display Name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Describe me...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My Custom XBlock&lt;/span&gt;&lt;span class="sh"&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;What we just did is create a new XBlock, &lt;code&gt;MyXBlock&lt;/code&gt;. It is exactly the same as &lt;code&gt;LtiConsumerXBlock&lt;/code&gt; except for the display name.&lt;/p&gt;

&lt;p&gt;Next, open &lt;a href="https://github.com/edx/xblock-lti-consumer/blob/d44cd795e577cfd3f5e807dd9b8250462bf0c180/setup.py#L63" rel="noopener noreferrer"&gt;setup.py&lt;/a&gt; and modify it like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# setup.py
&lt;/span&gt;&lt;span class="n"&gt;entry_points&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xblock.v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lti_consumer = lti_consumer.lti_xblock:LtiConsumerXBlock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my_xblock = lti_consumer.lti_xblock: MyXBlock&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;# add this
&lt;/span&gt;    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in &lt;a href="https://github.com/edx/xblock-lti-consumer/blob/d44cd795e577cfd3f5e807dd9b8250462bf0c180/lti_consumer/__init__.py" rel="noopener noreferrer"&gt;lti-consumer/__init__.py&lt;/a&gt;, add the following line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lti_consumer/__init__.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.lti_xblock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MyXBlock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Now that we've created the XBlock, we can add it to a course by going to "Advanced Settings" and adding &lt;code&gt;"my_xblock"&lt;/code&gt; to the advanced module list.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqxxag8wqcxxg5o85m16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqxxag8wqcxxg5o85m16.png" alt="Screen Shot 2021-02-26 at 5.13.46 PM" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now see "My Custom XBlock" in advanced components&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9e43owhvdaveq07buklz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9e43owhvdaveq07buklz.png" alt="Screen Shot 2021-02-26 at 5.29.46 PM" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>edx</category>
      <category>xblock</category>
      <category>lti</category>
    </item>
    <item>
      <title>How to Use Elixir Pipes</title>
      <dc:creator>Michael Roudnitski</dc:creator>
      <pubDate>Tue, 05 Jan 2021 02:30:55 +0000</pubDate>
      <link>https://dev.to/mroudnitski/elixir-pipes-2cjb</link>
      <guid>https://dev.to/mroudnitski/elixir-pipes-2cjb</guid>
      <description>&lt;p&gt;A while ago I was introduced to an awesome functional language called Elixir. It's a young ecosystem but has plenty of useful libraries and frameworks, like &lt;a href="https://phoenixframework.org" rel="noopener noreferrer"&gt;Phoenix Web Framework&lt;/a&gt; which we use on my team here at IBM.&lt;/p&gt;

&lt;p&gt;When I was learning Elixir there were completely new concepts I hadn't seen in previous languages. The pipe operator is one of those and it's well worth learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipe Operator
&lt;/h2&gt;

&lt;p&gt;Is used to &lt;em&gt;pipe&lt;/em&gt; output from one function to another via the 1st argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# traditional&lt;/span&gt;
&lt;span class="n"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# with a pipe&lt;/span&gt;
&lt;span class="n"&gt;arg1&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All it does is feed the left side into the right side -- riveting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;It's best to teach something like this through examples. Let's say we want to write a function which filters an array and then sorts it.&lt;/p&gt;

&lt;p&gt;In JavaScript, we may write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;filterSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&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;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Elixir, we can simplify using pipes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;filterSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;# return is not required in Elixir!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we needed 2 pipes. One to pass the original list into the filter function, and the second to pass our filtered list into sort. We can chain pipes as much as we like. It makes for some really clear and maintainable code when done right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging with pipes
&lt;/h2&gt;

&lt;p&gt;We can debug effortlessly by adding &lt;code&gt;IO.inspect()&lt;/code&gt; (Elixir's equivalent to &lt;code&gt;console.log&lt;/code&gt; or &lt;code&gt;print&lt;/code&gt;) to our chain of pipes. Continuing from our example, let's inspect the state of our list between each manipulation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;filterSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;l&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;# return is not required in Elixir!&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because &lt;code&gt;IO.inspect()&lt;/code&gt; will return whatever value you originally gave it; allowing the pipe to carry on your list to the next function in your chain.&lt;/p&gt;

&lt;p&gt;Having a hard time understanding pipes? Feel free to ask questions in the comments!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
