<?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: Eloy Pérez</title>
    <description>The latest articles on DEV Community by Eloy Pérez (@epergo).</description>
    <link>https://dev.to/epergo</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%2F5860%2F0e876523-6355-49a8-80ac-74f6eaacabbb.png</url>
      <title>DEV Community: Eloy Pérez</title>
      <link>https://dev.to/epergo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/epergo"/>
    <language>en</language>
    <item>
      <title>Part 2 - Saving data. Creating a Ruby App to get notified about Epic Free Games.</title>
      <dc:creator>Eloy Pérez</dc:creator>
      <pubDate>Tue, 27 Jun 2023 09:15:08 +0000</pubDate>
      <link>https://dev.to/epergo/creating-a-ruby-app-to-get-notified-about-epic-free-games-saving-data-5e4k</link>
      <guid>https://dev.to/epergo/creating-a-ruby-app-to-get-notified-about-epic-free-games-saving-data-5e4k</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/epergo/creating-a-ruby-app-to-get-notified-about-epic-free-games-fetching-data-1ahm"&gt;Part 1&lt;/a&gt; | &lt;strong&gt;Part 2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we know how to fetch the information we want, we can insert it into a database for later use. In this series we are going to use PostgreSQL so first of all we need to install the gem &lt;code&gt;pg&lt;/code&gt; in our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add pg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in order to have a database to play with we can use &lt;code&gt;docker-compose&lt;/code&gt; to set up our development (and test) postgres instance. So in a new &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;services:
  db:
    image: postgres
    volumes:
      - ./.data/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
    ports:
      - &lt;span class="s2"&gt;"5432:5432"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that we can create our postgres container with &lt;code&gt;docker compose up -d&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For connecting to the database in our Ruby applications we are going to use &lt;a href="https://github.com/jeremyevans/sequel"&gt;sequel&lt;/a&gt; so let's add it with &lt;code&gt;bundle add sequel&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once we have our postgres instance up and running and &lt;code&gt;sequel&lt;/code&gt; installed we need to create the database itself. Sequel gives us all the tools to manage our database but doesn't provide &lt;code&gt;rake&lt;/code&gt; tasks, so we are going to create them for easier database management.&lt;/p&gt;

&lt;p&gt;Add the gem &lt;code&gt;rake&lt;/code&gt; with &lt;code&gt;bundle add rake&lt;/code&gt; and, in a new &lt;code&gt;Rakefile&lt;/code&gt; file, add the &lt;code&gt;db:create&lt;/code&gt; task:&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;namespace&lt;/span&gt; &lt;span class="ss"&gt;:db&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:create&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;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"sequel"&lt;/span&gt;

    &lt;span class="n"&gt;development_database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"free_game_development"&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"POSTGRES_URL"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DROP DATABASE IF EXISTS &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;development_database&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CREATE DATABASE &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;development_database&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="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;This task expects an environment variable called &lt;code&gt;POSTGRES_URL&lt;/code&gt; that points to the default &lt;code&gt;postgres&lt;/code&gt; database. For managing environment variables in your local environment you can use many different tools, I use &lt;a href="https://direnv.net/"&gt;direnv&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In a .envrc file&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;POSTGRES_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgres://postgres:password@127.0.0.1:5432/postgres"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can run the task with &lt;code&gt;bundle exec rake "db:create"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For running Sequel migrations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  namespace :db do
    task :create do |t, args|
      require "sequel"

      development_database = "free_game_development"

      db = Sequel.connect(ENV["POSTGRES_URL"].to_s)
      db.run("DROP DATABASE IF EXISTS #{development_database}")
      db.run("CREATE DATABASE #{development_database}")
    end
&lt;span class="gi"&gt;+
+   task :migrate do
+     require "sequel"
+     require "sequel/extensions/migration"
+
+     Sequel::Migrator.run(Sequel.connect(ENV["DATABASE_URL"].to_s), "db/migrations")
+   end
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This task will connect to a database using the &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  export POSTGRES_URL="postgres://postgres:password@127.0.0.1:5432/postgres"
&lt;span class="gi"&gt;+ export DATABASE_URL="postgres://postgres:password@127.0.0.1:5432/free_game_development"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rake task expects migrations to be placed in a folder &lt;code&gt;db/migrations&lt;/code&gt;, create it and lets move to creating models and their migrations.&lt;/p&gt;

&lt;p&gt;We want to store games and their promotions, either active or expired. The migrations for games need to include data like the game's title and the url slug for crafting the Store URL we will send in the notification.&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;# In a new file 'db/migrations/001_create_games.rb'&lt;/span&gt;
&lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:games&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;primary_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:url_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;down&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;drop_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:games&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;Each game may have many promotions so we are going to store promotion data in a different table. For each promotion we are going to store the reference to the game itself, start/end dates and the discount itself.&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;# In a new file 'db/migrations/002_create_promotions.rb'&lt;/span&gt;
&lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:promotions&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;primary_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;foreign_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:game_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:games&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:end_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:discount_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:discount_percentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;null: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;down&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;drop_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:promotions&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;Before running migrations lets modify the rake task to create a &lt;code&gt;schema.rb&lt;/code&gt; file with our database structure after running migrations. Having this file is useful when we want to quickly check the structure of the database without reading all migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  require "sequel"
  require "sequel/extensions/migration"

