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.

They are stored in YAML files, one file per model, which are placed in the directory appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just put your files in <your-rails-app>/test/fixtures/ ). The fixture file ends with the .yml file extension, for example: <your-rails-app>/test/fixtures/web_sites.yml ).

Did using ERb inside fixtures help?