FactoryGirl в Rails - Ассоциации с уникальными ограничениями - PullRequest
9 голосов
/ 13 марта 2012

Этот вопрос является продолжением вопроса, поднятого здесь:

Использование factory_girl в Rails с ассоциациями, которые имеют уникальные ограничения. Получение дубликатов ошибок

Предлагаемый ответ отлично сработал для меня. Вот как это выглядит:

# Creates a class variable for factories that should be only created once.

module FactoryGirl

  class Singleton
    @@singletons = {}

    def self.execute(factory_key)
      begin
        @@singletons[factory_key] = FactoryGirl.create(factory_key)
      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
        # already in DB so return nil
      end

      @@singletons[factory_key]
    end
  end

end

Проблема, которая возникла у меня, заключается в том, что мне нужно вручную создать ассоциацию для поддержки полиморфной ассоциации с ограничением уникальности в ловушке. Например:

class Matchup < ActiveRecord::Base
  belongs_to :event
  belongs_to :matchupable, :polymorphic => true

  validates :event_id, :uniqueness => { :scope => [:matchupable_id, :matchupable_type] }
end

class BaseballMatchup < ActiveRecord::Base
  has_one :matchup, :as => :matchupable
end

FactoryGirl.define do
  factory :matchup do
    event { FactoryGirl::Singleton.execute(:event) }
    matchupable { FactoryGirl::Singleton.execute(:baseball_matchup) }
    home_team_record '10-5'
    away_team_record '9-6'
  end

  factory :baseball_matchup do
    home_pitcher 'Joe Bloe'
    home_pitcher_record '21-0'
    home_pitcher_era 1.92
    home_pitcher_arm 'R'
    away_pitcher 'Jack John'
    away_pitcher_record '0-21'
    away_pitcher_era 9.92
    away_pitcher_arm 'R'
    after_build do |bm|
      bm.matchup = Factory.create(:matchup, :matchupable => bm)
    end
  end
end

Моя текущая одноэлементная реализация не поддерживает вызов FactoryGirl::Singleton.execute(:matchup, :matchupable => bm), только FactoryGirl::Singleton.execute(:matchup).

Как бы вы порекомендовали изменить фабрику синглтона для поддержки вызова, такого как FactoryGirl::Singleton.execute(:matchup, :matchupable => bm) ИЛИ FactoryGirl::Singleton.execute(:matchup)?

Поскольку прямо сейчас приведенный выше код будет выдавать ошибку проверки уникальности («событие уже выполнено») каждый раз, когда перехват выполняется на фабрике: baseball_matchup. В конечном счете, это то, что необходимо исправить, чтобы в БД не более одного matchup или baseball_matchup.

Ответы [ 3 ]

3 голосов
/ 30 марта 2012

Как уже упоминал zetetic, вы можете определить второй параметр в вашей функции execute для отправки атрибутов, которые будут использоваться во время вызова FactoryGirl.create, со значением по умолчанию пустого хэша, чтобы он не перекрывал ни один из них.в случае, если вы не используете его (вам не нужно проверять в этом конкретном случае, если хеш атрибутов пуст).

Также обратите внимание, что вам не нужно определять начало .. конецблокировать в этом случае, потому что после спасения ничего не нужно делать, поэтому вы можете упростить свой метод, определив спасение как часть определения метода.Назначение в случае, если инициализация прошла успешно, также вернет присвоенное значение, поэтому нет необходимости явно обращаться к хешу снова, чтобы вернуть его.Со всеми этими изменениями код будет выглядеть так:

# Creates a class variable for factories that should be only created once.

module FactoryGirl

  class Singleton
    @@singletons = {}

    def self.execute(factory_key, attrs = {})
      @@singletons[factory_key] = FactoryGirl.create(factory_key, attrs)
    rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
      # already in DB so return nil
    end
  end

end
1 голос
/ 06 апреля 2012

Вам нужно сделать две вещи, чтобы сделать эту работу:

  1. Принимайте атрибуты в качестве аргумента вашего execute метода.
  2. Отключите имя фабрики и атрибуты при создании фабрики синглтона.

Обратите внимание, что шаг 1 недостаточен для решения вашей проблемы. Даже если вы разрешите execute принимать атрибуты, первый вызов execute(:matchup, attributes) будет кэшировать этот результат и возвращать его в любое время, когда вы execute(:matchup), даже если вы попытаетесь передать различные атрибуты execute. Вот почему вам также нужно изменить то, что вы используете в качестве хеш-ключа для @@singletons хеша.

Вот реализация, которую я протестировал:

module FactoryGirl
  class Singleton
    @@singletons = {}

    def self.execute(factory_key, attributes = {})

      # form a unique key for this factory and set of attributes
      key = [factory_key.to_s, '?', attributes.to_query].join

      begin
        @@singletons[key] = FactoryGirl.create(factory_key, attributes)
      rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
        # already in DB so return nil
      end

      @@singletons[key]
    end
  end
end

Ключ представляет собой строку, состоящую из имени фабрики и строки запроса представления атрибута хэша (что-то вроде "matchup?event=6&matchupable=2"). Мне удалось создать несколько разных сопоставлений с разными атрибутами, но при этом учитывалась уникальность комбинации событие / сопоставляемость.

> e = FactoryGirl.create(:event)
> bm = FactoryGirl.create(:baseball_matchup)
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> f = FactoryGirl.create(:event)
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm)
> m.id
3

Дайте мне знать, если это не сработает для вас.

1 голос
/ 15 марта 2012

Методы Ruby могут иметь значения по умолчанию для аргументов, поэтому определите ваш одноэлементный метод с пустым хешем параметров по умолчанию:

  def self.execute(factory_key, options={})

Теперь вы можете вызывать его двумя способами:

  FactoryGirl::Singleton.execute(:matchup)
  FactoryGirl::Singleton.execute(:matchup, :matchupable => bm)

внутри метода протестируйте хеш аргумента параметров, чтобы увидеть, было ли что-либо передано в:

if options.empty?
  # no options specified
else
  # options were specified
end
...