+ ENV["RACK_ENV"] ||= "development"

  Sequel::Migrator.run(Sequel.connect(ENV["DATABASE_URL"].to_s), "db/migrations")

+ if ENV["RACK_ENV"] == "development"
&lt;span class="gi"&gt;+   system("sequel -d #{ENV["DATABASE_URL"]} &amp;gt; db/schema.rb")
+ end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally run &lt;code&gt;bundle exec rake db:migrate&lt;/code&gt;, this should have created a &lt;code&gt;schema.rb&lt;/code&gt; with:&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;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:games&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;primary_key&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;
      &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="ss"&gt;:url_slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:schema_info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Integer&lt;/span&gt; &lt;span class="ss"&gt;:version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:default&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="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:promotions&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;primary_key&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;
      &lt;span class="n"&gt;foreign_key&lt;/span&gt; &lt;span class="ss"&gt;:game_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:games&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:key&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt; &lt;span class="ss"&gt;:start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="no"&gt;DateTime&lt;/span&gt; &lt;span class="ss"&gt;:end_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="no"&gt;String&lt;/span&gt; &lt;span class="ss"&gt;:discount_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:text&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
      &lt;span class="no"&gt;Integer&lt;/span&gt; &lt;span class="ss"&gt;:discount_percentage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:null&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make use of these tables we need two Sequel models, &lt;code&gt;Game&lt;/code&gt; and &lt;code&gt;Promotion&lt;/code&gt;. In our &lt;code&gt;app&lt;/code&gt; directory create a new &lt;code&gt;models&lt;/code&gt; directory to store them.&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;# app/models/game.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Game&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="n"&gt;one_to_many&lt;/span&gt; &lt;span class="ss"&gt;:promotions&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# app/models/promotion.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Promotion&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="n"&gt;many_to_one&lt;/span&gt; &lt;span class="ss"&gt;:game&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Saving data into the database
&lt;/h2&gt;

&lt;p&gt;In the previous post we created a class to fetch all games and return them as &lt;code&gt;Data&lt;/code&gt; structures. We need to create a new class that make use of that &lt;strong&gt;and&lt;/strong&gt; insert the data into the database.&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;app/update_games.rb&lt;/code&gt; with our new class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpdateGames&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&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;As you can see this class receives the adapter to use and contains a method to do all the logic. The &lt;code&gt;call&lt;/code&gt; method will fetch all free games and insert them (and their promotions) into the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  class UpdateGames
    def initialize(adapter)
      @adapter = adapter
    end

    def call
&lt;span class="gi"&gt;+       @adapter.get_free_games.each do |game_data|
+         game = Game.update_or_create(title: game_data.title) do |new_game|
+           new_game.url_slug = game_data.url_slug
+         end
+
+         game_data.promotions.each do |promotion_data|
+           Promotion.update_or_create(
+             game_id: game.id,
+             start_date: promotion_data.start_date,
+             end_date: promotion_data.end_date,
+             discount_type: promotion_data.discount_type,
+             discount_percentage: promotion_data.discount_percentage
+           )
+         end
+       end
+     end
&lt;/span&gt;  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use &lt;code&gt;update_or_create&lt;/code&gt; because we are going to run this everyday and we will receive multiple times the same games.&lt;/p&gt;

&lt;p&gt;Now lets create a simple test that checks that we have insert the correct number of games and promotions. In a new file &lt;code&gt;spec/app/epic_store_adapter_spec.rb&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"../../app/update_games"&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;describe&lt;/span&gt; &lt;span class="no"&gt;UpdateGames&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;described_class&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;adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;EpicStoreAdapter&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="s2"&gt;"https://store-site-backend-static.ak.epicgames.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"inserts game data into the database"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"free_games"&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;expect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="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;Game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;from&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="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"inserts promotion data into the database"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"free_games"&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;expect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="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;Promotion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;from&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="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now before we run the test we have to update our testing configuration to connect to the database. First of all we need to create a different database for testing.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;.envrc&lt;/code&gt; file to add a new environment variable &lt;code&gt;TEST_DATABASE_URL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;export POSTGRES_URL="postgres://postgres:password@127.0.0.1:5432/postgres"
export DATABASE_URL="postgres://postgres:password@127.0.0.1:5432/free_game_development"
export TEST_DATABASE_URL="postgres://postgres:password@127.0.0.1:5432/free_game_test"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And create a new rake tast to prepare the test environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  namespace :db do
  ...
&lt;span class="gi"&gt;+   namespace :test do
+     task :prepare do
+       require "sequel"
+       require "sequel/extensions/migration"
+
+       test_database = "free_game_test"
+
+       db = Sequel.connect(ENV["POSTGRES_URL"].to_s)
+       db.run("DROP DATABASE IF EXISTS #{test_database}")
+       db.run("CREATE DATABASE #{test_database}")
+
+       Sequel::Migrator.run(Sequel.connect(ENV["TEST_DATABASE_URL"].to_s), "db/migrations")
+     end
+   end
&lt;/span&gt;  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in our &lt;code&gt;spec_helper&lt;/code&gt; we have to connect to the new database using Sequel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;
- require "vcr"
&lt;span class="gi"&gt;+ Bundler.require(:default, :test)
&lt;/span&gt;
+ Sequel.connect(ENV["DATABASE_URL"].to_s)
&lt;span class="gi"&gt;+ Sequel::Model.plugin(:update_or_create)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have removed the &lt;code&gt;vcr&lt;/code&gt; require and updated it to use the bundler &lt;code&gt;require&lt;/code&gt; method that will require every gem we have defined in our Gemfile, either outside any groups or in the testing one.&lt;/p&gt;

