Testing custom validators

When writing custom validators like described in the Rails guide on validations it’s good practice to write tests for them so you are sure they work as intended. Let’s take a look at the custom validator example shown in that Rails guide:

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors.add attribute, (options[:message] || "is not an email")
    end
  end
end

The above validator will validate attributes against a regular expression to see if the value is a valid email address, if it is not a valid email address the error “is not an email” is added to the errors for that attribute.

You can test the validator by working with a test model class inside your test like so:

require "test_helper"

class EmailValidatorTest < ActiveSupport::TestCase
  class TestModel
    include ActiveModel::Model

    attr_accessor :email

    validates :email, email: true
  end

  test "recognizes a valid email address" do
    test_object = TestModel.new(email: "mark@without-brains.net")

    assert test_object.valid?
  end

  test "fails validation for an invalid email address" do
    test_object = TestModel.new(email: "no-email")

    refute test_object.valid?
    assert_equal ["is not an email"], test_object.errors[:email]
  end
end

By defining the TestModel class inside the test it will actually be named EmailValidatorTest::TestModel preventing clashes with other tests.

If you want to try the above code in a Rails project you can save the files as app/validators/email_validator.rb and test/validators/email_validator_test.rb respectively and then run the tests.

Using instance_exec to change block scope

When you create a block in Ruby and execute it later it’s context is tied to where it was defined. Take a look at this very contrived example:

class Dog
  attr_accessor :name, :block

  def initialize(name)
    @name = name
    @block = proc do
      puts "#{self.name} the #{self.class.name}"
    end
  end
end

class Person
  attr_accessor :name, :block

  def initialize(name)
    @name = name
  end
end

@fido = Dog.new('Fido')
@mark = Person.new('Mark')
@mark.block = @fido.block

If you load the code above into irb or pry you can see this in action:

[2] pry(main)> @mark.block.call
Fido the Dog
=> nil

In the block self refers to @fido, the instance of the Dog class in which the block was defined. If you want to execute the block in the context of another object you can use instance_exec like so:

[3] pry(main)> @mark.instance_exec(&@fido.block)
Mark the Person
=> nil

You can find the documentation of instance_exec here.

Selecting and locking records for update with PostgreSQL

In PostgreSQL you can lock records while selecting them using FOR UPDATE. When using a LEFT JOIN you can run into an error if your selection includes NULL records for the joined table, to prevent this you should specify for which table(s) the records should be locked like so:

SELECT * FROM notes LEFT JOIN authors ON notes.author_id = authors.id FOR UPDATE OF notes;

If you also want to lock the author records in the example above you would have to use a regular join and lock the records without an author in a second query.

In some cases you may only want to select records that have not been locked yet (in cases where any match to the selection, for example in a queue like table), you can use the SKIP LOCKED modifier to do this like so:

SELECT * FROM notes LEFT JOIN authors ON notes.author_id = authors.id FOR UPDATE OF notes SKIP LOCKED;

You cannot use FOR UPDATE with aggregate functions such as COUNT, if you are using an ORM of some sorts you may have to retrieve your records and count them afterwards in code.

For more information take a look at the official PostgreSQL documentation for this feature.

Using find_each to avoid memory problems

When selecting records with ActiveRecord in Rails all of the selected objects are usually loaded into memory, if you are dealing with a large record set (for example in a background job, rake task or migration) this can lead to memory problems that result in your code crashing.

By using find_each at the end your scope you will only a small set of records into memory at a time:

Note.all.find_each { |note| note.update(body: note.body.reverse) }

For more information see the documentation here.

Revert a file to a specific version in git

To revert a commit in git you can use the git revert command, this undoes all the changes of the given commit with a new commit. But what if you only want to revert one specific file to a specific revision?

First you need to identify the revision that you are after by using the git log command, you can see the history of a specific file by appending it to the command:

$ git log README.txt
commit 20f4b96db867e471b16b1392af0ffe05f2c5976e (HEAD -> feature-c)
Author: Mark Cornelissen <mark@boxture.com>
Date:   Thu Oct 7 20:03:20 2021 +0200

    Adding even more text, and FILE2

commit 30d3c934363f7effe51ac528cd1ce16dbab2bf57
Author: Mark Cornelissen <mark@boxture.com>
Date:   Thu Oct 7 20:02:28 2021 +0200

    Adding some more text and FILE1

commit d2bcd858aadf352f0fb33fe33249a972e08e3301 (origin/trunk, origin/HEAD, trunk)
Author: Mark Cornelissen <mark@boxture.com>
Date:   Sat Nov 7 08:50:17 2020 +0100

    Initial commit

