When do default values in Postgres get assigned in Rails callback chain?

Viewed 60

At what point in the Rails callback chain does PostgreSQL assign any database (not null constraint) default values?

For example, I have an Experiment model that has a before_create callback to set the experiment_type. An experiment has_many Samples. If samples have been created at the time of experiment creation, the experiment is considered the same experiment_type as the samples' sample_type. Otherwise, it gets assigned the default value of the database.

class Experiment < ApplicationRecord
  before_create :setup_exp_type

  def setup_exp_type
    sample_set = self.samples.first  # An Experiment has_many samples
    self.experiment_type ||= sample_set&.sample_type
  end

The database table has the constraint:

          Column           |            Type             | Collation | Nullable |                      Default                      | Storage  | Stats target | Description 
---------------------------+-----------------------------+-----------+----------+---------------------------------------------------+----------+--------------+-------------
 id                        | integer                     |           | not null | nextval('experiments_id_seq'::regclass)           | plain    |              | 
...
 experiment_type           | experiment_type             |           | not null | '1'::experiment_type                              | plain    |              | 

The controller is straightforward:

  def create
    @experiment = Experiment.new(experiment_params)
    respond_to do |format|
      if @experiment.save
        format.html { redirect_to @experiment, notice: 'Experiment was successfully created.' }
        format.json { render :show, status: :created, location: @experiment }
      else
        format.html { render :new }
        format.json { render json: @experiment.errors, status: :unprocessable_entity }
      end
    end
  end

Assuming samples have been created prior to the experiment creation and assigned to the experiment, at the point the setup_exp_type callback gets called, I would assume the database default values have not been assigned yet since the record is still only in local memory. However, in testing, I am seeing self.experiment_type = 1 when debugging the second line of setup_exp_type. There are no other callbacks prior to this, so it is not being assigned anywhere else in the source code.

1 Answers

The default values are set when you call new on an object, you can see in the source code of the method here,

# File activerecord/lib/active_record/base.rb, line 1543
      def initialize(attributes = nil, options = {})
        @attributes = attributes_from_column_definition
        @association_cache = {}
        @aggregation_cache = {}
        @attributes_cache = {}
        @new_record = true
        @readonly = false
        @destroyed = false
        @marked_for_destruction = false
        @previously_changed = {}
        @changed_attributes = {}
        @relation = nil

        ensure_proper_type
        set_serialized_attributes

        populate_with_current_scope_attributes

        assign_attributes(attributes, options) if attributes

        yield self if block_given?
        run_callbacks :initialize
      end

In the first line attributes_from_column_definition is called which you can check here

def attributes_from_column_definition
        self.class.columns.inject({}) do |attributes, column|
          attributes[column.name] = column.default unless column.name == self.class.primary_key
          attributes
        end
      end

As you can see in second line it is calling the column default which sets the default value of an object when it is initialised.

Related