whenever: Installing cron jobs only for a given Rails environment or Capistrano stage

We use the whenever gem to automatically update the crontab of the servers we deploy to. By default, whenever will update all servers with a matching role (we use the :cron role ).

This card describes how to install some tasks only for a given Rails environment or for a given Capistrano stage ("deployment target").

Installing jobs only for a given Rails environment
-----------------------------------...

How Ruby method lookup works

When you call a method on an object, Ruby looks for the implementation of that method. It looks in the following places and uses the first implementation it finds:

  1. Methods from the object's singleton class (an unnamed class that only exists for that object)
  2. Methods from prepended modules (Ruby 2.0+ feature)
  3. Methods from the object's class
  4. Methods from included modules
  5. Methods from the class hierarchy (superclass and its an...

Rspec: Expecting a Rake task to be called

This seems to be obvious, but you can expect Rake tasks to be called in RSpec.

it 'deletes all Users' do
   FactroyBot.create(:user)
   expect(Rake::Task['notify:critical_operation']).to receive(:invoke)
   
   expect { described_class.clean }.to change(User, :count).from(1).to(0) 
end

Note: Try to avoid logic in rake tasks and prefer to just call classes in them.

Example:

desc 'Some task'
task :some_task do
  SomeClass.new.run
end

How to use git fixup

Using git fixup helps you to speed up appending changes further back in the git history of your feature branch.

Example:

git commit --fixup aabbcc # Create a commit with the message "fixup! Commit message of aabbcc"
git rebase -i --autosquash master

It would be nice if you could use this feature without the -i flag, but until now it seems not to be possible. Read more about our recommended git workflow for feature branches.

Also have a look at [git shortcut to use git fixup](https://makandracards.com/makand...

Installing multiple MySQL versions on the same Linux with mysql-sandbox

Ubuntu has a package mysql-sandbox that lets you install multiple MySQL versions into your user home:

  1. Install mysql-sandbox
sudo apt install mysql-sandbox
  1. Download the version of MySQL you want to use from mysql.com:
    https://dev.mysql.com/downloads/file/?id=480427
    Make sure to choose "Generic Linux" instead of "Ubuntu" so you get a .tar.gz instead of .deb

  2. cd into the directory the mysql binaries will be extracted to

mkdir -p ~/bin/sandbox_dist
cd ~/bin/sandbox_dist
  1. Build the sandbox
make_sandbo...

HTML: Making browsers wrap long words

By default, browsers will not wrap text at syllable boundaries. Text is wrapped at word boundaries only.

This card explains some options to make browsers wrap inside a long word like "Donaudampfschifffahrt".

Option 1: hyphens CSS property

Modern browsers are able to hyphenate natively with the CSS property hyphens:

hyphens: auto

There is also hyphens: none (disable hyphenations even at ­ entities) and hyphens: manual (hyphenation at ­ only).
This feature was integrated [just ...

Rails: When defining scopes with class methods, don't use `self`

Sometimes it is useful to define a named scope by implementing a static method with the scope's name on the scoped class. For instance, when a method should decide which existing scope should be the next link in the scope chain. Take this class for example:

class Meal < ActiveRecord::Base

  named_scope :for_date, lambda { |date| :conditions => { :date => date }}
  named_scope :with_meat, :conditions => { :meat => true }
  named_scope :without_meat, :conditions => { :meat => false }

  def self.suitable_for(user)
    if user.vegetar...

How to grep through the DOM using the Capybara API

When your Cucumber feature needs to browse the page HTML, and you are not sure how to express your query as a clever CSS or XPath expression, there is another way: You can use all and find to grep through the DOM and then perform your search in plain Ruby.

Here is an example for this technique:

Then /^I should see an image with the file...

Rubygems: Installing the last version of rubygems that has no rubyforge_project deprecation warning

You can install rubygems 3.0.8 (released on February 18, 2020) to keep all the Gem::Specification#rubyforge_project deprecation warnings away from your development log. With Rubygems >= 3.1 this deprecation warning was introduced. While maintaining older projects this could get quite annoying and the fix below might okey, for newer projects the right ways is to upgrade the gems.

gem update --system 3.0.8

Example message:

NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed o...

Git: How to add changes matching a regular expression

When you have many changes, and you want to spread them across different commits, here is a way to stage all changes matching a given regular expression for a single commit.

Example

Consider the following git diff output.

diff --git a/file1.rb b/file1.rb
index 806ca88..36d536b 100644
--- a/file1.rb
+++ b/file1.rb
@@ -1,7 +1,5 @@
-# Here is a useless comment.
-# It will be removed.
 class File1
-  def foo
+  def bar
     # ...
   end
 end
diff --git a/file2.rb b/file2.rb
index 550e1c6..600f4e3 100644
--- a/file2.rb
+++ b/file2...

PSA: "index: true" in Rails migrations does not work as you'd expect

Several Rails migration methods accept index: true as an option to create an index. In some cases (like #add_column), this option is silently discarded. Know what you are doing, or use #add_index instead.

Example

Consider the following migration.

class CreateExamples < ActiveRecord::Migration
  def change
    create_table :examples do |t|
      t.references :category, index: true
      t.boolean :positive, index: true
      t.integer :number_of_participants, index: true
    end

    add_reference :examples, :user, index: tr...

Five years of "Today I Learned" from Josh Branchaud

The linked GitHub repository is a bit like our "dev" cards deck, but groomed from a single person (Josh Branchaud). It includes an extensive list of over 900 TILs on many topics that might be interesting for most of us. (e.g. Ruby, Rails, Git, Unix..)

Ruby

Here is an excerpt of all the Ruby TILs that were new to me. I encourage you to take your time to skim over the original list as well!

Rails: Concurrent requests in development and tests

With puma you can have concurrent requests. There are two concepts on how Puma can handle two incoming requests: Workers and Threads.

Workers

Puma can have multiple workers. Each worker is a process fork from puma and therefore a very heavy instance and can have multiple threads, that handle the incoming requests.

Example: A Puma server with 2 workers and 1 thread each can handle 2 request in parallel. A third request has to wait until the thread of one of the workers is free.

Threads

Rails is thread-safe since version 4 (n...

How to use Simplecov to find untested code in a Rails project with RSpec and Cucumber

Simplecov is a code coverage tool. This helps you to find out which parts of your application are not tested.

Integrating this in a rails project with rspec, cucumber and parallel_tests is easy.

  1. Add it to your Gemfile and bundle

    group :test do
      gem 'simplecov', require: false
    end
    
  2. Add a .simplecov file in your project root:

    SimpleCov.start 'rails' do
      # any custom configs like groups and filters can be here at a central place
      enable_cov...
    

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...

How to: Throttle CPU in Google Chrome

Chrome allows you to throttle the Network and the CPU. Both settings are useful to measure the performance of you application and reproduce performance issues (Example Debugging frontend performance issues with Chrome DevTools).

You find the settings in: DevTools > Performance > Capture Settings (Gear icon in the right corner) > `CPU: No...

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...

Devise: How to allow only HTTP Basic Auth and disable the HTML sign-in form

By default, Devise redirects to a sign-in form when accessing a route that requires authentication. If for some reason you do not want this, but use Basic Authentication (and the corresponding browser username/password dialog) instead, this is a simple change.

Note that Devise's default configuration actually only redirects requests for HTML content (as requested by the HTTP Accept header).
For all other formats (like JSON) it would use Basic Auth if the http_authenticatable setting was enabled. So you can simply enable that flag and cl...

How to write good code comments

Code comments allow for adding human readable text right next to the code: notes for other developers, and for your future self.

As always, with great power comes great responsibility. Code comments can go wrong in many ways: they may become outdated, silently move away from the code they're referring to, restate the obvious, or just clutter files.

Good Comments

Here are some simple rules to keep your comments helpful:

Avoid stating what some code does – prefer the Why

Some say, perfect code would not need a single comment. Th...

Writing a README for a project

Rails applications and ruby gems should have a README that gives the reader a quick overview of the project. Its size will vary as projects differ in complexity, but there should always be some introductory prose for a developer to read when starting on it.

Purpose

That's already the main purpose of a project README: Give a new developer a quick overview of the project. In sketching this outline, the README should notify the reader of any peculiarity he needs to know of.

Remember that in a few months, you'll be a kind of "new ...

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.

Best Practice: Creating User Accounts Without Sending the Password

In applications without a sign-up, user accounts are usually created by an admin. This imposes two challenges:

  • How to transmit the password securely and
  • How to make the user change the initial password immediately

There is a simple solution: create the account with a secret password, then ask the user to use the password reset with his user name.

How to avoid ActiveRecord::EnvironmentMismatchError on "rails db:drop"

After loading a staging dump into development, you might get an ActiveRecord::EnvironmentMismatchError when trying to replace the database (like rails db:drop, rails db:schema:load).

$ rails db:drop
rails aborted!
ActiveRecord::EnvironmentMismatchError: You are attempting to modify a database that was last run in `staging` environment.
You are running in `development` environment. If you are sure you want to continue, first set the environment using:

        bin/rails db:environment:set RAILS_ENV=development

Starting with R...