Compare library versions as "Gem::Version" instances, not as strings
Sometimes we have to write code that behaves differently based on the version of a specific gem or the Ruby Version itself. The version comparison can often be seen with simple string comparison like so.
# ❌ Not recommended
if Rails.version > '6.1.7.8' || RUBY_VERSION > '3.1.4'
raise Error, 'please check if the monkey patch below is still needed'
end
If you are lucky, the version comparison above works by coincidence. But chances are that you are not: For example, Rails version 6.1.10.8
would not raise an error in the code ...
RubyMine: Adjust Code Templates
tl;dr
To adjust code templates in RubyMine, navigate to Settings -> Editor -> File and Code Templates.
Example
You can navigate to the test file of the currently open file with the shortcut Ctrl + T
. If no test file exists, you can generate a new test file. If you are not pleased with the output, you can adjust the related code template. To do this, open the project settings and navigate to Editor -> File and Code Templates -> Files. Now, choose your relevant file type and adjust the code template according ...
How to speed up JSON rendering with Rails
I was recently asked to optimize the response time of a notoriously slow JSON API endpoint that was backed by a Rails application.
While every existing app will have different performance bottlenecks and optimizing them is a rabbit hole of arbitrary depth, I'd like to demonstrate a few techniques which could help reaching actual improvements.
The baseline
The data flow examined in this card are based on an example barebone rails app, which can be used to reproduce the r...
How to query GraphQL APIs with Ruby
While most Rails Apps are tied to at least one external REST API, machine-to-machine communication via GraphQL is less commonly seen. In this card, I'd like to give a quick intro on how to query a given GraphQL API - without adding any additional library to your existing app.
Core aspects of GraphQL
Interacting with GraphQL feels a bit like querying a local database. You are submitting queries to fetch data in a given structure (like SELECT in SQL) or mutations to alter the database (similar to POST/PUT/DELETE in REST). You can ...
JavaScript: Humanizing durations
Modern JavaScript includes Intl.NumberFormat to format numbers in different formats and locales.
In this card, we describe a wrapper for it that humanizes a given number of seconds in the "next best" unit, like seconds, minutes, etc.
Example usage
>> new Duration(42).humanized()
=> '42 Sekunden'
>> new Duration(123456).humanized()
=> '1 Tag'
>> new Duration(123456).humanized('es')
=> '1 día'
Code
Here is the code ...
Ruby: Retrieving and processing files via Selenium and JavaScript
This card shows an uncommon way to retrieve a file using selenium where JavaScript is used to return a binary data array to Ruby code.
The following code example retrieves a PDF but the approach also works for other file types.
require "selenium-webdriver"
selenium_driver = Selenium::WebDriver.for :chrome
selenium_driver.navigate.to('https://example.com')
link_to_pdf = 'https://blobs.example.com/random-pdf'
binary_data_array = selenium_driver.execute_script(<<-JS, link_to_pdf)
const response = await fetch(arguments[0])
if (!r...
Simple debounce in vanilla JavaScript
Debouncing a method call delays its execution until after a specified time has passed.
If it's called again before that time has passed, its execution is delayed again.
This technique is commonly used to improve performance when code would be run more often than it needs to.
One example for that are scroll
event handlers in JavaScript: You want to react to a user scrolling, but it's enough to do that when they have stopped scrolling.
Here is a small JavaScript function that you can use for that:
function debounce(callback...
Ruby: How to make your ruby library configurable
You might know a few examples, where you configure some library via a block. One example is the Rails configuration:
Rails.application.configure do |config|
config.enable_reloading = false
end
This card describes a simple example on how to make your ruby library configurable.
Example
module FooClient
class Client
class_attribute :config
def self.configure
self.config ||= Configuration.new
yield(config)
end
def test
uri = URI.parse(FooClient::Client.config.endpoint)
Net:...
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 ...
Chrome: disable "Choose your search engine" popup in tests
Fresh Chrome installations now show a "Choose your search engine" popup in Europe. This might make your Cucumber tests fail.
Fortunately there is a flag to disable the popup. Add the following option to your chromedriver setup code:
options.add_argument('--disable-search-engine-choice-screen')
I found this flag in Peter Beverloo's list.
Background: This was experienced locally with google-chrome 127.0.6533.72
. In CI I did not get the popup.
Ruby: Different ways of assigning multiple attributes
This card is a short summary on different ways of assigning multiple attributes to an instance of a class.
Using positional parameters
Using parameters is the default when assigning attributes. It works good for a small number of attributes, but becomes more difficult to read when using multiple attributes.
Example:
class User
def initialize(salutation, first_name, last_name, street_and_number, zip_code, city, phone_number, email, newsletter)
@salutation = salutation
@first_name = first_name
@last_name = last_nam...
The Framework Field Guide - Fundamentals | Unicorn Utterances
I used two lab days to read the The framework field guide - Fundamentals, the first of a three part series to learn the basics of frontend technologies. I can highly suggest it for learning the fundamentals. 'The framework field guide' is written by Unicron Utterances and there side has many high quality articles on web development and computer science related to programming.
[The Framework Field Guide](https://unicorn-ut...
RSpec: Applying stubs only within a block
When you mocked method calls in RSpec, they are mocked until the end of a spec, or until you explicitly release them.
You can use RSpec::Mocks.with_temporary_scope
to have all mocks applied inside a block to be released when the block ends.
Example:
RSpec::Mocks.with_temporary_scope do
allow(Rails).to receive(:env).and_return('production'.inquiry)
puts Rails.env # prints "production"
end
puts Rails.env # prints "test"
Note that, when overriding pre-existing mocks inside the block, they are not reverted to the previously ...
Caveat when using Rails' new "strict locals" feature
In Rails 7.1 it has become possible to annotate partials with the locals they expect:
# partial _user_name.erb
<%# locals: (user:) %>
<%= user.name %>
# view
<%= render 'user_name' %> <%# this raises an ArgumentError %>
Unfortunately, when some other code in that template raises an ArgumentError
(for example an error in the User#name
method) you will end up with a confusing stacktrace that looks like you have an error in your render
call.
If th...
Active Record: Never use optional with a symbol, lambda or proc
tl;dr
Do not use the option
optional
on association declarations with a symbol, lambda or proc.
Explanation
Association declarations like belongs_to
support the option optional
. This option does not support symbols, lambdas or procs. If you do so, this will always result in optional: true
. So your records can miss a presence validation if optional
is used with a symbol, lambda or proc.
If you set t...
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...
RSpec: Using helpers in view specs
If an view spec crashes due to undefined helper methods, you can enable this option:
# config/application.rb
config.action_controller.include_all_helpers = true
If you cannot use this setting, your spec can include individual helper modules like this:
describe 'some view', type: :view do
helper SomeHelper
helper OtherHelper
it 'renders' do
render 'view_that_uses_helpers'
end
end
Alternatively you can also explicitly include *all help...
Rails: Using require and permit for attributes
Raising errors for required and permitted attributes makes it easier to find errors in your application during development and in tests. Consider this approach if you want to strengthen the params handling in your application.
Example
# config/application.rb
config.action_controller.action_on_unpermitted_parameters = :raise
def user_params
params.require(:user).permit(:full_name)
end
Effects
- This raises an error `Ac...
Unpoly + Nested attributes in Rails: A short overview of different approaches
This card describes four variants, that add a more intuitive workflow when working with nested attributes in Rails + Unpoly:
- Without JS
- With HTML template and JS
- With HTML template and JS using dynamic Unpoly templates
- Adding Records via XHR and JS
Example
For the following examples we use a simple data model where a user has zero or more tasks.
Ruby: Following redirects with the http gem ("httprb")
When making requests using the http
gem you might want to automatically follow redirects to get the desired response. This is supported but not the default behavior.
You can do so by using the .follow
method.
This follows redirects for the following status codes: 300, 301, 302, 303, 307, 308
response = HTTP.get('https://www.example.com/redirect')
response.status # => 302
response.uri.to_s # => "https://www.example.com/redirect"
response = HTTP.follow.get('https://www.example.com/redirect')
response.status # => 200
response....
Open UI: Future development in web components and controls
tl;dr When browsers start to adapt proposals from Open UI, it might not be necessary to use any 3rd party libraries to have nice components and controls in web applications e.g. selects. It would require only a minimum of CSS and Javascript to get them working and looking good.
The purpose of the Open UI, a W3C Community Group, is to allow web developers to style and extend built-in web UI components and controls, such as
<select>
dropdowns, checkboxes, radio buttons, and date/color pickers.To do that, we’ll need to fully speci...
Rails: Different flavors of concatting HTML safe strings in helpers
This card describes different flavors for concatting HTML safe strings in a helper method in Rails. You might want to use the tag helper instead of the content_tag helper (the tag
helper knows all self closing tags).
Example
We want to generate HTML like this:
<h1>Navigation</h1>
<ul>
<li>Left</li>
<li>Right</li>
</ul>
Below you ca...
How to handle when an HTML <video> element cannot autoplay
HTML <video>
elements can automatically start playing when the autoplay
attribute is set on them. Except for when they can not, e.g. on pageload, or when the element enters the DOM without user interaction, or when the browser for some other reason decided to not start playing the video.
While there is no native "autoplay failed" event to listen to, you can wait for video data to be loaded and then check if the video actually started playing.
Example
<video autoplay>
<source src="example.mp4" type="video/mp4" />
</video>
...
Using rack-mini-profiler (with Unpoly)
Debugging performance issues in your Rails app can be a tough challenge.
To get more detailed insights consider using the rack-mini-profiler gem.
Setup with Unpoly
Add the following gems:
group :development do
gem 'memory_profiler'
gem 'rack-mini-profiler'
gem 'stackprof'
end
Unpoly will interfere with the rack-mini-profiler widget, but configuring the following works okayish:
// rack-mini-profiler + unpoly
if (process...