Set activerecord model defaults before mass assignment initialize

285 views Asked by At

I need defaults (from a remote service) in ModelA to be set before the object is passed to the view from ModelAController#new. I have used after_initialize for this. However, in #create I have a problem. If I use model_b.create_model_a(some_attributes), the attributes are passed in during initialization and then overwritten by the after_initialize call:

class ModelA < ActiveRecord::Base
  after_initialize :set_defaults, if: :new_record?

  def set_defaults
    self.c = "default"
    #actually a remote call, not something can be set as database default
  end
end

class ModelB < ActiveRecord::Base
  belongs_to :model_a
end

class ModelAController < ApplicationController
  #ModelA is nested under ModelB in routes.rb

  #GET /model_bs/:model_b_id/model_as/new
  def new
    model_b = ModelB.find(params[:model_b_id])
    #no problem
    respond_with model_b.build_model_a
  end

  #POST /model_bs/:model_b_id/model_as
  def create
    model_b = ModelB.find(params[:id])
    #problem:
    respond_with model_b.create_model_a({c: "not default"})
    #at this point the model_a instance still has attribute c set to "default"
  end
  ...
end

I could separate the create steps:

model_b = ModelB.find(params[:id])
model_a = model_b.build_model_a #should fire after_initialize
model_a.update_attributes({c: "not default"}) #overwrite default c value

But I feel like this makes the lifecycle of ModelA a bit of a trap for other programmers. This looks like an obvious candidate for refactoring the last two lines into one, but that would create this problem again. Is there a neater solution?

1

There are 1 answers

0
BroiSatse On BEST ANSWER

Make a conditional assignment:

def set_defaults
  self.c ||= "default"
end

Alternatively instead of after_initialize hook set a default in attribute reader. That way you only set the default when you actually need attribute value, so it saves you a remote call if you don't need it:

def c
  super || self.c = 'default'
end