In RSpec, some matchers can take another matcher as an argument. It is sometimes useful for writing an expectation for a collection. Let’s say we want to test that an array contains a string that includes a particular text. We can do that by combining two matchers: include and match as follows.
expect(an_array).to include(match(/Lorem/)) # Of course we also could do: # expect(an_array.grep(/Lorem/)).not_to be_empty # or # expect(an_array).to be_any {|item| /Lorem/ === item } I think that in this case, the expectation with include and match is much more concise than the commented-out alternative ways above.
Read more →
Sometimes I want to create a tiny one-off script that intended to run on a sandbox environment like a Docker container and needs only one file. Most of the time, I also need test frameworks to do some refactoring. It can be done with bundler/inline and rspec/autorun.
require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'rspec' end def doit 'return a string' end require 'rspec/autorun' RSpec.describe '#doit' do example do expect(doit).to be_a(String) end end Usually, Bundler reads the dependencies from Gemfile. However, we can forgo this file by using gemfile block instead. As running the script, Bundler will install gems that are required by the block but not installed in the system. So the initial run may take a little longer time for installation. Note that you cannot place require 'rspec/autorun' before the gemfile block since some of the dependencies might not be installed yet before that.
Read more →
Today I learned that RSpec’s #to method takes the 2nd argument as its custom failing-message.
expect(actual_value).to eq(expected_value), message Let’s say we are going to test a JSON object. The following example expects a part of the JSON object response_json['eye_colour'] to be "blue".
require 'net/https' require 'uri' require 'json' RSpec.describe do example do response = Net::HTTP.get_response(URI('https://swapi.dev/api/people/1/')) response_json = JSON.parse(response.body) expect(response_json['eye_colour']).to eq('blue') end end Unfortunately, this test fails and displays messages like this:
Failures: 1) is expected to eq "blue" Failure/Error: expect(response_json['eye_colour']).to eq('blue') expected: "blue" got: nil (compared using ==) From this output, we could guess either response_json['eye_colour'] is set to nil or the key eye_colour is not defined. However, there is no more information here. To find out the cause of the error, an additional print-debug would be needed.
Read more →
Today I learned that Rails 6.1.3 does not have methods that raise exceptions when more than one record was found. However, in future versions, we could use ActiveRecord::FinderMethods#sole for that purpose. It seems Enumerable#sole is also going to be introduced.
Sometimes I need to make sure there is one and only one record that matches a condition without using a unique-index, for example, when I cannot add that constraint to the database.
Read more →
Today I learned/remembered that to format a number with a delimiter, we can use ActiveSupport::NumericWithFormat#to_s(:delimited).
require 'active_support' require 'active_support/core_ext' 123456789.to_s(:delimited) # => "123,456,789" Not only that, but this method also provides other formats and takes options to tweak its behavior.
123456789.to_s(:delimited, delimiter: '-') # => "123-456-789" 123456789.to_s(:currency, precision: 3) # => "$123,456,789.000" 123456789.to_s(:human_size) # => "118 MB" 123456789.to_s(:human) # => "123 Million" Originally, when I need to format a number to a delimited number, I would try to use number_to_delimited provided by ActiveSupport::NumberHelper. But to_s is handy when we have to do that where the helper is not reachable by default, such as in a serializer class for ActiveModel Serializer. Calling this method passing a format is the same as calling ActiveSupport::NumberHelper.number_to_#{format}.
Read more →
When we have to process a large number of files, we should be aware of the resources the process consumes. Here’s the scenario I faced in my work recently: we have to create one archive file from lots of files that are stored on an S3 bucket. Created archive files must be placed on another S3 bucket. The number of files of an archive could be 700,000. The size of each file is up to 10MiB. The size of an archive file will be less than 140GiB.
Read more →
Stripe provides an easy way to implement subscription services. This document explains how Stripe’s subscription works and the things you can. They also provide an example to implement fixed-price subscriptions, like Netflix. You can try the example repository on GitHub and see the whole lifecycle of a subscription. Actually, you don’t have to even set up any environment for the example app if you have Docker on your computer. The only dependency is Docker.
Read more →
Sometimes I want to create a new gem or modify existing gems to extend/fix them. However, I do not want to install tons of additional development tools on my host machine for that. Recently, I created a shell script which creates an instant development environment on Docker for that.
#!/bin/sh -e exec docker run --rm -it \ --workdir=/work \ -v $(pwd):/work \ -v=$HOME/.gitconfig:/root/.gitconfig \ -v=$HOME/.ssh:/root/.ssh \ -v=$HOME/.gem/credentials:/root/.gem/credentials \ ruby:${VERSION:-2.6.5} \ /bin/bash "$@" This is a deadly-simple wrapper for docker command to run a ruby container mounting minimum-required files. Just put this file under a directory whatever it is in $PATH list (For example, /usr/local/bin/rubyshell) and run rubyshell in a Ruby project. Then I can do Ruby things such as bundle.
Read more →
Active Job adapter interface I’ve created an Active Job adapter to run my background jobs on Google Cloud Run via Google Cloud Tasks. Although it is the first time for me to create an adapter for Active Job, this is working very well so far.
https://github.com/esminc/activejob-google_cloud_tasks-http
Creating an Active Job adapter itself is not so hard. You can see that by exploring the repository above. In fact, Active Job adapters are required to implement only two methods: enqueue and enqueue_at. For example, let’s take a look at the implementation of Active Job Inline, which is one of the built-in implementations of Active Job.
Read more →
Recently, I had migrated from GCM (Google Cloud Messaging) to FCM (Firebase Cloud Messaging) because GCM was ending its life.
At first, I found fcm gem which supports FCM. I changed my application code to use the gem and I deployed the new version since it was working nicely on the staging. However, as time goes, I realized that FCM notification sometimes stops working when the application runs on multiple threads. And it’s turned out that the latest released version of fcm gem does not support multi-threaded applications as of today. That’s why I decided to use another library and that’s where Andpush gem comes into play.
Read more →
I’m developing a small application which is built on Ruby on Rails, React and Relay. Since the project is based on my personal hobby and just a prototype, I preferred to write application codes rather than to write tests at first. However, as the application grows larger, I had to test the application manually every time when I make changes to make sure it doesn’t break other features. The lack of tests made development speed slower. So I decided to write automated tests.
Read more →