How to run model initializer with Rails fixtures?

I have a Message model with an overloaded initializer, however when creating a fixture for this model, the initializer is never executed and tests thus fail. How can I ensure the initializer gets called?

The only relevant info I found in the documentation is to add self.use_instantiated_fixtures = true, but regardless of whether I add this or not (to the test itself, to a setup do ... end block or to test_helper.rb) the initializer doesn’t get called anyway.

See example below:

# db/schema.rb
  create_table "messages", force: :cascade do |t|
    t.string "template_name"
    t.string "subject"
    t.string "body"
    # ...
  end

# app/models/message.rb
class Message < ApplicationRecord
  # ...
  def initialize(attributes = nil)
    if (template_name = attributes[:template_name])
      attributes[:subject] = I18n.t("messages.#{template_name}.subject")
      attributes[:body] = I18n.t("messages.#{template_name}.body")
    end
    super
  end

# config/locales/en.yml

  messages:
    welcome:
      subject: "Welcome!"
      body: Lorem ipsum dolor sit amet.

The message gets created fine using Message.create:

Message.create(template_name: "welcome")
=> #<Message:0x00007fa2b2f4ad88
 ...
 template_name: "welcome",
 subject: "Welcome!",
 body: "Lorem ipsum dolor sit amet.\n",
 ...>

So now I want to test the instantiation works fine in my tests using a Rails fixture:

# test/fixtures/messages.yml

msg:
  template_name: welcome
# test/models/message_test.rb

  test "message instantiated with template has subject and body" do
    self.use_instantiated_fixtures = true # tried with and without
    message = messages(:msg)

    assert_equal "Welcome!", message.subject # assertion fails, subject and body are nil
    assert_equal "Lorem ipsum dolor sit amet.\n", message.body
  end

initialize is only called when you instantiate a new record (MyModel.new), when a record is retrieved from the database, it goes through MyModel.allocate.init_with().

It’s not recommended to redefine either initialize or init_with. If you need to execute code when a record is instantiated you have dedicated callbacks for that: Active Record Callbacks — Ruby on Rails Guides

1 Like

Thank you @byroot, I completely forgot about using callbacks. That’s the danger of looking at old stackoverflow posts…

Here is the result:

class Message < ApplicationRecord
  before_create :load_template, if: ->{ template_name }

  # ...

  private def load_template
    self.subject ||= I18n.t("messages.#{template_name}.subject")
    self.body ||= I18n.t("messages.#{template_name}.body")
  end
end
1 Like

Don’t forget you can use ERb inside your fixtures, in case that helps.