&lt;p&gt;Is mandatory to enable the plugin &lt;code&gt;update_or_create&lt;/code&gt; because we use in our &lt;code&gt;UpdateGames&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;To allow our test to access our models we need to require them too in &lt;code&gt;spec_helper&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  Bundler.require(:default, :test)

  Sequel.connect(ENV["DATABASE_URL"].to_s)
  Sequel::Model.plugin(:update_or_create)

+ require_relative "../app/models/game"
&lt;span class="gi"&gt;+ require_relative "../app/models/promotion"
&lt;/span&gt;
  VCR.configure do |config|
    config.cassette_library_dir = "spec/fixtures/vcr_cassettes"
    config.hook_into(:faraday)
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it, let's run the test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
..F

Failures:

  1&lt;span class="o"&gt;)&lt;/span&gt; UpdateGames inserts promotion data into the database
     Failure/Error: expect &lt;span class="o"&gt;{&lt;/span&gt; subject.call &lt;span class="o"&gt;}&lt;/span&gt;.to change &lt;span class="o"&gt;{&lt;/span&gt; Promotion.count &lt;span class="o"&gt;}&lt;/span&gt;.from&lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;.to&lt;span class="o"&gt;(&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt;
       expected &lt;span class="sb"&gt;`&lt;/span&gt;Promotion.count&lt;span class="sb"&gt;`&lt;/span&gt; to have initially been 0, but was 5
     &lt;span class="c"&gt;# ./spec/app/update_games_spec.rb:17:in `block (3 levels) in &amp;lt;top (required)&amp;gt;'&lt;/span&gt;
     &lt;span class="c"&gt;# ./spec/app/update_games_spec.rb:16:in `block (2 levels) in &amp;lt;top (required)&amp;gt;'&lt;/span&gt;

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.07583 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 0.30525 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
3 examples, 1 failure

Failed examples:

rspec ./spec/app/update_games_spec.rb:15 &lt;span class="c"&gt;# UpdateGames inserts promotion data into the database&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Woops. The first one succeeded but the second one failed. It says that initially it should have had 0 promotions in the database which is correct but it found 5 already. The issue is that we already inserted promotions in the previous spec so we have to clean up the database between test cases.&lt;/p&gt;

&lt;p&gt;For that we can add the gem &lt;code&gt;database_cleaner-sequel&lt;/code&gt; which will truncate all data created in each test. Add it as usual with &lt;code&gt;bundle add database_cleaner-sequel&lt;/code&gt; and configure it in our &lt;code&gt;spec_helper.rb&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  RSpec.configure do |config|
&lt;span class="gi"&gt;+   config.before(:suite) do
+     DatabaseCleaner.strategy = :transaction
+     DatabaseCleaner.clean_with(:truncation)
+   end
+
+   config.around(:each) do |example|
+     DatabaseCleaner.cleaning do
+       example.run
+     end
+   end
&lt;/span&gt;  ...
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if we run our tests again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
...

Finished &lt;span class="k"&gt;in &lt;/span&gt;0.06822 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 0.30429 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
3 examples, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice!&lt;/p&gt;

&lt;p&gt;We've reached the end of the second part. We have created the logic needed to save games and promotions into our database. The next step is to notify about new promotions to a telegram channel that users can join.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>telegram</category>
      <category>games</category>
      <category>sequel</category>
    </item>
    <item>
      <title>Part 1 - Fetching Data. Creating a Ruby App to get notified about Epic Free Games.</title>
      <dc:creator>Eloy Pérez</dc:creator>
      <pubDate>Fri, 23 Jun 2023 10:12:34 +0000</pubDate>
      <link>https://dev.to/epergo/creating-a-ruby-app-to-get-notified-about-epic-free-games-fetching-data-1ahm</link>
      <guid>https://dev.to/epergo/creating-a-ruby-app-to-get-notified-about-epic-free-games-fetching-data-1ahm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Part 1&lt;/strong&gt; | &lt;a href="https://dev.to/epergo/creating-a-ruby-app-to-get-notified-about-epic-free-games-saving-data-5e4k"&gt;Part 2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Epic Store there is a selection of free games that changes each week. If you want to get them you have to manually access the web page of the store each week, could we build something to get notified?&lt;/p&gt;

&lt;p&gt;Turns out there is an endpoint to get the current free games so we can build a Ruby app to fetch it and notify us if there is anything new. Let's build it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;free-game-watcher
&lt;span class="nb"&gt;cd &lt;/span&gt;free-game-watcher
bundle init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a simple &lt;code&gt;Gemfile&lt;/code&gt; pointing to RubyGems and nothing more. I like to always have installed a static analysis library, so I encourage you to add &lt;a href="https://github.com/standardrb/standard"&gt;standard&lt;/a&gt; to your development dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add standard &lt;span class="nt"&gt;--groups&lt;/span&gt; development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And configure your editor to run it on save and/or tell you about issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making HTTP Requests
&lt;/h2&gt;

&lt;p&gt;The main selling point of the application is to get notified of free games, for that we need to query the Epic Games Store to fetch the current selection.&lt;/p&gt;

&lt;p&gt;The endpoint that the Store uses is public so we will use it as well. It is a GET request without query params and doesn't require any authentication. Let's have a look.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl https://store-site-backend-static.ak.epicgames.com/freeGamesPromotions | jq &lt;span class="s1"&gt;'.data.Catalog.searchStore.elements[1] | {title: .title, promotions: .promotio
ns.promotionalOffers, upcoming: .promotions.upcomingPromotionalOffers, images: .keyImages[3]}'&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"title"&lt;/span&gt;: &lt;span class="s2"&gt;"Breathedge"&lt;/span&gt;,
  &lt;span class="s2"&gt;"promotions"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"promotionalOffers"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"startDate"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-04-27T15:00:00.000Z"&lt;/span&gt;,
          &lt;span class="s2"&gt;"endDate"&lt;/span&gt;: &lt;span class="s2"&gt;"2023-05-04T15:00:00.000Z"&lt;/span&gt;,
          &lt;span class="s2"&gt;"discountSetting"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"discountType"&lt;/span&gt;: &lt;span class="s2"&gt;"PERCENTAGE"&lt;/span&gt;,
            &lt;span class="s2"&gt;"discountPercentage"&lt;/span&gt;: 0
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;,
  &lt;span class="s2"&gt;"upcoming"&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;,
  &lt;span class="s2"&gt;"images"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"type"&lt;/span&gt;: &lt;span class="s2"&gt;"Thumbnail"&lt;/span&gt;,
    &lt;span class="s2"&gt;"url"&lt;/span&gt;: &lt;span class="s2"&gt;"https://cdn1.epicgames.com/08ae29e4f70a4b62aa055e383381aa82/offer/EGS_Breathedge_RedRuinsSoftworks_S2-1200x1600-c0559585221ea11c9d48273c3a79b1ba.jpg"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I've used &lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt; to show you the information that we will care about (only for one game), feel free to inspect the full JSON response.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We have to implement this query in Ruby, for that we will use &lt;a href="https://github.com/lostisland/faraday"&gt;Faraday&lt;/a&gt;. There are many different http libraries but I've chosen Faraday because, besides being a great tool, the gem that we will use later to connect to telegram has Faraday as a dependency so installing the same library saves us from having different libraries for the same purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle add faraday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are going to create an &lt;em&gt;EpicStoreAdapter&lt;/em&gt; class that will contain the method to query the endpoint. Where should we place it?&lt;/p&gt;

