RSpec: Executing specs by example id (or "nesting index")
There are several ways to run a single spec. I usually copy the spec file path with the line number of the example and pass it to the RSpec binary: bin/rspec spec/models/user_spec.rb:30 (multiple line numbers work as well: :30:36:68). Another is to tag the example with focus: true or to run the example by matching its name.
In this card I'd like to ...
Why preloading associations "randomly" uses joined tables or multiple queries
ActiveRecord gives you the :include option to load records and their associations in a fixed number of queries. This is called preloading or eager loading associations. By preloading associations you can prevent the n+1 query problem that slows down a many index view.
You might have noticed that using :include randomly seems to do one of the following:
- Execute one query per involved table with a condit...
What we know about PDFKit
What PDFKit is
- PDFKit converts a web page to a PDF document. It uses a Webkit engine under the hood.
- For you as a web developer this means you can keep using the technology you are familar with and don't need to learn LaTeX. All you need is a pretty print-stylesheet.
How to use it from your Rails application
- You can have PDFKit render a website by simply calling PDFKit.new('http://google.com').to_file('google.pdf'). You can then send the...
How to simulate limited bandwidth in Google Chrome and Firefox
Your development machine is usually on a very good network connection.
To test how your application behaves on a slow network (e.g. mobile), you can simulate limited bandwidth.
Chrome
- Open the dev tools (Ctrl+Shift+I or F12) and switch to the "Network" tab
- In the row below the dev tool tabs, there's a throttling dropdown which reads "Online" by default.
- Inside the dropdown, you will find a few presets and an option to add your own download/upload/latency settings.
Firefox
- Open the dev tools (Ctrl+Shift+I or F12) and switc...
Rails: Example on how to extract domain independent code from the `app/models` folder to the `lib/` folder
This cards describes an example with a Github Client on how to keep your Rails application more maintainable by extracting domain independent code from the app/models folder to the lib/ folder. The approach is applicable to arbitrary scenarios and not limited to API clients.
Example
Let's say we have a Rails application that synchronizes its users with the Github API:
.
└── app
    └── models
        ├── user
        │   ├── github_client.rb
        │   └── sychronizer.rb
        └── user.rb
