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 web development

Do you need DevOps-experts?

Your development team has a full backlog? No time for infrastructure architecture? Our DevOps team is ready to support you!

  • We build reliable cloud solutions with Infrastructure as code
  • We are experts in security, Linux and databases
  • We support your dev team to perform
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)