&lt;p&gt;The application logic should go into its own &lt;code&gt;app&lt;/code&gt; folder. If later we want to add a simple web page we can create a standalone &lt;code&gt;web&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;So, create the new &lt;code&gt;app&lt;/code&gt; folder and place a new file called &lt;code&gt;epic_store_adapter.rb&lt;/code&gt; inside it with our new class that needs to receive the base URL of the endpoint to configure the Faraday client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; app/epic_store_adapter.rb

+ class EpicStoreAdapter
&lt;span class="gi"&gt;+  def initialize(base_url)
+    @connection = Faraday.new(
+      url: base_url,
+      headers: {"Content-Type": "application/json"},
+      request: {timeout: 15}
+    )
+  end
+ end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a method to query free games:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; app/epic_store_adapter.rb

class EpicStoreAdapter
  def initialize(base_url)
    @connection = Faraday.new(
      url: base_url,
      headers: {"Content-Type": "application/json"},
      request: {timeout: 15}
    )
  end
&lt;span class="gi"&gt;+
+  def get_free_games
+    response = @connection.get("/freeGamesPromotions")
+    JSON.parse(response.body)
+  end
&lt;/span&gt;&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's check that this class does what we want. To create basic for it we'll use &lt;a href="https://github.com/rspec/rspec-metagem"&gt;rspec&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install RSpec&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle add rspec &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nb"&gt;test
&lt;/span&gt;Fetching gem metadata from https://rubygems.org/...
...

&lt;span class="c"&gt;# Init RSpec configuration files&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec &lt;span class="nt"&gt;--init&lt;/span&gt;
  create   .rspec
  create   spec/spec_helper.rb

&lt;span class="c"&gt;# Check that it works&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rspec
No examples found.


Finished &lt;span class="k"&gt;in &lt;/span&gt;0.00051 seconds &lt;span class="o"&gt;(&lt;/span&gt;files took 0.06503 seconds to load&lt;span class="o"&gt;)&lt;/span&gt;
0 examples, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before creating our spec we are going to add &lt;a href="https://github.com/vcr/vcr"&gt;VCR&lt;/a&gt; to store API responses so everytime we run tests we avoid making new real requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install VCR&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle add vcr &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VCR needs to be configured first so in our &lt;code&gt;spec_helper.rb&lt;/code&gt; file (which was created previously with &lt;code&gt;rspec --init&lt;/code&gt;) set the cassette library directory and the HTTP library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; spec/spec_helper.rb

+ require "vcr"
&lt;span class="gi"&gt;+
+ VCR.configure do |config|
+   config.cassette_library_dir = "spec/fixtures/vcr_cassettes"
+   config.hook_into(:faraday)
+ end
&lt;/span&gt;
RSpec.configure do |config|
  # ...
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we can write a simple test to check that we have correctly fetched games.&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/app/epic_store_adapter_spec.rb&lt;/span&gt;

