Using #deep_dup for copying whole hashes and array
"Everything in Ruby is an object". This is also true for nested hashes and arrays. If you copy a hash with #clone or #dup and you modify the copy, you will run into the following behavior:
original_hash = { foo: { bar: 'original value' } }
copied_hash = original_hash.dup
copied_hash[:foo][:bar] = 'changed value'
original_hash # => { foo: { bar: "changed value" }
This is, because { bar: 'baz' } is an object, which is referenced in :foo. The copy of original_hash still holds the reference to the same object, so alterin...
Bundler in deploy mode shares gems between patch-level Ruby versions
A recent patch level Ruby update caused troubles to some of us as applications started to complain about incompatible gem versions. I'll try to explain how the faulty state most likely is achieved and how to fix it.
Theory
When you deploy a new Ruby version with capistrano-opscomplete, it will take care of a few things:
- The new Ruby version is installed
- The Bundler version stated in the Gemfil...
Video transcoding: Web and native playback overview (April 2020)
Intro
Embedding videos on a website is very easy, add a <video> tag to your source code and it just works. Most of the time.
The thing is: Both the operating system and Browser of your client must support the container and codecs of your video. To ensure playback on every device, you have to transcode your videos to one or more versions of which they are supported by every device out there.
In this card, I'll explore the available audio and video standards we have right now. The goal is to built a pipeline that...
Heads up: Ruby's Net::HTTP silently retries a failing request
Ruby's Net::HTTP library repeats a failing request once, as long as it deems it idempotent (GET, HEAD etc). Both requests will use the configured timeout. Hence, if both requests time out, Net::HTTP will only return after twice the configured timeout.
This can become an issue if you rely on the timeout to strike precisely.
Ruby: How to fetch a remote host's TLS certificate
TLS/SSL certificates are often used for HTTPS traffic. Occasionally a service may also use their TLS certificate to support public-key encrypting data (e.g. when it is part of the URI and visible to the user, but contains sensitive information).
Here is how to easily fetch such certificate data.
certificate = Net::HTTP.start('example.com', 443, use_ssl: true) { |http| http.peer_cert }
# => #<OpenSSL::X509::Certificate: subject=#<OpenSSL::X509::Name CN=www.example.org,...>
certificate.public_key
# => #<OpenSSL::PKey::RSA:0x...
Guide to String Encoding in Ruby
The linked article has a great explanation how to to deal with string encodings in Ruby. Furthermore you can check out some of our cards about encoding:
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: Validate dynamic attributes / JSON in ActiveRecord
PostgreSQL and ActiveRecord have a good support for storing dynamic attributes (hashes) in columns of type JSONB. But sometimes you are missing some kind of validation or lookup possibility (with plain attributes you can use Active Record's built-in validations and have your schema.rb).
One approach about being more strict with dynamic attributes is to use JSON Schema validations. Here is an example, where a project has the dynamic attributes analytic_stats, that we can use to store analytics from an external measurement tool.
- A g...
Structuring Rails applications: the Modular Monorepo Monolith
Root Insurance runs their application as a monolithic Rails application – but they've modularized it inside its repository. Here is their approach in summary:
Strategy
- Keep all code in a single repository (monorepo)
- Have a Rails Engine for each logical component instead of writing a single big Rails Application
- Build database-independent components as gems
- Thus: gems/ and engines/ directories instead of app/
- Define a dependency graph of components. It should have few edges.
- Gems and Engines can be extracted easier once nece...
Capybara 'fill_in': Ambiguous match for different input names
When you have two inputs, where one contains the name of the other (eg. Name and Name with special treatment), Capybara's fill_in method will fail with the following message:
Ambiguous match, found 2 elements matching visible field "Name" that is not disabled (Capybara::Ambiguous)
You can force Capybara to match exactly what you are typing (which makes your tests better anyways) with match: :prefer_exact:
name = 'Name'
value = 'Bettertest Cucumberbatch'
fill_in(field, with: value, match: :prefer_exact)
Furthermore...
Always convert and strip user-provided images to sRGB
Debugging image color profiles is hard. You can't trust your eyes in this matter, as the image rendering depends on multiple factors. At least the operation system, browser or image viewer software and monitor influence the resulting image colors on your screen.
When we offer our users the possibility to upload images, they will most likely contain tons of EXIF metadata and sometimes exotic color profiles like eciRGB. We want to get rid of the metadata, as it might contain sensitiv...
How to set up SMTP email delivery with a Gmail account
If you want to make your Rails application be capable of sending SMTP emails, check out the action mailer configuration section in the Ruby on Rails guide.
TL;DR you will end up having an smtp_settings hash that looks something like this:
smtp_settings = {
address: ...,
domain: ...,
port: ...,
user_name: ...,
password: ...,
authentication: ...,
tls: ...,
enable_starttls_auto: ...,
}
This hash can be set as the `delivery_me...
Effectively Using Materialized Views in Ruby on Rails · pganalyze
It's every developer's nightmare: SQL queries that get large and unwieldy. This can happen fairly quickly with the addition of multiple joins, a subquery and some complicated filtering logic. I have personally seen queries grow to nearly one hundred lines long in both the financial services and health industries.
Luckily Postgres provides two ways to encapsulate large queries: Views and Materialized Views. In this article, we will cover in detail how to utilize both views and materialized views within Ruby on Rails, and we can even take...
How to update the bundler version in a Gemfile.lock
-
Install the latest
bundlerversion:gem install bundler Fetching bundler-2.3.5.gem Successfully installed bundler-2.3.5 1 gem installed -
Update the bundler version in
Gemfile.lock:bundle update --bundler -
Confirm it worked:
$ tail -n2 Gemfile.lock BUNDLED WITH 2.3.5
Notes:
-
Bundler should automatically detect the latest installed version. If it does not, you can specify your preferred version like so:
b...
Rbenv: Alias a Ruby version
For newer Ubuntu versions we currently need to install the patch level version 1.8.7-p375, otherwise the dev dependencies from openssl will cause the installation to fail.
For a project that specifies the Ruby version 1.8.7 in the .ruby-version the rbenv autoswitch will not work. You have several options how you can solve this problem:
- Install rbenv-aliases, which will alias your Ruby 1.8.7-p375 ...
Bundler: How to release a gem with 2FA enabled
Rubygems supports a 2FA for your account. Once enabled you need to provide your personal OTP code for every release. Despite the CLI of the rake release task does not work well with the command prompt for your OTP code with Bundler versions < 2.0.2. It just looks like the task is frozen:
- Workaround 1: Just type your OTP code and hit enter, your gem is released afterwards.
- Workaround 2: Upgrade to Bundler >=
2.0.2.. Your supported Ruby versions for this gem must be>= 2.3.
When r...
ActiveSupport includes Timecop-like helpers
ActiveSupport (since 4.1) includes test helpers to manipulate time, just like the Timecop gem:
-
To freeze the current time, use
freeze_time(ActiveSupport 5.2+):freeze_time -
To travel to a specific moment in time, use
travel_to:travel_to 1.hour.from_nowImportant
When freezing time with
#travel_to, time will be frozen (like withfreeze_time). This means that your application can't detect passage of time by usingTime.now. -
To travel a re...
Ruby: The YAML safe_load method hides some pitfalls
The Ruby standard lib ships with a YAML Parser called Psych. But serializing and deserializing data seems not as obvious as if you are using JSON.
To safely write and read YAML files you should use Psych#dump (String#to_yaml) and Psych.safe_load (YAML.safe_load):
data = {'key' => 'value'}.to_yaml
=> "---\nkey: value\n"
YAML.safe_load(data)
=> {"key"=>"value"}
Unfortunately you might encounter a few pitfalls which are not obvious in the first place. All of them are a side effect that you can not configure Psych#dump to o...
Capybara: Execute asynchronous JavaScript
Capybara provides execute_script and evaluate_script to execute JavaScript code in a Selenium-controlled browser. This however is not a good solution for asynchronous JavaScript.
Enter evaluate_async_script, which allows you to execute some asynchronous code and wait until it finishes. There is a timeout of a couple of seconds, so it will not wait forever.
Use it like this:
page.evaluate_async_script(<<~JS)
let [done] = arguments
doSomethingAsynchronous().then(() => {
done() // call this to indicate we're done
})
J...
Email validation regex
There is a practical short list for valid/invalid example email addresses - Thanks to Florian L.! The definition for valid emails (RFC 5322) can be unhandy for some reasons, though.
Since Ruby 2.3, Ruby's URI lib provides a built-in email regex URI::MailTo::EMAIL_REGEXP. That's the best solution to work with.
/\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[...
Integrating or upgrading makandra-rubocop
Introduction
Most of the time it is a tedious task to apply a code style guide to an existing code base as there are likely to be a lot of conflicts. At makandra we are using makandra-rubocop to have code style checks. Here is some advice on how to add makandra-rubocop efficiently.
Note
RubyMine by default has a Rubocop inspection with rules that we don't always agree with. We recommend replacing this with makandra-rubocop or disabling the inspection.
...
Fix error: rails console No such file to load -- irb/encoding_aliases.rb (LoadError)
I got this error after upgrading Ruby from 2.4.5 to 2.6.4 when I opened the Rails console - rails server still worked.
Running via Spring preloader in process 14679
Loading development environment (Rails 5.2.2.1)
Traceback (most recent call last):
.../lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:175:in 'fork': No such file to load -- irb/encoding_aliases.rb (LoadError)
.../lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:175:in 'fork': undefined method 'reject!' for nil:NilClass (NoMethodError)
.../li...
How to enable Chromedriver logging
When using Chrome for Selenium tests, the chromedriver binary will be used to control Chrome. To debug problems that stem from Selenium's Chrome and/or Chromedriver, you might want to enable logging for the chromedriver itself. Here is how.
Option 1: Use Selenium::WebDriver::Service
In your test setup, you may already have something like Capybara::Selenium::Driver.new(@app, browser: :chrome, options: ...), especially when passing options like device emulation.
Similar to options, simply add an extra key service and pass an inst...
palkan/isolator: Detect non-atomic interactions within DB transactions
With this gem your transaction blocks raise an error when they have side effects that cannot be rolled back.
By default it checks whether you're connecting with HTTP, queuing a Sidekiq job or sending an e-mail within a transaction. You can add custom checks, too.
Found in this RubyGuides article.