# Rui on Rails Strong opinions and thoughts on building software and tech. Source: https://rpereira.pt/ Generated: 2026-05-14 --- # Want feedback? Seek it - Date: 2023-01-19 - URL: https://rpereira.pt/leadership/want-feedback-seek-it/ - Tags: growth - Category: Leadership Soliciting criticism is crucial for growth. If you feel you’re not getting enough feedback, send this message to your peers: > Hi, I wanted to reach out and ask for your feedback on my work. 1+ > constructive points would be much appreciated. Thanks! Make this a habit and build a track record of seeking feedback. 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. --- # Setup Docker for Go development with hot reload - Date: 2020-02-15 - URL: https://rpereira.pt/programming/setup-docker-for-go-development/ - Tags: docker, go - Category: Programming For 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. {% highlight go %} // main.go package main import "github.com/gin-gonic/gin" func setupRouter() *gin.Engine { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) return r } func main() { r := setupRouter() r.Run() } {% endhighlight %} ## Setting up Docker We shall continue by creating a Docker image and a docker-compose file to build and start the Gin webserver. {% highlight dockerfile %} # Dockerfile FROM golang:latest RUN mkdir /app WORKDIR /app ADD . /app RUN go get github.com/gin-gonic/gin RUN go build main.go CMD ["./main"] {% endhighlight %} {% highlight yaml %} # docker-compose --- version: "3.7" services: app: build: . image: hot-reloading-app ports: - "8080:8080" volumes: - ./:/app environment: PORT: "8080" {% endhighlight %} The webserver can be started as follows: ``` $ 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 ``` And by sending a `GET /ping` request to the webserver we are expecting to receive a `"pong"`. ``` $ curl http://localhost:8080/ping {"message":"pong"} ``` However, after changing the response string to the `GET /ping` 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. ## Setting up Docker with hot reloading Having to manually rebuild the Docker image every time a change is made to the source code is simply not an option. [CompileDaemon](https://github.com/githubnemo/CompileDaemon), as the name implies, is a daemon for Go that watches all `.go` files in a given directory and invokes `go build` when a modification is detected. We shall proceed by changing the `Dockerfile` to install the `CompileDaemon` package and use it to build and start the webserver. {% highlight dockerfile %} # Dockerfile FROM golang:latest RUN mkdir /app WORKDIR /app ADD . /app RUN go get github.com/githubnemo/CompileDaemon RUN go get github.com/gin-gonic/gin ENTRYPOINT CompileDaemon --build="go build main.go" --command=./main {% endhighlight %} The next step is to rebuild the Docker image and start the server using `docker-compose up`. 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: ``` ... 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 ``` Finally, by sending a `GET /ping` request to the webserver we are expecting *not to* receive a `"pong"` but rather the updated string. ``` $ curl http://localhost:8080/ping {"message":"pong pong pong!"} ``` We have now successfully set up Gin webserver in Docker that automatically reloads changes when files are changed. 🙌 You can grab the final source code from here: [https://gist.github.com/rpereira/c52190d18bc41f2ec016c3f15b059e5f](https://gist.github.com/rpereira/c52190d18bc41f2ec016c3f15b059e5f) --- # Rails caching in RSpec - Date: 2019-12-22 - URL: https://rpereira.pt/programming/rails-caching-in-rspec/ - Tags: testing, rails, rspec - Category: Programming 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. Assuming the default caching configuration for test environment {% highlight ruby %} config.action_controller.perform_caching = false config.cache_store = :null_store {% endhighlight %} the strategy to enable it for individual tests consists of stubbing `Rails.cache` and clearing it before each example, like so: {% highlight ruby %} RSpec.describe User, type: :model do let(:memory_store) { ActiveSupport::Cache.lookup_store(:memory_store) } let(:cache) { Rails.cache } before do allow(Rails).to receive(:cache).and_return(memory_store) Rails.cache.clear end it 'fetches last_activity_at from cache' do user = create(:user) cache_key = "user:last_activity_at:#{user.id}" expect(Rails.cache.exist?(cache_key)).to be_falsy now = Time.now Rails.cache.write(cache_key, now) expect(Rails.cache.read(cache_key)).to eq(now) end end {% endhighlight %} --- # has_many, but with limits in Rails - Date: 2019-11-17 - URL: https://rpereira.pt/programming/has-many-but-with-limits-in-rails/ - Tags: rails - Category: Programming 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. 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 `before_add` 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. {% highlight ruby %} class User < ApplicationRecord belongs_to :organization end {% endhighlight %} {% highlight ruby %} class Organization < ApplicationRecord MAX_USERS_IN_ORGANIZATION = 10 has_many :users, before_add: :check_users_limit private def check_users_limit(_user) raise UserLimitExceeded if users.size >= MAX_USERS_IN_ORGANIZATION end end {% endhighlight %} By causing the `before_add` callback to throw an exception, the user object does not get added to the collection. {% highlight ruby %} class OrganizationTest < ActiveSupport::TestCase test 'user limits for organization' do org = create(:organisation) org.users = create_list(:user, 10) assert_equal 10, org.users.size assert_raises UserLimitExceeded do org.users << create(:user) end end end {% endhighlight %} 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. The following triggers the `before_add` callback: {% highlight ruby %} irb(main):001:0> org = Organization.create(name: "Example") => # irb(main):002:0> 11.times { |i| org.users << User.create(name: "User #{i}") } Traceback (most recent call last): 4: from (irb):3 3: from (irb):3:in `times' 2: from (irb):3:in `block in irb_binding' 1: from app/models/organization.rb:13:in `check_users_limit' UserLimitExceeded (UserLimitExceeded) irb(main):003:0> org.users.size => 10 {% endhighlight %} On the othet hand, the following does not trigger the `before_add` callback: {% highlight ruby %} irb(main):004:0> User.create(name: "John Doe", organization: org) => # irb(main):005:0> org.reload irb(main):006:0> org.users.size => 11 {% endhighlight %} --- # Rails multicolumn unique index allowing null or empty values - Date: 2019-09-14 - URL: https://rpereira.pt/programming/rails-multicolumn-unique-index-allowing-null-or-empty-values/ - Tags: rails, postgresql - Category: Programming A Rails application can make use of [uniqueness validations][rails-uniqueness-docs] 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. 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 `null` or an empty string. In Rails, this can be done as follows: {% highlight ruby %} class IndexUsersOnEmailAndUserDirectoryId add_index :users, [:email, :user_directory_id], unique: true, where: "(email IS NOT NULL) OR (email != '')" end {% endhighlight %} Note that `null` values are not considered equal. [rails-uniqueness-docs]: https://guides.rubyonrails.org/active_record_validations.html#uniqueness --- # Signing your work on Git - Date: 2016-04-09 - URL: https://rpereira.pt/workflow/signing-your-work-on-git/ - Tags: git - Category: Workflow ## Why Is It Important? A strict policy of signing all commits could prevent someone committing as you (perhaps with `GIT_COMMITTER_NAME` and `GIT_COMMITTER_EMAIL`) from fully blaming you for a change. You can verify signatures using `git log`: $ 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 " [ultimate] gpg: aka "Rui Afonso Pereira " [ultimate] Author: Rui Afonso Pereira Date: Sat Apr 9 11:19:40 2016 +0100 This article shows how to use public-key cryptography to sign git commits. ## Getting Started 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 [RFC4880][1], also known as PGP — short for [Pretty Good Privacy][PGP]. We shall start by installing the GnuPG tool. On macOS, you can grab it using Homebrew: brew install gpg2 Furthermore, this binary can be aliased as `gpg`. In other *NIX systems, either `gpg` or `gnupg` 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 `gpg`. [1]: http://www.ietf.org/rfc/rfc4880.txt [PGP]: https://en.wikipedia.org/wiki/Pretty_Good_Privacy ## Make Your Keys 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. __You should never share your private key with anyone, under any circumstances.__ If you don't have an existing keypair, let's proceed by generating one. $ 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? Let's start by pressing `Enter` and accepting `(1) RSA and RSA (default)` as default. RSA keys may be between 1024 and 8192 bits long. What keysize do you want? (2048) You should type `4096` here. Requested keysize is 4096 bits Please specify how long the key should be valid. 0 = key does not expire = key expires in n days w = key expires in n weeks m = key expires in n months y = key expires in n years Key is valid for? (0) I do not want to bother with refreshing my key regularly, so mine never expires. Then, we can move on to the next step. 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 " Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? Now take the time to review the information and press `o` for `Okay`. Finally, GnuPG needs a passphrase to protect the primary and subordinate private keys that you keep in your possession. You need a Passphrase to protect your private key. Enter passphrase: A good passphrase is crucial to the secure use of GnuPG so it should be carefully chosen. ## Adding Identities 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: $ 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 gpg> In this prompt, you can type `help` for more information. To add a new user ID, type `adduid`. gpg> adduid Real name: Rui Afonso Pereira Email address: rap_2@fake.com Comment: You selected this USER-ID: "Rui Afonso Pereira " 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 " 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 [ultimate] (2) Rui Afonso Pereira gpg> save The key now has two UIDs attached. $ gpg --list-keys 6C1EEE05 pub 2048R/6C1EEE05 2016-04-08 uid [ultimate] Rui Afonso Pereira uid [ultimate] Rui Afonso Pereira sub 2048R/8C44BD6A 2016-04-08 ## Exporting a Public Key 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 `6C1EEE05`: $ gpg --armor --export 6C1EEE05 # Prints the GPG key, in ASCII armor format You shall now proceed by [adding the GPG key to your GitHub account](https://help.github.com/articles/adding-a-new-gpg-key-to-your-github-account). ## Signing Commits 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. $ git config --global user.signingkey $ git config --global commit.gpgsign true Furthermore, since we are using the `gpg2` binary on macOS, we should also tell this to Git: $ git config --global gpg.program gpg2 Finally, from now on, every commit will be _automatically_ signed. However, it is still required to insert the passphrase every single time. ## Automatic Commit Signing on macOS 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. We shall start by installing the following binaries: $ brew install gpg-agent pinentry-mac Now navigate to your `GPGHOMEDIR`, which is `$HOME/.gnupg` by default, and add the following to your `gpg.conf` file: ```conf # Uncomment within config (or add this line) use-agent # This silences the "you need a passphrase" message once the passphrase # handling is all set. batch ``` While inside the same directory, let's configure our `gpg-agent.conf`: ```conf # Enables GPG to find gpg-agent use-standard-socket # 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. pinentry-program /usr/local/bin/pinentry-mac ``` Now, it's time for the real trick in this whole process. For GPG to find the `gpg-agent`, the later must be running, and there must be an environment variable pointing GPG to its socket. The following will either start `gpg-agent` or set up the `GPG_AGENT_INFO` variable if it's already running. You should add this script to your `.bash_profile` or `.zprofile` so that it starts for every shell. ```bash if [[ -f ~/.gnupg/.gpg-agent-info ]] && [[ -n "$(pgrep gpg-agent)" ]]; then source ~/.gnupg/.gpg-agent-info export GPG_AGENT_INFO else eval $(gpg-agent --daemon --write-env-file ~/.gnupg/.gpg-agent-info) fi ``` The high level diagram for the automatic signing is thus `git -> gpg -> shell/env variable -> gpg-agent -> pinentry -> keychain`. Automatic commit signing should now be successfully configured. --- # Vim Spell-Checking - Date: 2016-03-25 - URL: https://rpereira.pt/workflow/vim-spell-checking/ - Tags: vim - Category: Workflow ### Spell check your work Since version 7, Vim has the ability to spell check documents on the fly. We can enable this functionality with the following command: :set spell Furthermore, we can also specify a regional variant of a language: :set spelllang=en_us The default `spelllang=en` will allow a word whose spelling is acceptable in any English-speaking region. ### See it in action In the following screenshot, the underlined word is considered a misspelling. ![My helpful screenshot](/assets/images/vim-spell-checking.png) In Normal mode, we can jump backward and forward between misspelled words using `]s` and `]s` commands, respectively. Then, issuing the `z=` 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. ### Spell check for file type 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 `.vim` under `~/.vim/ftplugin` with `setlocal spell` in its content. You can take a look at my [dotfiles](https://github.com/rpereira/dotfiles/tree/master/vim/ftplugin), which contains examples for `gitcommit` and `markdown` files. ### Adding words to the spell file In Normal model, we can add any word to the `spellfile` by cursoring over the desired word and issuing the command `zg`. ---