I have two ActiveModels, MultivariateExperiment
which has_many
MultivariateExperimentVariant
. Conversely, a MultivariantExperimentVariant
belongs_to
a MultivariateExperiment
.
MultivariateExperiment
has an attribute experiment_name
.
MultivariantExperimentVariant
has the attributes name
and weighting
.
I'd like the variants' name
to be in the format experiment_name_0
, experiment_name_1
, etc.
For instance, given the following MultivariateExperiment:
mve = MultivariateExperiment.create({ experiment_name: 'user_signup' })
I'd like to have a programmatic way of having the associated variants be:
mve.multivariate_experiment_variants.create({ weighting: 1 }) # expected name: "user_signup_0"
mve.multivariate_experiment_variants.create({ weighting: 1 }) # expected name: "user_signup_1"
mve.multivariate_experiment_variants.create({ weighting: 2 }) # expected name: "user_signup_2"
I initially thought about putting this in an after_commit
callback but was told in code review to avoid it as that callback is finnicky (not sure why)
I took a look at some other callbacks but none of them seem comprehensive enough to cover the myriad of ways an association can be created, such as the following:
# 1st approach
mve.multivariate_experiment_variants.create({ weighting: 1 })
# 2nd approach
variant = MultivariateExperimentVariant.create({ weighting: 1 })
mve << variant
mve.save
# 3rd approach
mve.multivariate_experiment_variants.build({ weighting: 1 })
mve.save
# etc. etc.
So, given the various ways to create associations, are there any mechanisms or approaches that can successfully compute an attribute of a Model in a has_many
relationship using an attribute of the Model it belongs to?
In the MultivariateExperimentVariant
model you could do...
after_save :set_name
private
def set_name
# Assuming you have a required belongs_to
update(name: "#{multivariate_experiment.name}_#{multivariate_experiment.multivariate_experiment_variants.length - 1}")
end
You wouldn't want to use after_commit
unless you specify the on:
condition
after_commit :set_name, on: [:create, :update]
Otherwise, it will try to set name after the record is destroyed.
Side note: It may be better to check the index instead...
update(name: "#{multivariate_experiment.name}_#{multivariate_experiment.multivariate_experiment_variants.index(self)}")