&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"../../app/epic_store_adapter"&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;describe&lt;/span&gt; &lt;span class="no"&gt;EpicStoreAdapter&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:adapter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;EpicStoreAdapter&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="s2"&gt;"https://store-site-backend-static.ak.epicgames.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"fetches current selection of free games"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;VCR&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_cassette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"free_games"&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_free_games&lt;/span&gt;
      &lt;span class="n"&gt;games&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"Catalog"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"searchStore"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;"elements"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;games&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Borderlands 3 Season Pass"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;We will clean up that &lt;code&gt;require_relative&lt;/code&gt; later&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You should find the saved response from the API in the folder we specified before.&lt;/p&gt;

&lt;p&gt;Now, instead of returning the parsed JSON from the response we could create a couple of data structures to store the information we care about. For that we can use the new &lt;a href="https://docs.ruby-lang.org/en/3.2/Data.html"&gt;Data&lt;/a&gt; core class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;class EpicStoreAdapter
&lt;/span&gt;&lt;span class="gi"&gt;+ GameData = Data.define(
+   :title,
+   :product_slug,
+   :url_slug,
+   :image_url,
+   :promotions
+ )
+ PromotionData = Data.define(
+   :start_date,
+   :end_date,
+   :discount_type,
+   :discount_percentage
+ )
&lt;/span&gt;
def initialize(base_url)
&lt;span class="p"&gt;end
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Data&lt;/code&gt; class is similar to &lt;code&gt;Struct&lt;/code&gt; and it shares most of their implementation. We'll store only a selection of attributes from games and their promotions.&lt;/p&gt;

&lt;p&gt;We are going to create a new private method to map the JSON response to an array of Games.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ private
+
+ def build_games(games_data)
+   games_data.map do |game|
+     thumbnail_image = game["keyImages"].find { |image| image["type"] == "Thumbnail" }
+
+     GameData.new(
+       title: game["title"],
+       product_slug: game["productSlug"],
+       url_slug: game["urlSlug"],
+       image_url: thumbnail_image&amp;amp;.fetch("url", nil)
+     )
+   end
+ end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see is pretty straight-forward, the only attribute a bit more complex to fetch is the thumbnail image that we have to find in the array of images.&lt;/p&gt;

&lt;p&gt;There is still one attribute missing, &lt;code&gt;promotions&lt;/code&gt;. Promotions have their own structure so we are going to create another to map them into &lt;code&gt;Data&lt;/code&gt; structures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ def build_promotions(promotional_offers)
+   promotions = []
+   promotional_offers.each do |offer|
+     offer["promotionalOffers"].each do |promotional_offer|
+       promotions &amp;lt;&amp;lt; PromotionData.new(
+         start_date: Time.parse(promotional_offer["startDate"]),
+         end_date: Time.parse(promotional_offer["endDate"]),
+         discount_type: promotional_offer["discountSetting"]["discountType"],
+         discount_percentage: promotional_offer["discountSetting"]["discountPercentage"]
+       )
+     end
+   end
&lt;/span&gt;
+   promotions
&lt;span class="gi"&gt;+ end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we traverse the JSON to get the data we want (check the full JSON you have saved for details)&lt;/p&gt;

&lt;p&gt;Now we can update our &lt;code&gt;build_games&lt;/code&gt; method to add promotions. The JSON contains both current promotions and future ones, we can save both.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   def build_games(games_data)
    games_data.map do |game|
&lt;span class="gi"&gt;+     active_promotions = build_promotions(game.dig("promotions", "promotionalOffers") || [])
+     upcoming_promotions = build_promotions(game.dig("promotions", "upcomingPromotionalOffers") || [])
&lt;/span&gt;      thumbnail_image = game["keyImages"].find { |image| image["type"] == "Thumbnail" }

      GameData.new(
        title: game["title"],
        product_slug: game["productSlug"],
        url_slug: game["urlSlug"],
        image_url: thumbnail_image&amp;amp;.fetch("url", nil),
&lt;span class="gi"&gt;+       promotions: active_promotions + upcoming_promotions
&lt;/span&gt;      )
    end
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we have to update our &lt;code&gt;get_free_games&lt;/code&gt; method to, instead of returning the parsed JSON, create the needed structures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  def get_free_games
    response = @connection.get("/freeGamesPromotions")
&lt;span class="gd"&gt;-     JSON.parse(response.body)
&lt;/span&gt;&lt;span class="gi"&gt;+     parsed_response = JSON.parse(response.body)
+     games_data = parsed_response["data"]["Catalog"]["searchStore"]["elements"]
+     build_games(games_data)
&lt;/span&gt;  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now our spec is failing, we have to update it accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  # spec/app/epic_store_adapter_spec.rb

  require_relative "../../app/epic_store_adapter"

  RSpec.describe EpicStoreAdapter do
    let(:adapter) { EpicStoreAdapter.new("https://store-site-backend-static.ak.epicgames.com") }

    it "fetches current selection of free games" do
      VCR.use_cassette("free_games") do
&lt;span class="gd"&gt;-       response = adapter.get_free_games
-       games = response["data"]["Catalog"]["searchStore"]["elements"]
-       expect(games.first["title"]).to eq("Borderlands 3 Season Pass")
&lt;/span&gt;&lt;span class="gi"&gt;+       games = adapter.get_free_games
+       expect(games.first.title).to eq("Borderlands 3 Season Pass")
&lt;/span&gt;      end
    end
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it for now, we have implemented the request to the Epic Store and mapped the data into our own data structures.&lt;/p&gt;

