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.
CodePudding user response:
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.