Jekyll2024-01-20T08:50:45+00:00https://rpereira.pt/feed.xmlRui on RailsStrong opinions and thoughts on building software and tech.Rui Afonso PereiraWant feedback? Seek it2023-01-19T08:30:00+00:002023-01-19T08:30:00+00:00https://rpereira.pt/leadership/want-feedback-seek-it<p>Soliciting criticism is crucial for growth. If you feel you’re not getting
enough feedback, send this message to your peers:</p>
<blockquote>
<p>Hi, I wanted to reach out and ask for your feedback on my work. 1+
constructive points would be much appreciated. Thanks!</p>
</blockquote>
<p>Make this a habit and build a track record of seeking feedback.</p>
<p>Now embrace the discomfort — it’s imperative to listen with intent to understand
and not to react defensively. Focus on understanding the feedback, and use it to
learn and grow. Additionally, to encourage more feedback, demonstrate your
appreciation by making visible changes or showing progress based on the feedback
received.</p>Rui Afonso PereiraSoliciting criticism is crucial for growth. If you feel you’re not getting enough feedback, send this message to your peers:Setup Docker for Go development with hot reload2020-02-15T07:33:13+00:002020-02-15T07:33:13+00:00https://rpereira.pt/programming/setup-docker-for-go-development<p>For the purpose of this article, we shall consider the following Gin webserver
written in Go that responds with <code class="highlighter-rouge">{ "message": "pong" }</code> for a <code class="highlighter-rouge">GET /ping</code>
request.</p>
<figure class="highlight"><pre><code class="language-go" data-lang="go"><span class="c">// main.go</span>
<span class="k">package</span> <span class="n">main</span>
<span class="k">import</span> <span class="s">"github.com/gin-gonic/gin"</span>
<span class="k">func</span> <span class="n">setupRouter</span><span class="p">()</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Engine</span> <span class="p">{</span>
<span class="n">r</span> <span class="o">:=</span> <span class="n">gin</span><span class="o">.</span><span class="n">Default</span><span class="p">()</span>
<span class="n">r</span><span class="o">.</span><span class="n">GET</span><span class="p">(</span><span class="s">"/ping"</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">gin</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">{</span>
<span class="n">c</span><span class="o">.</span><span class="n">JSON</span><span class="p">(</span><span class="m">200</span><span class="p">,</span> <span class="n">gin</span><span class="o">.</span><span class="n">H</span><span class="p">{</span>
<span class="s">"message"</span><span class="o">:</span> <span class="s">"pong"</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="k">return</span> <span class="n">r</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="n">r</span> <span class="o">:=</span> <span class="n">setupRouter</span><span class="p">()</span>
<span class="n">r</span><span class="o">.</span><span class="n">Run</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>
<h2 id="setting-up-docker">Setting up Docker</h2>
<p>We shall continue by creating a Docker image and a docker-compose file to build
and start the Gin webserver.</p>
<figure class="highlight"><pre><code class="language-dockerfile" data-lang="dockerfile"><span class="c"># Dockerfile</span>
<span class="k">FROM</span><span class="s"> golang:latest</span>
<span class="k">RUN </span><span class="nb">mkdir</span> /app
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">ADD</span><span class="s"> . /app</span>
<span class="k">RUN </span>go get github.com/gin-gonic/gin
<span class="k">RUN </span>go build main.go
<span class="k">CMD</span><span class="s"> ["./main"]</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="c1"># docker-compose</span>
<span class="nn">---</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.7"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span>
<span class="na">build</span><span class="pi">:</span> <span class="s">.</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">hot-reloading-app</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">8080:8080"</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./:/app</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">PORT</span><span class="pi">:</span> <span class="s2">"</span><span class="s">8080"</span></code></pre></figure>
<p>The webserver can be started as follows:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker-compose up
Starting app_1 ... done
Attaching to app_1
app_1 | 2020/02/15 08:20:13 Running build command!
app_1 | 2020/02/15 08:20:13 Build ok.
...
app_1 | 2020/02/15 08:20:13 stdout: [GIN-debug] Listening and serving HTTP on :8080
</code></pre></div></div>
<p>And by sending a <code class="highlighter-rouge">GET /ping</code> request to the webserver we are expecting to
receive a <code class="highlighter-rouge">"pong"</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl http://localhost:8080/ping
{"message":"pong"}
</code></pre></div></div>
<p>However, after changing the response string to the <code class="highlighter-rouge">GET /ping</code> request you will
observe that the actual response did not change. This is expected and, because
the Docker image itself did not change, it is still running the most up-to-date
binary that does not include the change made to the response. This effectively
means that the image has to be stopped and rebuilt so that the changes made to
the webserver are applied.</p>
<h2 id="setting-up-docker-with-hot-reloading">Setting up Docker with hot reloading</h2>
<p>Having to manually rebuild the Docker image every time a change is made to the
source code is simply not an option.
<a href="https://github.com/githubnemo/CompileDaemon">CompileDaemon</a>, as the name
implies, is a daemon for Go that watches all <code class="highlighter-rouge">.go</code> files in a given directory
and invokes <code class="highlighter-rouge">go build</code> when a modification is detected.</p>
<p>We shall proceed by changing the <code class="highlighter-rouge">Dockerfile</code> to install the <code class="highlighter-rouge">CompileDaemon</code>
package and use it to build and start the webserver.</p>
<figure class="highlight"><pre><code class="language-dockerfile" data-lang="dockerfile"><span class="c"># Dockerfile</span>
<span class="k">FROM</span><span class="s"> golang:latest</span>
<span class="k">RUN </span><span class="nb">mkdir</span> /app
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">ADD</span><span class="s"> . /app</span>
<span class="k">RUN </span>go get github.com/githubnemo/CompileDaemon
<span class="k">RUN </span>go get github.com/gin-gonic/gin
<span class="k">ENTRYPOINT</span><span class="s"> CompileDaemon --build="go build main.go" --command=./main</span></code></pre></figure>
<p>The next step is to rebuild the Docker image and start the server using
<code class="highlighter-rouge">docker-compose up</code>. Go ahead and verify that the webserver is running as
described in the first paragraph. Now, modify the response to some other string
and observe the Docker image logs:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
api_1 | 2020/02/15 07:24:46 Build ok.
api_1 | 2020/02/15 07:24:46 Hard stopping the current process..
api_1 | 2020/02/15 07:24:46 Restarting the given command.
...
api_1 | 2020/02/15 07:24:46 stdout: [GIN-debug] Listening and serving HTTP on :8080
api_1 | 2020/02/15 07:24:50 stdout: [GIN] 2020/02/15 - 07:24:50 | 200 | 1.015ms | 172.21.0.1 | GET /ping
</code></pre></div></div>
<p>Finally, by sending a <code class="highlighter-rouge">GET /ping</code> request to the webserver we are expecting
<em>not to</em> receive a <code class="highlighter-rouge">"pong"</code> but rather the updated string.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl http://localhost:8080/ping
{"message":"pong pong pong!"}
</code></pre></div></div>
<p>We have now successfully set up Gin webserver in Docker that automatically
reloads changes when files are changed. 🙌</p>
<p>You can grab the final source code from here: <a href="https://gist.github.com/rpereira/c52190d18bc41f2ec016c3f15b059e5f">https://gist.github.com/rpereira/c52190d18bc41f2ec016c3f15b059e5f</a></p>Rui Afonso PereiraFor the purpose of this article, we shall consider the following Gin webserver written in Go that responds with { "message": "pong" } for a GET /ping request.Rails caching in RSpec2019-12-22T12:05:02+00:002019-12-22T12:05:02+00:00https://rpereira.pt/programming/rails-caching-in-rspec<p>By default, caching in a Rails application is only enabled in a production
environment. To be able to test application logic for caching there are at least
two immediate solutions, being (1) enable caching for all tests and (2) enable
cache for individual tests. For this article, we shall consider the latter.</p>
<p>Assuming the default caching configuration for test environment</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">config</span><span class="p">.</span><span class="nf">action_controller</span><span class="p">.</span><span class="nf">perform_caching</span> <span class="o">=</span> <span class="kp">false</span>
<span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:null_store</span></code></pre></figure>
<p>the strategy to enable it for individual tests consists of stubbing
<code class="highlighter-rouge">Rails.cache</code> and clearing it before each example, like so:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="no">RSpec</span><span class="p">.</span><span class="nf">describe</span> <span class="no">User</span><span class="p">,</span> <span class="ss">type: :model</span> <span class="k">do</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:memory_store</span><span class="p">)</span> <span class="p">{</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">Cache</span><span class="p">.</span><span class="nf">lookup_store</span><span class="p">(</span><span class="ss">:memory_store</span><span class="p">)</span> <span class="p">}</span>
<span class="n">let</span><span class="p">(</span><span class="ss">:cache</span><span class="p">)</span> <span class="p">{</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span> <span class="p">}</span>
<span class="n">before</span> <span class="k">do</span>
<span class="n">allow</span><span class="p">(</span><span class="no">Rails</span><span class="p">).</span><span class="nf">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:cache</span><span class="p">).</span><span class="nf">and_return</span><span class="p">(</span><span class="n">memory_store</span><span class="p">)</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">clear</span>
<span class="k">end</span>
<span class="n">it</span> <span class="s1">'fetches last_activity_at from cache'</span> <span class="k">do</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">create</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span>
<span class="n">cache_key</span> <span class="o">=</span> <span class="s2">"user:last_activity_at:</span><span class="si">#{</span><span class="n">user</span><span class="p">.</span><span class="nf">id</span><span class="si">}</span><span class="s2">"</span>
<span class="n">expect</span><span class="p">(</span><span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">exist?</span><span class="p">(</span><span class="n">cache_key</span><span class="p">)).</span><span class="nf">to</span> <span class="n">be_falsy</span>
<span class="n">now</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">cache_key</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
<span class="n">expect</span><span class="p">(</span><span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">cache_key</span><span class="p">)).</span><span class="nf">to</span> <span class="n">eq</span><span class="p">(</span><span class="n">now</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>Rui Afonso PereiraBy default, caching in a Rails application is only enabled in a production environment. To be able to test application logic for caching there are at least two immediate solutions, being (1) enable caching for all tests and (2) enable cache for individual tests. For this article, we shall consider the latter.has_many, but with limits in Rails2019-11-17T08:50:34+00:002019-11-17T08:50:34+00:00https://rpereira.pt/programming/has-many-but-with-limits-in-rails<p>For the purpose of this article, we shall consider a Rails application that
includes a model for organizations and a model for users. Each organization can
have many users. Furthermore, we shall limit the number of users and
organization can have.</p>
<p>There are a handful of strategies to accomplish this, but today let’s explore
how association callbacks can solve the situation described above. These
callbacks hook into the life cycle of Active Record objects, allowing to work
with those objects at various points. More specifically, the <code class="highlighter-rouge">before_add</code>
callback can be used to ensure the number of users in an organization is bellow
the limit, preventing the object from being saved to the database if not.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">belongs_to</span> <span class="ss">:organization</span>
<span class="k">end</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">Organization</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="no">MAX_USERS_IN_ORGANIZATION</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">has_many</span> <span class="ss">:users</span><span class="p">,</span> <span class="ss">before_add: :check_users_limit</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">check_users_limit</span><span class="p">(</span><span class="n">_user</span><span class="p">)</span>
<span class="k">raise</span> <span class="no">UserLimitExceeded</span> <span class="k">if</span> <span class="n">users</span><span class="p">.</span><span class="nf">size</span> <span class="o">>=</span> <span class="no">MAX_USERS_IN_ORGANIZATION</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>By causing the <code class="highlighter-rouge">before_add</code> callback to throw an exception, the user object does
not get added to the collection.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">OrganizationTest</span> <span class="o"><</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">TestCase</span>
<span class="nb">test</span> <span class="s1">'user limits for organization'</span> <span class="k">do</span>
<span class="n">org</span> <span class="o">=</span> <span class="n">create</span><span class="p">(</span><span class="ss">:organisation</span><span class="p">)</span>
<span class="n">org</span><span class="p">.</span><span class="nf">users</span> <span class="o">=</span> <span class="n">create_list</span><span class="p">(</span><span class="ss">:user</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">assert_equal</span> <span class="mi">10</span><span class="p">,</span> <span class="n">org</span><span class="p">.</span><span class="nf">users</span><span class="p">.</span><span class="nf">size</span>
<span class="n">assert_raises</span> <span class="no">UserLimitExceeded</span> <span class="k">do</span>
<span class="n">org</span><span class="p">.</span><span class="nf">users</span> <span class="o"><<</span> <span class="n">create</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>However, this approach comes with a caveat. As association callbacks are
triggered by events in the life cycle of a collection, these are called only
when the associated objects are added or removed through the association
collection.</p>
<p>The following triggers the <code class="highlighter-rouge">before_add</code> callback:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">001</span><span class="p">:</span><span class="mi">0</span><span class="o">></span> <span class="n">org</span> <span class="o">=</span> <span class="no">Organization</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">name: </span><span class="s2">"Example"</span><span class="p">)</span>
<span class="o">=></span> <span class="c1">#<Organization id: 1, name: "Example", created_at: "2019-11-17 10:33:45", updated_at: "2019-11-17 10:33:45"></span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">002</span><span class="p">:</span><span class="mi">0</span><span class="o">></span> <span class="mi">11</span><span class="p">.</span><span class="nf">times</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">org</span><span class="p">.</span><span class="nf">users</span> <span class="o"><<</span> <span class="no">User</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">name: </span><span class="s2">"User </span><span class="si">#{</span><span class="n">i</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="p">}</span>
<span class="no">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
<span class="mi">4</span><span class="p">:</span> <span class="n">from</span> <span class="p">(</span><span class="n">irb</span><span class="p">):</span><span class="mi">3</span>
<span class="mi">3</span><span class="p">:</span> <span class="n">from</span> <span class="p">(</span><span class="n">irb</span><span class="p">):</span><span class="mi">3</span><span class="ss">:in</span> <span class="sb">`times'
2: from (irb):3:in `</span><span class="n">block</span> <span class="k">in</span> <span class="n">irb_binding</span><span class="s1">'
1: from app/models/organization.rb:13:in `check_users_limit'</span>
<span class="no">UserLimitExceeded</span> <span class="p">(</span><span class="no">UserLimitExceeded</span><span class="p">)</span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">003</span><span class="p">:</span><span class="mi">0</span><span class="o">></span> <span class="n">org</span><span class="p">.</span><span class="nf">users</span><span class="p">.</span><span class="nf">size</span>
<span class="o">=></span> <span class="mi">10</span></code></pre></figure>
<p>On the othet hand, the following does not trigger the <code class="highlighter-rouge">before_add</code> callback:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">004</span><span class="p">:</span><span class="mi">0</span><span class="o">></span> <span class="no">User</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">name: </span><span class="s2">"John Doe"</span><span class="p">,</span> <span class="ss">organization: </span><span class="n">org</span><span class="p">)</span>
<span class="o">=></span> <span class="c1">#<User id: 11, organization_id: 1, name: "John Doe", email: nil, created_at: "2019-11-17 10:37:28", updated_at: "2019-11-17 10:37:28"></span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">005</span><span class="p">:</span><span class="mi">0</span><span class="o">></span> <span class="n">org</span><span class="p">.</span><span class="nf">reload</span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">):</span><span class="mo">006</span><span class="p">:</span><span class="mi">0</span><span class="o">></span> <span class="n">org</span><span class="p">.</span><span class="nf">users</span><span class="p">.</span><span class="nf">size</span>
<span class="o">=></span> <span class="mi">11</span></code></pre></figure>Rui Afonso PereiraFor the purpose of this article, we shall consider a Rails application that includes a model for organizations and a model for users. Each organization can have many users. Furthermore, we shall limit the number of users and organization can have.Rails multicolumn unique index allowing null or empty values2019-09-14T09:01:17+00:002019-09-14T09:01:17+00:00https://rpereira.pt/programming/rails-multicolumn-unique-index-allowing-null-or-empty-values<p>A Rails application can make use of <a href="https://guides.rubyonrails.org/active_record_validations.html#uniqueness">uniqueness
validations</a> to detect duplicated records. However, this
is not enough to ensure data integrity. Constraining the values allowed by your
application at the database-level, rather than at the application-level, is a
more robust way of ensuring your data stays sane.</p>
<p>Database indexes can be used to enforce uniqueness of a column’s value, or the
uniqueness of the combined values of more than one column. It might be the case
that the requirements at hand specify that one of the columns can be <code class="highlighter-rouge">null</code> or
an empty string.</p>
<p>In Rails, this can be done as follows:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">class</span> <span class="nc">IndexUsersOnEmailAndUserDirectoryId</span>
<span class="n">add_index</span> <span class="ss">:users</span><span class="p">,</span> <span class="p">[</span><span class="ss">:email</span><span class="p">,</span> <span class="ss">:user_directory_id</span><span class="p">],</span>
<span class="ss">unique: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">where: </span><span class="s2">"(email IS NOT NULL) OR (email != '')"</span>
<span class="k">end</span></code></pre></figure>
<p>Note that <code class="highlighter-rouge">null</code> values are not considered equal.</p>Rui Afonso PereiraA Rails application can make use of uniqueness validations to detect duplicated records. However, this is not enough to ensure data integrity. Constraining the values allowed by your application at the database-level, rather than at the application-level, is a more robust way of ensuring your data stays sane.Signing your work on Git2016-04-09T09:56:09+00:002016-04-09T09:56:09+00:00https://rpereira.pt/workflow/signing-your-work-on-git<h2 id="why-is-it-important">Why Is It Important?</h2>
<p>A strict policy of signing all commits could prevent someone committing as you
(perhaps with <code class="highlighter-rouge">GIT_COMMITTER_NAME</code> and <code class="highlighter-rouge">GIT_COMMITTER_EMAIL</code>) from fully blaming
you for a change. You can verify signatures using <code class="highlighter-rouge">git log</code>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log --show-signature
commit e80c6f611429db4e437a377d7b1b76c167594dcd
gpg: Signature made Sat Apr 9 11:19:58 2016 WEST using RSA key ID 6C1EEE05
gpg: Good signature from "Rui Afonso Pereira <rap_2@fake.com>" [ultimate]
gpg: aka "Rui Afonso Pereira <rap@fake.com>" [ultimate]
Author: Rui Afonso Pereira <rap@fake.com>
Date: Sat Apr 9 11:19:40 2016 +0100
</code></pre></div></div>
<p>This article shows how to use public-key cryptography to sign git commits.</p>
<h2 id="getting-started">Getting Started</h2>
<p>This article relies on GNU Privacy Guard (GnuPG), which is a tool for secure
communication. It is a complete and free implementation of the OpenPGP
standard as defined by <a href="http://www.ietf.org/rfc/rfc4880.txt">RFC4880</a>, also known as PGP — short for <a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy">Pretty Good
Privacy</a>.</p>
<p>We shall start by installing the GnuPG tool. On macOS, you can grab it using
Homebrew:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install gpg2
</code></pre></div></div>
<p>Furthermore, this binary can be aliased as <code class="highlighter-rouge">gpg</code>.</p>
<p>In other *NIX systems, either <code class="highlighter-rouge">gpg</code> or <code class="highlighter-rouge">gnupg</code> is likely already installed. From
this point on, due to the different ways that systems refer to the binary, I’m
going to address it as <code class="highlighter-rouge">gpg</code>.</p>
<h2 id="make-your-keys">Make Your Keys</h2>
<p>To use the GnuPG system, you’ll need a public key and a private key, also known
together as a keypair. A public key is available to many, whereas the private
key must be kept secret. <strong>You should never share your private key with anyone,
under any circumstances.</strong></p>
<p>If you don’t have an existing keypair, let’s proceed by generating one.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gpg --gen-key
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection?
</code></pre></div></div>
<p>Let’s start by pressing <code class="highlighter-rouge">Enter</code> and accepting <code class="highlighter-rouge">(1) RSA and RSA (default)</code> as
default.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RSA keys may be between 1024 and 8192 bits long.
What keysize do you want? (2048)
</code></pre></div></div>
<p>You should type <code class="highlighter-rouge">4096</code> here.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Requested keysize is 4096 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
</code></pre></div></div>
<p>I do not want to bother with refreshing my key regularly, so mine never expires.</p>
<p>Then, we can move on to the next step.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GnuPG needs to construct a user ID to identify your key.
Real name: Rui Afonso Pereira
Email address: rap@fake.com
Comment:
You selected this USER-ID:
"Rui Afonso Pereira <rap@fake.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?
</code></pre></div></div>
<p>Now take the time to review the information and press <code class="highlighter-rouge">o</code> for <code class="highlighter-rouge">Okay</code>.</p>
<p>Finally, GnuPG needs a passphrase to protect the primary and subordinate private
keys that you keep in your possession.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You need a Passphrase to protect your private key.
Enter passphrase:
</code></pre></div></div>
<p>A good passphrase is crucial to the secure use of GnuPG so it should be
carefully chosen.</p>
<h2 id="adding-identities">Adding Identities</h2>
<p>We shall consider having multiple email addresses: a work email and a personal
email. It would be tedious if we had to go through this whole process for each.
You can easily edit your key to add another user ID:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gpg --edit-key rap@fake.com
Secret key is available.
pub 2048R/6C1EEE05 created: 2016-04-08 expires: never usage: SC
trust: ultimate validity: ultimate
sub 2048R/8C44BD6A created: 2016-04-08 expires: never usage: E
[ultimate] (1) Rui Afonso Pereira <rap@fake.com>
gpg>
</code></pre></div></div>
<p>In this prompt, you can type <code class="highlighter-rouge">help</code> for more information. To add a new user ID,
type <code class="highlighter-rouge">adduid</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg> adduid
Real name: Rui Afonso Pereira
Email address: rap_2@fake.com
Comment:
You selected this USER-ID:
"Rui Afonso Pereira <rap_2@fake.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a passphrase to unlock the secret key for
user: "Rui Afonso Pereira <rap@fake.com>"
2048-bit RSA key, ID 6C1EEE05, created 2016-04-08
pub 2048R/6C1EEE05 created: 2016-04-08 expires: never usage: SC
trust: ultimate validity: ultimate
sub 2048R/8C44BD6A created: 2016-04-08 expires: never usage: E
[ultimate] (1). Rui Afonso Pereira <rap_2@fake.com>
[ultimate] (2) Rui Afonso Pereira <rap@fake.com>
gpg> save
</code></pre></div></div>
<p>The key now has two UIDs attached.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gpg --list-keys 6C1EEE05
pub 2048R/6C1EEE05 2016-04-08
uid [ultimate] Rui Afonso Pereira <rap_2@fake.com>
uid [ultimate] Rui Afonso Pereira <rap@fake.com>
sub 2048R/8C44BD6A 2016-04-08
</code></pre></div></div>
<h2 id="exporting-a-public-key">Exporting a Public Key</h2>
<p>To add your public key to your development platform, such as GitHub, you must
first export it. To do so, paste the text below, substituting in the GPG key ID
you’d like to use. In this example, the GPG key ID is <code class="highlighter-rouge">6C1EEE05</code>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gpg --armor --export 6C1EEE05
# Prints the GPG key, in ASCII armor format
</code></pre></div></div>
<p>You shall now proceed by <a href="https://help.github.com/articles/adding-a-new-gpg-key-to-your-github-account">adding the GPG key to your GitHub
account</a>.</p>
<h2 id="signing-commits">Signing Commits</h2>
<p>We should now configure git to automatically gpgsign commits. This consists of
pointing git to your signing key ID, and then enabling automatic signing of
git commits.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git config --global user.signingkey <YOUR-SIGNING-KEY-PUB-ID>
$ git config --global commit.gpgsign true
</code></pre></div></div>
<p>Furthermore, since we are using the <code class="highlighter-rouge">gpg2</code> binary on macOS, we should also tell
this to Git:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git config --global gpg.program gpg2
</code></pre></div></div>
<p>Finally, from now on, every commit will be <em>automatically</em> signed. However, it
is still required to insert the passphrase every single time.</p>
<h2 id="automatic-commit-signing-on-macos">Automatic Commit Signing on macOS</h2>
<p>For security reasons, GnuPG always requires the passphrase every time it needs
to sign something. This effectively means that the passphrase is required on
every single git commit. If this sounds like a lot of work to you, we can
automate the process, in a clear trade-off between security and convenience.</p>
<p>We shall start by installing the following binaries:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install gpg-agent pinentry-mac
</code></pre></div></div>
<p>Now navigate to your <code class="highlighter-rouge">GPGHOMEDIR</code>, which is <code class="highlighter-rouge">$HOME/.gnupg</code> by default, and add
the following to your <code class="highlighter-rouge">gpg.conf</code> file:</p>
<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Uncomment within config (or add this line)
</span><span class="n">use</span>-<span class="n">agent</span>
<span class="c"># This silences the "you need a passphrase" message once the passphrase
# handling is all set.
</span><span class="n">batch</span>
</code></pre></div></div>
<p>While inside the same directory, let’s configure our <code class="highlighter-rouge">gpg-agent.conf</code>:</p>
<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Enables GPG to find gpg-agent
</span><span class="n">use</span>-<span class="n">standard</span>-<span class="n">socket</span>
<span class="c"># Connects gpg-agent to the macOS keychain via the brew-installed
# pinentry program from GPGtools. This is the macOS magic, allowing
# the gpg key's passphrase to be stored in the login keychain,
# enabling automatic key signing.
</span><span class="n">pinentry</span>-<span class="n">program</span> /<span class="n">usr</span>/<span class="n">local</span>/<span class="n">bin</span>/<span class="n">pinentry</span>-<span class="n">mac</span>
</code></pre></div></div>
<p>Now, it’s time for the real trick in this whole process. For GPG to find the
<code class="highlighter-rouge">gpg-agent</code>, the later must be running, and there must be an environment
variable pointing GPG to its socket. The following will either start <code class="highlighter-rouge">gpg-agent</code>
or set up the <code class="highlighter-rouge">GPG_AGENT_INFO</code> variable if it’s already running. You should add
this script to your <code class="highlighter-rouge">.bash_profile</code> or <code class="highlighter-rouge">.zprofile</code> so that it starts for every
shell.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">[[</span> <span class="nt">-f</span> ~/.gnupg/.gpg-agent-info <span class="o">]]</span> <span class="o">&&</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="si">$(</span>pgrep gpg-agent<span class="si">)</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">source</span> ~/.gnupg/.gpg-agent-info
<span class="nb">export </span>GPG_AGENT_INFO
<span class="k">else
</span><span class="nb">eval</span> <span class="si">$(</span>gpg-agent <span class="nt">--daemon</span> <span class="nt">--write-env-file</span> ~/.gnupg/.gpg-agent-info<span class="si">)</span>
<span class="k">fi</span>
</code></pre></div></div>
<p>The high level diagram for the automatic signing is thus <code class="highlighter-rouge">git -> gpg ->
shell/env variable -> gpg-agent -> pinentry -> keychain</code>.</p>
<p>Automatic commit signing should now be successfully configured.</p>Rui Afonso PereiraWhy Is It Important?Vim Spell-Checking2016-03-25T01:53:33+00:002016-03-25T01:53:33+00:00https://rpereira.pt/workflow/vim-spell-checking<h3 id="spell-check-your-work">Spell check your work</h3>
<p>Since version 7, Vim has the ability to spell check documents on the fly. We can
enable this functionality with the following command:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:set spell
</code></pre></div></div>
<p>Furthermore, we can also specify a regional variant of a language:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:set spelllang=en_us
</code></pre></div></div>
<p>The default <code class="highlighter-rouge">spelllang=en</code> will allow a word whose spelling is acceptable in any
English-speaking region.</p>
<h3 id="see-it-in-action">See it in action</h3>
<p>In the following screenshot, the underlined word is considered a misspelling.</p>
<p><img src="/assets/images/vim-spell-checking.png" alt="My helpful screenshot" /></p>
<p>In Normal mode, we can jump backward and forward between misspelled words using
<code class="highlighter-rouge">]s</code> and <code class="highlighter-rouge">]s</code> commands, respectively. Then, issuing the <code class="highlighter-rouge">z=</code> command, we
instruct Vim to suggest a list of correctly spelled words for the word
under/after the cursor. The prompt at the bottom of the screen advises us to
insert the index of the word we want to use in place of the misspelled word.</p>
<h3 id="spell-check-for-file-type">Spell check for file type</h3>
<p>Instead of manually turn on the spell checker each time we need it, we can turn
it on based on the file’s extension. My preferred way of accomplish that is
creating a file whose name is <code class="highlighter-rouge"><filetype>.vim</code> under <code class="highlighter-rouge">~/.vim/ftplugin</code> with
<code class="highlighter-rouge">setlocal spell</code> in its content. You can take a look at my
<a href="https://github.com/rpereira/dotfiles/tree/master/vim/ftplugin">dotfiles</a>, which
contains examples for <code class="highlighter-rouge">gitcommit</code> and <code class="highlighter-rouge">markdown</code> files.</p>
<h3 id="adding-words-to-the-spell-file">Adding words to the spell file</h3>
<p>In Normal model, we can add any word to the <code class="highlighter-rouge">spellfile</code> by cursoring over the
desired word and issuing the command <code class="highlighter-rouge">zg</code>.</p>Rui Afonso PereiraSpell check your work