&lt;p&gt;In the following part we will save the data into a database.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>telegram</category>
      <category>games</category>
      <category>sequel</category>
    </item>
    <item>
      <title>Using Nemo in Fedora</title>
      <dc:creator>Eloy Pérez</dc:creator>
      <pubDate>Thu, 08 Dec 2022 19:35:00 +0000</pubDate>
      <link>https://dev.to/epergo/using-nemo-in-fedora-37-1ha3</link>
      <guid>https://dev.to/epergo/using-nemo-in-fedora-37-1ha3</guid>
      <description>&lt;p&gt;File managers are used a lot. GNOME comes with its own file manager called &lt;a href="https://gitlab.gnome.org/GNOME/nautilus"&gt;&lt;strong&gt;Files&lt;/strong&gt;&lt;/a&gt; (formerly known as &lt;em&gt;Nautilus&lt;/em&gt;) which is good enough for most operations. However, every time I do heavy work with it, there is one major feature that is missing: split view.&lt;/p&gt;

&lt;p&gt;Split view was part of Nautilus up until version 3.6 where it was removed to reduce the complexity of the software. However, there are at least &lt;em&gt;dozens&lt;/em&gt; of us that just work better with a split view file manager so, what's the alternative?&lt;/p&gt;

&lt;p&gt;For me is &lt;a href="https://github.com/linuxmint/nemo/"&gt;Nemo&lt;/a&gt;. Nemo is the file manager that &lt;a href="https://linuxmint.com/"&gt;Linux Mint&lt;/a&gt; maintains and uses by default in its desktop environment. It is a fork of Nautilus so is similar and migrating to it is painless.&lt;/p&gt;

&lt;p&gt;As of today my Linux distribution of choice is &lt;a href="https://getfedora.org/"&gt;Fedora&lt;/a&gt;, so Nemo is not installed by default, as expected. Fortunately, installing it is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;nemo-5.4.3-1.fc37.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can install extensions to Nemo too, for example to be able to use &lt;a href="https://wiki.gnome.org/Apps/FileRoller"&gt;File Roller&lt;/a&gt; you just do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;nemo-fileroller-5.4.1-2.fc37.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is one minor thing we have to fix though, there isn't any launcher to access the software we just installed. You can launch it from the terminal just typing &lt;code&gt;nemo&lt;/code&gt; but that's not ideal for a GUI application.&lt;/p&gt;

&lt;p&gt;To add a new icon to the GNOME launcher for Nemo, you just have to create its &lt;em&gt;Desktop entry&lt;/em&gt; file and place it in &lt;code&gt;~/.local/share/applications&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;[Desktop Entry]
Encoding = UTF-8
Name = Nemo File Manager
Exec = /usr/bin/nemo
Icon = nemo
Type = Application
Categories = Development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find the location of the Nemo executable, you can use &lt;code&gt;which&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;which nemo
/usr/bin/nemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Now once you are inside Nemo you can press &lt;code&gt;F3&lt;/code&gt; and the side panel will show up.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>gnome</category>
      <category>productivity</category>
      <category>desktop</category>
    </item>
    <item>
      <title>Format CSV in your terminal using awk</title>
      <dc:creator>Eloy Pérez</dc:creator>
      <pubDate>Mon, 28 Nov 2022 12:02:00 +0000</pubDate>
      <link>https://dev.to/epergo/format-csv-in-your-terminal-using-awk-3edb</link>
      <guid>https://dev.to/epergo/format-csv-in-your-terminal-using-awk-3edb</guid>
      <description>&lt;p&gt;It's pretty common to have a CSV file that you want to have a look at and you are forced to either open it in a software that support reading such files like Libreoffice Calc or you open it as a plain text file in which case you are not able to read the content easily because it's not pretty printed.&lt;/p&gt;