In this example the app folder ...
RSpec's hash_including matcher does not support nesting
You can not use the hash_including argument matcher with a nested hash:
describe 'user' do
  let(:user) { {id: 1, name: 'Foo', thread: {id: 1, title: 'Bar'} }
  it do 
    expect(user).to match(
      hash_including(
        id: 1, thread: {id: 1}
      )
    )
  end
end  
The example will fail and returns a not very helpful error message:
expected {:id => 1, :name => "Foo", :thread => {:id => 1, :title => "Bar"}} to...
Rails: Rest API post-mortem analysis
This is a personal post-mortem analysis of a project that was mainly build to provide a REST API to mobile clients.
For the API backend we used the following components:
- Active Model Serializer (AMS) to serializer our Active Record models to JSON.
- JSON Schema to test the responses of our server.
- SwaggerUI to document the API.
It worked
The concept worked really good. Here are two points that were extraordinary compared to normal Rails project with many UI components:
- Having a Rails application, that has no UI components (only...
How to evaluate CSS media queries in JavaScript
To make CSS rules dependent on the screen size, we use media queries:
@media (max-width: 500px) {
  // rules for screen widths of 500px or smaller
}
Browsers will automatically enable and disable the conditional rules as the screen width changes.
To detect responsive breakpoints from JavaScript, you may use the global matchMedia() function. It  is supported in all brow...
Sass: How to get rid of deprecation warnings in dependencies
sass >= 1.35.0 has the option quietDeps and silenceDeprecations to silence deprecation warnings from dependencies.
- 
quietDeps: No deprecation warnings for dependencies e.g. Bootstrap
- 
silenceDeprecations: Allows to set a list of deprecation types you want to silence for your own code
Below there are a few examples for different build tools how to set the Sass options.
Webpacker
const sassLoaderConfig = environment.loaders.get('sass')
const...
ActiveType::Object: Be careful when overriding the initialize method
Background:
ActiveType::Object inherits from ActiveRecod::Base and is designed to behave like an ActiveRecord Object, just without the database persistence.
Don't remove any of the default behavior of the initialize method!
If you have a class which inherits from ActiveType::Object and you need to override the #initialize method, then you should be really careful:
- Always pass exactly one attribute. ActiveRecod::Baseobjects really want to get their arguments processable as keyword arguments. Don't change the syntax, or y...
Use CSS "text-overflow" to truncate long texts
When using Rails to truncate strings, you may end up with strings that are still too long for their container or are not as long as they could be. You can get a prettier result using stylesheets.
The CSS property text-overflow: ellipsis has been around for quite a long time now but since Firefox did not support it for ages, you did not use it. Since Firefox 7 you can!
Note that this only works for single-line texts. If you want to truncate tests across multiple lines, use a JavaScript solution like...
Simple database lock for MySQL
Note: For PostgreSQL you should use advisory locks. For MySQL we still recommend the solution in this card.
If you need to synchronize multiple rails processes, you need some shared resource that can be used as a mutex. One option is to simply use your existing (MySQL) database.
The attached code provides a database-based model level mutex for MySQL. You use it by simply calling
Lock.acquire('string to synchronize on') do
  # non-th...
How to run a small web server (one-liner)
Sometimes you just want to have a small web server that serves files to test something.
Serve the current directory
On Ruby 1.9.2+ you can do the following ("." for current directory). You might need to gem install webrick on modern Rubies.
ruby -run -ehttpd . -p8000
Python 2.x offers a similar way.
python -m SimpleHTTPServer 8000 .
This is the same way with Python 3.x
python -m http.server
In both cases your web server is single-threaded and will block when large files are being downloaded from you.
WEBrick ...
Capybara: Pretending to interact with the document
Browsers blocks abusable JavaScript API calls until the user has interacted with the document. Examples would be opening new tab or start playing video or audio.
E.g. if you attempt to call video.play() in a test, the call will reject with a message like this:
NotAllowedError: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD
Workaround
To pretend document interaction in a test you can create an element, click on it, and remove the element again. This unblocks the entire JavaSc...
How to: Upgrade CarrierWave to 3.x
While upgrading CarrierWave from version 0.11.x to 3.x, we encountered some very nasty fails. Below are the basic changes you need to perform and some behavior you may eventually run into when upgrading your application. This aims to save you some time understanding what happens under the hood to possibly discover problems faster as digging deeply into CarrierWave code is very fun...
Whitelists and blacklists
The following focuses on extension allowlisting, but it is the exact same thing for content type allowlisting with the `content_ty...
Automatic Log Rotation in Rails
Rails log files rotate automatically when they reach approx. 100MB:
$ ls -lh log/
-rw-r--r-- 1 user group  55M Sep 15 09:54 development.log
-rw-r--r-- 1 user group 101M Aug 22 13:45 development.log.0
This behavior is a built-in feature of Ruby's standard Logger class, which Rails uses by default.
To control the maximum file size, set config.log_file_size in yo...
Switch to a recently opened tab with Cucumber
Similar to closing an opened browser window, spreewald now supports the I switch to the new browser tab step.
Info
See the Spreewald README for more cool features.
You can use it to test links that were opened with a link_to(..., :target => '_blank') link or other ways that create new tabs or windows.
Important
This only works with
Selenium...
Bash script to list git commits by Linear ID
As we're switching from PT to Linear, I've updated the existing bash script to work for commits that are referencing Linear IDs.
A core benefit of our convention to prefix commits by their corresponding issue ID is that we can easily detect commits that belong to the same issue. You can either do that manually or use the bash script below. It can either be placed in your .bashrc or a...
Cucumber step to match table rows with Capybara
These steps are now part of Spreewald.
This note describes a Cucumber step that lets you write this:
Then I should see a table with the following rows:
  | Bruce Wayne       | Employee    | 1972 |
  | Harleen Quinzel   | HR          | 1982 |
  | Alfred Pennyworth | Engineering | 1943 |
If there are additional columns or rows in the table that are not explicitely expected, the step won't complain. It does however expect the rows to be ordered as stat...
Creating a Rails application in a single file
Greg Molnar has written a neat article about creating a single-file Rails app.
This is not meant for production use but can be useful to try things out, e.g. when hunting down a bug or embedding a Rails app into the tests of a gem.
What you do is basically:
- Put everything (gems, application config, database migrations, models, controllers) into a single .rufile, likeapp.ru.
- Run it via rackup app.ru. (Hint: if your file is calledconfig.ru, you can just run `rac...
OpenAI TTS: How to generate audio samples with more than 4096 characters
OpenAI is currently limiting the Audio generating API endpoint to text bodies with a maximum of 4096 characters.
You can work around that limit by splitting the text into smaller fragments and stitch together the resulting mp3 files with a CLI tool like mp3wrap or ffmpeg.
Example Ruby Implementation
Usage
input_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mi eget mauris pharetra et ultrices neque."
output_mp3_path = Rails.root.join("tts/ipsum...
RestClient / Net::HTTP: How to communicate with self-signed or misconfigured HTTPS endpoints
Occasionally, you have to talk to APIs via HTTPS that use a custom certificate or a misconfigured certificate chain (like missing an intermediate certificate).
Using RestClient will then raise RestClient::SSLCertificateNotVerified errors, or when using plain Net::HTTP:
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
Here is how to fix that in your application.
Important: Do not disable certificate checks for production. The interwebs are full of people say...
RSpec: You can super into parent "let" definitions
RSpec's let allows you to super into "outside" definitions, in parent contexts.
Example:
describe '#save' do
  subject { described_class.new(attributes) }
  let(:attributes) { title: 'Example', user: create(:user) }
  it 'saves' do
    expect(subject.save).to eq(true)
  end
  context 'when trying to set a disallowed title' do
    let(:attributes) { super().merge(title: 'Hello') } # <==
    it 'will not save' do
      expect(subject.save).to eq(false)
    end
  end
end
I suggest you don't make a habit of using this regula...
How to open files from better_errors with RubyMine on Linux
I recently noticed that better_errors allows you to to open files from within your favorite editor. However it was not so easy to get rubymine:// links to work on Gnome/Linux. Here is how it finally worked for me:
Step 1: Add a Desktop launcher
Add this file to ~/.local/share/applications/rubymine.desktop:
[Desktop Entry]
Version=1.0
T...