Then use the git checkout command to get the version that you want to restore:

$ git checkout 30d3c934363f7effe51ac528cd1ce16dbab2bf57 -- README.txt
$ git status
On branch feature-c
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   README.txt

The selected revision is now staged for commit, you can now either make further changes to it or commit it directly.

If you want to try the commands from this article you can get a copy of the repository I used from GitHub (and checkout branch feature-c though).

Stashing part of your changes with git

Do you ever run into that situation while coding where you need to temporarily undo some of your uncommitted changes? I sure do.

If you are a git user you have likely used git’s stash feature, this allows you to park changes that you have made rather than committing them. If you didn’t know already: you can even stash new files if you stage them first (using the git add command).

If you want to stash changes to specific files rather than all your changes you can use the push sub-command like shown in the below example:

$ git status
On branch trunk
Your branch is up to date with 'origin/trunk'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        FILE1.txt
        FILE2.txt
        FILE3.txt

no changes added to commit (use "git add" and/or "git commit -a")
$ git add .
$ git status
On branch trunk
Your branch is up to date with 'origin/trunk'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   FILE1.txt
        new file:   FILE2.txt
        new file:   FILE3.txt
        modified:   README.txt

$ git stash push README.txt FILE1.txt
Saved working directory and index state WIP on trunk: d2bcd85 Initial commit
$ git status
On branch trunk
Your branch is up to date with 'origin/trunk'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   FILE2.txt
        new file:   FILE3.txt

Restoring your stashed changes works as it normally would (using git stash pop).

Using the Rails router elsewhere

When working on Rails projects a commonly used part is the Rails router which is used to direct incoming requests to the appropriate controllers and actions and to generate paths and URLs so you don’t have to hardcode them all over the place.

In systems tests and integration tests the Rails router is also available for you to direct your tests to the appropriate URLs, in your regular tests or the Rails console however this is not the case. You can however get access to it by including a module that Rails can generate for you, if you call Rails.application.routes.url_helpers a module is returned that provides the Rails router’s methods.

Continue reading “Using the Rails router elsewhere”

Changing your default kernel

A while back my laptop running Ubuntu started hanging on boot after a kernel update, to make it work I had to revert to using an earlier kernel version from the boot screen.

Its a bit inconvenient to have to do this manually every time you boot your machine, you can however change the default boot option that is used by grub. You can do this by editing /etc/default/grub and setting GRUB_DEFAULT to the item to select and then running sudo update-grub.

Continue reading “Changing your default kernel”

Testing ActiveSupport concern modules

Using modules to share code between classes in Ruby is a commonly used method to add re-usable behaviour without blocking the inheritance chain, the Rails ActiveSupport::Concern adds some convenience methods to do so. What (at least in my opinion) should not be forgotten is to test the usage of such modules on your classes, this can actually be achieved fairly easily creating a module with test code that you can then again include in your tests.

Take this very contrived example module which adds an instance method to the classes it is included in:

module ReverseName  
  def reversed_name 
    name.reverse    
  end               
end                 

It’s function could be tested with the following test:

test 'reverse name' do                             
  assert_equal 'htimS nhoJ', @name_reversable.reversed_name 
end                                                

This can be poured into a module using ActiveSupport::Concern that can be included in test cases of other classes like so:

module Concerns                                          
  module ReverseNameTest                                 
    include ActiveSupport::Concern                       
                                                         
    included do                                          
      test 'reverse name' do                             
        assert_equal 'htimS nhoJ', @name_reversable.reversed_name 
      end                                                
    end                                                  
  end                                                    
end                                                      

In Rails projects I usually put shared test code inside the directory test/test_helpers, to have these loaded when running tests you will have to add the following line to your test_helper.rb:

Dir[Rails.root.join('test/test_helpers/**/*.rb')].sort
                                                 .each { |file| require file }

To now use this test module on a class that can make use of it we can include the test module in that model’s test like so:

require 'test_helper'                                             
                                                                  
class ContactTest < ActiveSupport::TestCase                       
  include Concerns::ReverseNameTest                               
                                                                  
  setup do                                                        
    @name_reversable = Contact.new(first_name: 'John', last_name: 'Smith') 
  end                                                             
                                                                  
  test 'name' do                                                  
    contact = Contact.new(first_name: 'John', last_name: 'Smith') 
    assert_equal 'John Smith', contact.name                       
  end                                                             
end                                                               

If you want to verify that the test gets executed for your model you can purposely break the test(s) to see them fail like shown below: