Search code examples
ruby-on-railsfactory-botrspec-railspolymorphic-associations

factorygirl with polymorphic associations and accepted nested attributes fails validations


I have a pretty straight forward poly association setup. I am trying to validate tokens only exist on providers or shops, not both. These validations work correctly when I use pry, however, my refactor has borked the factories.

Problem

If you create the shop with a token i.e. FactoryGirl.create(:shop, :with_authentication_token) it blows up because the shop can not get created and saved like FG is trying to process it? Can anyone point me in the right direction to setup the shop factory?

Error

ActiveRecord::RecordInvalid: Validation failed: Owner can't be blank

Right now the provider factory works because it is the parent.

Working in PRY?

shop = FactoryGirl.build(:shop)
shop.authentication_token_attributes = { token: 'test', owner: shop }
shop.save

Table

  create_table "authentication_tokens", force: :cascade do |t|
    t.string   "token",      limit: 255, null: false
    t.datetime "created_at",             null: false
    t.datetime "updated_at",             null: false
    t.integer  "owner_id",   limit: 4,   null: false
    t.string   "owner_type", limit: 255, null: false
  end

Factories

FactoryGirl.define do
  factory :shop do
    provider
    ...

    trait :with_authentication_token do
      before(:create) do |shop|
        create(:authentication_token, owner: shop)
      end
      after(:build) do |shop|
        build(:authentication_token, owner: shop)
      end
    end

    trait :inactive do
      active { false }
    end
  end
end

Models

class Shop < ActiveRecord::Base
  belongs_to :provider
  has_one :authentication_token, as: :owner, dependent: :destroy

  accepts_nested_attributes_for(:authentication_token, update_only: true)

  ...

  validates :authentication_token, presence: true, if: :shop_is_owner?

  ...

  private

  def shop_is_owner?
    return false if provider.authentication_token
    true
  end
end

class Provider < ActiveRecord::Base
  ...

  has_many :shops
  has_one :authentication_token, as: :owner, dependent: :destroy

  ...

  accepts_nested_attributes_for(:authentication_token, update_only: true)

end


class AuthenticationToken < ActiveRecord::Base
  belongs_to :owner, polymorphic: true

  validates :token,
            length: { maximum: 245 },
            presence: true,
            uniqueness: true

  validates :owner, presence: true

  validate :unique_auth_token

  def shop
    return owner if owner_type == 'Shop'
  end

  def provider
    return owner if owner_type == 'Provider'
  end

  private

  def unique_auth_token
    errors.add(:base, I18n.t('activerecord.errors.models.shop.no_auth_token_sharing')) if shop && shop.provider.authentication_token
  end
end

Solution

  • so you can't trigger a save on either model before the related model is instantiated and related

    trait :with_authentication_token do
      before(:create) do |shop|
        create(:authentication_token, owner: shop)
      end
      after(:build) do |shop|
        build(:authentication_token, owner: shop)
      end
    end
    

    change to

    trait :with_authentication_token do
      before(:create) do |shop|
        shop.authentication_token = build(:authentication_token, owner: shop)
      end
      after(:build) do |shop|
        shop.authentication_token = build(:authentication_token, owner: shop)
      end
    end
    

    i think that should work