3

Simplified Example:

I recently setup Single Table Inheritance on an Animal model. Cat and Dog are subclasses of Animal.

I have an Animal factory : factory :animal do type { ["Dog","Cat"] }.sample end

Almost everywhere in my test suite I call let(:animal) { Factory.create(:animal) } because the type of Animal is irrelevant for the test. Since moving to STI I am getting errors where I perform equality checks on these animals because the superclass Animal is returned by the factory but when associated objects instantiate Animal they return the subclass.

Example: expect(zoo.animal).to eq(animal) fails with: expected: #<Cat:0x007fa01a8cd360 same_other_attributes...> actual: #<Animal:0x007fa01b8d33b8 same_other_attributes...>

Is there a way I can change the Animal factory to return an instance of its subclass?

I did try calling .reload on the Animal after the factory creates it but it did not trigger reloading the new (sub)class. I know normally you can call superclass.becomes!(subclass) to force the change but don't know how to put that in a FactoryGirl callback in a way that will actually return the converted object.

kittyminky
  • 478
  • 6
  • 27
  • What does your `animal` declaration look like in `Zoo`? You might be able to change that declaration to return the child class instead of the parent class. – kcdragon May 14 '17 at 03:17
  • Can you change your test suite to { ["Dog", "Cat"] }.sample.class? – hashrocket May 14 '17 at 03:21
  • @HashRocket in what way would that help? @kcdragon In that specific example a `Zoo` has_one `Animal` and I test that a job properly sets that Zoo's animal. But, it is irrelevant, as I mentioned I am getting failing tests all throughout my test suite bc multiple tests that expect the `Animal` to be the one originally created with `FactoryGirl` are now instead getting back a `Cat` or a `Dog`. I understand *why*, I'm wondering if there's something I can do to ensure they match the originally created `Animal` rather than having to specify the type everywhere (especially when it shouldn't matter). – kittyminky May 14 '17 at 03:41
  • You want to return Animal from your factory instead of Dog or Cat correct? – hashrocket May 14 '17 at 03:45
  • Yes. Do you mean to add `.sample.class` at the end of the factory definition? `type` in the factory refers to the literal `type` column which sets the subclass. – kittyminky May 14 '17 at 03:48
  • Then if ```type``` sets the actual subclass, you should be able to call superclass on it and it should return 'Animal'. – hashrocket May 14 '17 at 04:34

1 Answers1

7

You can force the superclass factory to return an instance of the subclass by using initialize_with

Ex:

initialize_with do
  klass = type.constantize
  klass.new(attributes)
end
kittyminky
  • 478
  • 6
  • 27