Read more

When reading model columns during class definition, you must handle a missing/empty database

Arne Hartherz
April 25, 2019Software engineer at makandra GmbH

When doing some meta-programming magic and you want to do something for all attributes of a class, you may need to access connection or some of its methods (e.g. columns) during class definition.

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

While everything will be fine while you are working on a project that is in active development, the application will fail to boot when the database is missing or has no tables. This means that Raketasks like db:create or db:migrate fail on a freshly cloned project.

The reason is your environment.rb which is loaded for Raketasks and calls Rails.application.initialize! which in turn may/will evaluate classes. If one of those classes is tries to access its database connection, you will encounter fun errors such as:

  • PG::ConnectionBad (for missing databases on PostgreSQL)
  • ActiveRecord::StatementInvalid: PG::UndefinedTable (when database exists, but has no tables)

Generally speaking, evaluating model columns during class definition is not a bad thing, but you need to make it work when the model has no database or database columns yet. Example:

class Post < ApplicationRecord

  begin
    # Magically auto-strips all string attributes

    columns.each do |column|
      next if [:string, :text].exclude?(column.type)

      attribute_name = column.name
      class_eval <<~RUBY, __FILE__, __LINE__ + 1
        def #{attribute_name}=(value)
          super(value.to_s.strip.presence)
        end
      RUBY
    end
  rescue PG::ConnectionBad, ActiveRecord::StatementInvalid
    # When the database does not exist at all or is missing a model's table,
    # accessing "columns" raises an error and the application fails to boot.
    #
    # To allow calling "rake db:create" or "rake db:migrate" on an empty
    # database, we swallow such errors.
  end

end

For MySQL, rescue Mysql2::Error, ActiveRecord::StatementInvalid might be fitting.

Posted by Arne Hartherz to makandra dev (2019-04-25 09:20)