&lt;p&gt;However, if you already have a terminal open, you can easily format the file right in your terminal without the need for extra software. Say we have the following CSV file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type, Primary Text, Name, Description, Owner
"A type", "Lorem ipsum dolor sit amet consectetur adipiscing elit", "A name", "This requirement defines something", "John Snow"
"Something else", "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", "Another name", "This is THE requirement", "Eloy Perez"
"Important stuff", "consectetur adipiscing elit", "And another one", "It cannot be", "Luke"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you want to pretty print it right in your terminal, you can use &lt;code&gt;awk&lt;/code&gt; for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="s1"&gt;'{ printf "%-030s %-70s %-30s %-40s %-30s\n", $1, $2, $3, $4, $5 }'&lt;/span&gt; file.csv
Type                            Primary Text                                                           Name                           Description                              Owner
&lt;span class="s2"&gt;"A type"&lt;/span&gt;                        &lt;span class="s2"&gt;"Lorem ipsum dolor sit amet consectetur adipiscing elit"&lt;/span&gt;               &lt;span class="s2"&gt;"A name"&lt;/span&gt;                       &lt;span class="s2"&gt;"This requirement defines something"&lt;/span&gt;     &lt;span class="s2"&gt;"John Snow"&lt;/span&gt;
&lt;span class="s2"&gt;"Something else"&lt;/span&gt;                &lt;span class="s2"&gt;"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"&lt;/span&gt;    &lt;span class="s2"&gt;"Another name"&lt;/span&gt;                 &lt;span class="s2"&gt;"This is THE requirement"&lt;/span&gt;                &lt;span class="s2"&gt;"Eloy Perez"&lt;/span&gt;
&lt;span class="s2"&gt;"Important stuff"&lt;/span&gt;               &lt;span class="s2"&gt;"consectetur adipiscing elit"&lt;/span&gt;                                          &lt;span class="s2"&gt;"And another one"&lt;/span&gt;              &lt;span class="s2"&gt;"It cannot be"&lt;/span&gt;                           &lt;span class="s2"&gt;"Luke"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;awk&lt;/code&gt; works with text that uses whitespace as separator &lt;code&gt;-F','&lt;/code&gt; specifies what separator to use. In this case we are working with &lt;strong&gt;C&lt;/strong&gt;omma-&lt;strong&gt;S&lt;/strong&gt;eparated &lt;strong&gt;V&lt;/strong&gt;alues, so we will use a &lt;code&gt;,&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After that, we configure the output for each line of the file. We configure each column with the width of the content it will contain, for example &lt;code&gt;%-30s&lt;/code&gt; means that it will have a string (&lt;code&gt;%s&lt;/code&gt;) aligned to the left (&lt;code&gt;%-s&lt;/code&gt; character) and it will be 30 characters wide (&lt;code&gt;%-30s&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Then we specify what values are going to be printed &lt;code&gt;$1, $2, $3, $4, $5&lt;/code&gt; which are the return values of splitting the original string with the separator &lt;code&gt;,&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we wanted to only print certain columns, we just have to not add them to the &lt;code&gt;awk&lt;/code&gt; program. Let's say we only want to show &lt;code&gt;Type&lt;/code&gt; and &lt;code&gt;Name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="s1"&gt;'{ printf "%-30s %-30s\n", $1, $3 }'&lt;/span&gt; file.csv
Type                            Name
&lt;span class="s2"&gt;"A type"&lt;/span&gt;                        &lt;span class="s2"&gt;"A name"&lt;/span&gt;
&lt;span class="s2"&gt;"Something else"&lt;/span&gt;                &lt;span class="s2"&gt;"Another name"&lt;/span&gt;
&lt;span class="s2"&gt;"Important stuff"&lt;/span&gt;               &lt;span class="s2"&gt;"And another one"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>csv</category>
      <category>terminal</category>
      <category>unix</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Linux application stuck in fullscreen</title>
      <dc:creator>Eloy Pérez</dc:creator>
      <pubDate>Thu, 24 Nov 2022 12:45:21 +0000</pubDate>
      <link>https://dev.to/epergo/linux-application-stuck-in-fullscreen-2jag</link>
      <guid>https://dev.to/epergo/linux-application-stuck-in-fullscreen-2jag</guid>
      <description>&lt;p&gt;From time to time a running application gets stuck in fullscreen mode. This has happened to me even in software that does not support fullscreen mode, like Spotify. Usually when that happens I used to &lt;code&gt;kill&lt;/code&gt; the process and restart it, but there is a way of fixing it without being forced to restart the application.&lt;/p&gt;

&lt;p&gt;We can use &lt;a href="https://linux.die.net/man/1/wmctrl"&gt;&lt;code&gt;wmctrl&lt;/code&gt;&lt;/a&gt; for that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;wmctrl is a command that can be used to interact with an X Window manager that is compatible with the EWMH/NetWM specification. wmctrl can query the window manager for information, and it can request that certain window management actions be taken.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Important: &lt;code&gt;wmctrl&lt;/code&gt; &lt;strong&gt;wont't&lt;/strong&gt; work with Wayland applications.&lt;/p&gt;

&lt;p&gt;First we need to list all applications with &lt;code&gt;wmctrl -l&lt;/code&gt; to get the name of the one stuck.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; wmctrl &lt;span class="nt"&gt;-l&lt;/span&gt;
0x00c00003  1 fedora Spotify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have to modify the &lt;code&gt;fullscreen&lt;/code&gt; property of the application, for that we'll use &lt;code&gt;wmctrl -b&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; wmctrl &lt;span class="nt"&gt;-r&lt;/span&gt; Spotify &lt;span class="nt"&gt;-b&lt;/span&gt; toggle,fullscreen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've used &lt;code&gt;-r&lt;/code&gt; to specify the application we are going to modify and &lt;code&gt;-b&lt;/code&gt; to tell &lt;code&gt;wmctrl&lt;/code&gt; the action (&lt;code&gt;toggle&lt;/code&gt;) and the property (&lt;code&gt;fullscreen&lt;/code&gt;) to be modified.&lt;/p&gt;

&lt;p&gt;With that the application should be back to windowed mode.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>fullscreen</category>
      <category>xorg</category>
      <category>wmctrl</category>
    </item>
    <item>
      <title>Get static analysis of your Pull Requests with Pronto</title>
      <dc:creator>Eloy Pérez</dc:creator>
      <pubDate>Mon, 14 Aug 2017 17:47:18 +0000</pubDate>
      <link>https://dev.to/epergo/get-static-analysis-of-your-pull-requests-with-pronto</link>
      <guid>https://dev.to/epergo/get-static-analysis-of-your-pull-requests-with-pronto</guid>
      <description>&lt;p&gt;Almost in every programming language there are static analysis tools which evaluates code quality and check if it follows a series of basics rules of styling.&lt;/p&gt;

&lt;p&gt;This is very important because in a project where many developers work, it will be easier to read, understand and collaborate with if everyone generates their code following a set of common rules.&lt;/p&gt;

&lt;p&gt;In Ruby, for example, we have &lt;a href="https://github.com/bbatsov/rubocop" rel="noopener noreferrer"&gt;Rubocop&lt;/a&gt; as reference to styling (and more) rules. These rules have been created and maintained by the ruby community.&lt;/p&gt;

&lt;p&gt;Rubocop can be installed in our development environment and used in our day to day basis to check if the code we generate follows the Rubocop's style guide, however this procedure is not as effective as it should, because it depends on the developer to check and fix its code.&lt;/p&gt;

&lt;p&gt;To improve this procedure we could use any of the multiple SAAS that already exists to do checks on our project's Pull Requests, like &lt;a href="https://houndci.com/" rel="noopener noreferrer"&gt;HoundCI&lt;/a&gt;, but if we already have a continuous integration system configured we won't need to delegate these checks to any new SAAS.&lt;/p&gt;

&lt;p&gt;We can reuse our &lt;a href="https://circleci.com/" rel="noopener noreferrer"&gt;CircleCI&lt;/a&gt; or &lt;a href="https://travis-ci.org/" rel="noopener noreferrer"&gt;TravisCI&lt;/a&gt; (or the CI system of your choice) to launch our custom command of static analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pronto
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/prontolabs/pronto" rel="noopener noreferrer"&gt;Pronto&lt;/a&gt; is a tool created specifically to check the changes made in a Pull Request, whether it is in Github, Gitlab or Bitbucket. It has many plugins which you can add depending of which aspects of your codebase you want &lt;code&gt;pronto&lt;/code&gt; to check.&lt;/p&gt;

&lt;p&gt;We are going to use a Rails application as test subject, in this case we will check style of our code with Rubocop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration of the Rails application
&lt;/h2&gt;

&lt;p&gt;First we have to install the required dependencies, because we are going to check only styling we need to install &lt;code&gt;pronto&lt;/code&gt; and &lt;code&gt;pronto-rubocop&lt;/code&gt; in &lt;code&gt;development&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt; environments.&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;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'pronto'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'pronto-rubocop'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on we can use pronto cli tool to launch the style check. For example if we add the following Article class to our project and run pronto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_available?&lt;/span&gt;
      &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;publish_on&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;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AuRB1KAhaJS904fxdjFtYWw.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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AuRB1KAhaJS904fxdjFtYWw.gif" alt="example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way we can check the code that we just wrote before committing it, but we want this to be executed automatically, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Analysis in our Pull Requests
&lt;/h2&gt;

&lt;p&gt;The next step is to configure our test build to execute pronto when the build is successful, we can use the hooks that our CI provider gives us.&lt;/p&gt;

&lt;p&gt;In this example we are using TravisCI so we'll modify its configuration file, &lt;code&gt;.travis.yml&lt;/code&gt;, with the following configuration:&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="ss"&gt;language: &lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;
&lt;span class="ss"&gt;cache: &lt;/span&gt;&lt;span class="n"&gt;bundler&lt;/span&gt;
&lt;span class="ss"&gt;rvm:
  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="ss"&gt;after_success:
  &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="no"&gt;PRONTO_PULL_REQUEST_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="no"&gt;TRAVIS_PULL_REQUEST&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt; &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="n"&gt;pronto&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="n"&gt;github_pr&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;With the environment variable &lt;code&gt;TRAVIS_PULL_REQUEST&lt;/code&gt; we will obtain the ID of the Pull Request being analyzed, then pronto will launch and only will check code differences against &lt;code&gt;origin/master&lt;/code&gt;, not the full codebase.&lt;/p&gt;

&lt;p&gt;This time we have configured the build to check style issues only when test build is successful but you can use many different &lt;a href="https://docs.travis-ci.com/user/customizing-the-build/" rel="noopener noreferrer"&gt;hooks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before running to Github to check our Pull Request we have to do one more thing. Pronto needs a specific token to be able to access our Github repository, to create it access your &lt;a href="https://github.com/settings/tokens" rel="noopener noreferrer"&gt;github settings&lt;/a&gt; and generate a new token with only read access, it doesn't need anything else.&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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ArxgTqAJej5DcYt1ynWurDQ.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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ArxgTqAJej5DcYt1ynWurDQ.png" alt="settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have created it, go to your Travis configuration and add the token as an environment variable name &lt;code&gt;PRONTO_GITHUB_ACCESS_TOKEN&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If everything went right you can create a new Pull Request with your changes and check that pronto makes his work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/epergo/pronto_post/pull/1" rel="noopener noreferrer"&gt;This&lt;/a&gt; would be the result with the previous example code:&lt;/p&gt;

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

&lt;p&gt;The landscape of tools at our service to check and improve our code is huge, from styling issues to security and complexity checks.&lt;/p&gt;

&lt;p&gt;Here we have seen a little example of what it can be accomplished, but it can do a lot more, it depends on the necessities of every project and group of developers.&lt;/p&gt;

&lt;p&gt;The important thing is that we checked our code against a set of rules of quality that allows us to spend time on what is important, our business logic.&lt;/p&gt;

</description>
      <category>rubocop</category>
      <category>pronto</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
  </channel>
</rss>
