Статьи

Factory Girl 201

Конечный продукт
Что вы будете создавать

Моя вторая статья об этом популярном и полезном Ruby gem посвящена еще нескольким нюансам тем, которые новичкам не обязательно должны интересовать сразу, когда они начинают. Опять же, я сделал все возможное, чтобы держать его доступным для новичков, и объяснил, что все, кто знаком с языками, плохо знакомыми с Test-Driven Development (TDD), могут наткнуться.

  • Зависимые атрибуты
  • Переходные атрибуты
  • Ленивые атрибуты
  • Модифицирующие фабрики
  • Callbacks
  • ассоциации
  • Псевдонимы
  • Черты

Если вам нужно использовать значения атрибутов для составления других фабричных атрибутов на лету, Factory Girl поможет вам. Вам просто нужно обернуть значение атрибута в блок и интерполировать нужные атрибуты. Эти блоки имеют доступ к оценщику, который ему предоставлен, и который, в свою очередь, имеет доступ к другим атрибутам, даже временным.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
FactoryGirl.define do
 
  factory :supervillain do
    name ‘Karl Stromberg’
    passion ‘marine biology’
    ambition ‘human extinction’
    motivation ‘save the oceans’
    profile { «#{name} has a passion for #{passion} and aims to #{motivation} through #{ambition}.»}
  end
 
end
 
villain = create(:supervillain)
villain.profile
# => «Karl Stromberg has a passion for marine biology and aims to save the oceans through human extinction.»

Я думаю, что было бы справедливо назвать их поддельными атрибутами. Эти виртуальные атрибуты также позволяют передавать дополнительные параметры при создании экземпляров фабрики — конечно, через хеш. Сам экземпляр не будет затронут ими, так как эти атрибуты не будут установлены на вашем фабричном объекте. С другой стороны, Factory Girl рассматривает переходные атрибуты так же, как реальные.

Если вы используете attributes_for , они не будут отображаться. Зависимые атрибуты и обратные вызовы могут получить доступ к этим поддельным атрибутам внутри вашей фабрики. В целом, они являются еще одной стратегией, чтобы сохранить ваши фабрики сухими.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
FactoryGirl.define do
 
  factory :supervillain do
 
    transient do
      megalomaniac false
      cat_owner false
    end
 
    name ‘Karl Stromberg’
    passion ‘marine biology’
    ambition ‘human extinction’
    motivation { «Building an underwater civilization#{» and saving the world» if megalomaniac}» }
    profile { «Insane business tycoon#{» – friends with Blofeld» if cat_owner}» }
  end
 
end
 
villain = create(:supervillain)
villain.profile
# => «Insane business tycoon»
villain.motivation
# => «Building an underwater civilization»
 
cat_friendly_villain = create(:supervillain, cat_owner: true)
cat_friendly_villain.profile
# => «Insane business tycoon – friends with Blofeld»
 
narcissistic_villain = create(:supervillain, megalomaniac: true)
narcissistic_villain.motivation
# => «Building an underwater civilization and saving the world»

Вышеприведенный пример оказался немного более СУХИМ, поскольку не было необходимости создавать отдельные фабрики для суперзлодеев, которые хотят спасти мир или дружат с Блофельдом соответственно. Переходные атрибуты дают вам возможность вносить всевозможные корректировки и избегать создания множества очень похожих заводов.

Атрибуты «Normal» в Factory Girl оцениваются при определении фабрики. Обычно вы предоставляете статические значения в качестве параметров для методов с тем же именем, что и ваши атрибуты. Если вы хотите отложить оценку до последнего возможного момента — когда экземпляр будет создан — вам нужно будет передать атрибутам их значения через блок кода. Ассоциации и динамически создаваемые значения из таких объектов, как объекты DateTime станут вашими самыми частыми клиентами для этого ленивого обращения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
FactoryGirl.define do
 
  factory :exploding_device do
   
    transient do
      countdown_seconds 10*60
      time_of_explosion { Time.now + countdown_seconds }
    end
 
    time_of_explosion { «Exploding in #{countdown_seconds} seconds #{time_of_explosion.strftime(«at %I:%M %p»)}» }
  end
 
end
 
ticking_device = create(:exploding_device)
ticking_device.time_of_explosion
# => «Exploding in 600 seconds at 11:53 PM»

Вероятно, это не тот случай использования, с которым вы будете сталкиваться каждый день, но иногда вы наследуете фабрики от других разработчиков и хотите изменить их — например, если вы используете гем TDD. Если вы чувствуете необходимость настроить эти устаревшие фабрики для лучшего соответствия вашим конкретным тестовым сценариям, вы можете изменить их, не создавая новые и не используя наследование.

Вы делаете это через FactoryGirl.modify , и он должен находиться вне того конкретного блока FactoryGirl.define который вы хотите изменить. Что вы не можете сделать, так это изменить sequence или trait — вы можете переопределить атрибуты, определенные с помощью trait . Обратные вызовы на «оригинальной» фабрике также не будут отменены. Обратный вызов в вашем блоке Factory.modify будет запущен следующим.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
FactoryGirl.define do
  factory :spy do
    name ‘Marty McSpy’
    skills ‘Espionage and infiltration’
    deployment_status ‘Preparing mission’
  end
end
 
FactoryGirl.modify do
  sequence :mission_deployment do |number|
    «Mission #{number} at #{DateTime.now.to_formatted_s(:short)}»
  end
 
  factory :spy do
    name ‘James Bond’
    skills ‘CQC and poker’
    favorite_weapon ‘Walther PPK’
    body_count ‘Classified’
    favorite_car ‘Aston Martin DB9’
    deployment { generate(:mission_deployment) }
    end
end

В приведенном выше примере нам нужно, чтобы наши шпионы были более «изощренными» и использовали лучший механизм для управления развертыванием. Я видел примеры, когда авторам гемов приходилось по-разному обращаться со временем, и когда было удобно модифицировать фабричные объекты, просто переопределяя то, что нужно настроить.

after_save before_validation позволяют вам вводить некоторый код в различные моменты жизненного цикла объекта — например, save , after_save , before_validation и так далее. Rails, например, предлагает целую кучу из них и позволяет новичкам довольно легко злоупотреблять этой мощью.

Имейте в виду, что обратные вызовы, которые не связаны с постоянством объектов, являются известным анти-шаблоном, и это хороший совет, чтобы не пересекать эту линию. Например, может показаться удобным использовать обратный вызов после создания чего-то вроде пользователя для отправки электронных писем или обработки какого-либо заказа, но такого рода вещи вызывают ошибки и создают связи, которые излишне трудно реорганизовать. Возможно, это было одной из причин, по которой Factory Girl «only» предлагает вам пять вариантов обратного вызова для игры:

  • before(:create) выполняет блок кода перед сохранением вашего заводского экземпляра. Активируется при использовании create(:some_object) .
  • after(:create) выполняет блок кода после сохранения вашего заводского экземпляра. Активируется при использовании create(:some_object) .
  • after(:build) выполняет блок кода после того, как ваш фабричный объект был встроен в память. Активируется, когда вы используете как build(:some_object) и create(:some_object) .
  • after(:stub) выполняет блок кода после того, как ваша фабрика создала объект- after(:stub) . Активируется при использовании build_stubbed(:some_object) .
  • custom(:your_custom_callback) выполняет пользовательский обратный вызов без необходимости добавлять before или after .
1
2
3
4
5
6
7
8
9
FactoryGirl.define do
   
  factory :mission do
    objective ‘Stopping the bad dude’
    provided_gadgets ‘Mini submarine and shark gun’
    after(:build) { assign_support_analyst }
  end
 
end

Обратите внимание, что для всех опций обратного вызова внутри блоков обратного вызова у вас будет доступ к экземпляру фабрики через параметр блока. Это пригодится время от времени, особенно с ассоциациями.

1
2
3
4
5
6
7
FactoryGirl.define do
 
  factory :double_agent do
    after(:stub) { |double_agent|
  end
 
end

Внизу у ниндзя есть куча отвратительных метательных звезд (сюрикен). Поскольку у вас есть объект ninja в обратном вызове, вы можете легко назначить метающую звезду принадлежащей ниндзя. Загляните в раздел об ассоциациях, если этот пример оставит вас с парой вопросительных знаков.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
FactoryGirl.define do
 
  factory :ninja do
    name «Ra’s al Ghul»
 
    factory :ninja_with_shuriken do
      transient do
        number_of_shuriken 10
      end
 
      after(:create) do |ninja, evaluator|
        create_list(:shuriken, evaluator.number_of_shuriken, ninja: ninja)
      end
    end
  end
 
  factory :shuriken do
    name ‘Hira-shuriken’
    number_of_spikes ‘Four’
    ninja
  end
 
end
 
ninja = create(:ninja)
ninja.shurikens.length # => 0
 
ninja = create(:ninja_with_shuriken)
ninja.shurikens.length # => 10
 
ninja = create(:ninja_with_shuriken, number_of_shuriken: 20)
ninja.shurikens.length # => 20

Кроме того, через объект оценки у вас также есть доступ к переходным атрибутам. Это дает вам возможность передавать дополнительную информацию, когда вы «создаете» фабричные объекты и вам нужно настроить их на лету. Это дает вам всю гибкость, необходимую для игры с ассоциациями и записи выразительных тестовых данных.

Если вы обнаружите необходимость иметь несколько обратных вызовов на своей фабрике, Factory Girl не встанет у вас на пути — даже несколько типов. Естественно, порядок исполнения сверху вниз.

1
2
3
4
5
6
7
8
9
FactoryGirl.define do
   
    factory :henchman do
    name ‘Mr.
    after(:create) { |henchman|
    after(:create) { send_cleaner }
    end
 
end
1
2
3
4
5
6
7
8
9
FactoryGirl.define do
   
    factory :bond_girl do
    name ‘Lucia Sciarra’
    after(:build) { |bond_girl|
    after(:create) { close_hidden_safe_compartment }
    end
 
end

Дьявол кроется в деталях, конечно. Если вы используете create(:some_object) , будут выполняться обратные вызовы after(:build) и after(:create) .

Несколько стратегий сборки могут быть объединены для выполнения одного и того же обратного вызова.

1
2
3
4
5
6
7
8
FactoryGirl.define do
   
    factory :spy do
    name ‘Marty McFly’
    after(:stub, :build) { |spy|
    end
 
end

И последнее, но не менее важное: вы можете даже настроить что-то вроде «глобальных» обратных вызовов, которые переопределяют обратные вызовы для всех фабрик — по крайней мере, в этом конкретном файле, если вы разделили их на несколько заводских файлов.

заводы / gun.rb

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
FactoryGirl.define do
 
  before(:stub, :build, :create) { |object|
 
    factory :spy_gun do
    name ‘Walther PPK’
    ammunition ‘7.65mm Browning’
    association :owner
 
      factory :golden_gun do
      name ‘Custom Lazar’
      ammunition ’24-carat gold bullet’
        after(:create) { |golden_gun|
      end
    end
 
end

Если вы используете наследование для создания дочерних фабрик, обратные вызовы на родительском объекте также будут наследоваться.

Давайте соберем все это вместе в следующих разделах об ассоциациях и чертах — да, я также пробирался под псевдонимом, потому что это было лучшее место, без прыжков повсюду. Если вы обратили внимание и запомнили материал из первой статьи, то теперь все должно стать на свои места.

Ассоциации важны для каждого уважающего себя веб-приложения, имеющего небольшую сложность. Пост, принадлежащий пользователю, список с множеством оценок и т. Д. — это то, что разработчики хлеба и масла готовят на завтрак в любой день недели. С этой точки зрения становится очевидным, что для более сложных сценариев фабрики должны быть пуленепробиваемыми и простыми в обращении — по крайней мере, чтобы не связываться с вашим модемом TDD.

Эмулировать ассоциации моделей через Factory Girl довольно просто, я бы сказал. Это само по себе удивительно в моей голове. Достижение высокого уровня простоты и удобства для построения сложных наборов данных делает практику TDD простой и намного более эффективной.

Новый Q обладает навыками хакера и должен иметь приличный компьютер, верно? В этом случае у вас есть класс Computer и его экземпляры принадлежат экземплярам класса Quartermaster . Легко, правда?

01
02
03
04
05
06
07
08
09
10
11
12
13
FactoryGirl.define do
 
  factory :quartermaster do
    name ‘Q’
    skills ‘Inventing stuff’
  end
 
  factory :computer do
    model ‘Custom Lenovo ThinkPad W Series’
    quartermaster
    end
 
end

Как насчет чего-то более сложного? Допустим, наши шпионы используют gun cartridges has_many (пулями).

1
2
3
4
5
6
7
class Cartridge < ActiveRecord::Base
  belongs_to :gun
end
 
class Gun < ActiveRecord::Base
  has_many :cartridges
end
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
FactoryGirl.define do
    factory :cartridge do
    caliber ‘7.65’
    gun
  end
 
    factory :gun do
    name ‘Walther PPK’
    ammunition ‘7.65mm Browning’
    caliber ‘7.65’
 
    factory :gun_with_ammo do
      transient do
        magazine_size 10
      end
 
      after(:create) do |gun, evaluator|
        create_list(:cartridge, evaluator.magazine_size, gun: gun)
      end
    end
  end
end

Обратные вызовы очень полезны для ассоциаций, а? Теперь вы можете собрать оружие с боеприпасами или без них. С помощью hash gun: gun вы предоставили фабрике cartridge необходимую информацию для создания ассоциации с помощью foreign_key .

1
2
3
4
5
spy_gun = create(:gun)
spy_gun.cartridges.length # => 0
 
spy_gun_with_ammo = create(:gun_with_ammo)
spy_gun_with_ammo.cartridges.length # => 10

Если вам нужен журнал другого размера, вы можете передать его через свой переходный атрибут .

1
2
big_magazine_gun = create(:gun_with_ammo, magazine_size: 20)
big_magazine_gun.cartridges.length # => 20

Так что насчет разных стратегий сборки? Там не было что-то подозрительное? Хорошо, вот что вам нужно запомнить: если вы используете create для связанных объектов, оба они будут сохранены. Так что create(:quartermaster) соберет и сохранит как Q, так и его ThinkPad.

Тогда мне лучше использовать build , если я хочу избежать попадания в базу данных, верно? Хорошая идея, но в нашем примере build применима только к администратору квартир — связанный computer все равно будет сохранен. Я немного хитрый, я знаю. Вот что вы можете сделать, если вам не нужно сохранять связанный объект — вы указываете стратегию сборки, необходимую для вашей ассоциации.

01
02
03
04
05
06
07
08
09
10
11
12
13
FactoryGirl.define do
 
  factory :quartermaster do
    name ‘Q’
    skills ‘Inventing stuff’
  end
 
  factory :computer do
    model ‘Custom Lenovo ThinkPad W Series’
    association :quartermaster, strategy: :build
    end
 
end

Вы называете связанный объект фабрики и передаете хеш со своей стратегией сборки. Вам нужно использовать явный вызов ассоциации, чтобы это работало. Пример ниже не сработает.

1
2
3
4
factory :computer do
  model ‘Custom Lenovo ThinkPad W Series’
  quartermaster, strategy: :build
end

Теперь оба объекта используют build и ничего не сохраняется в базе данных. Мы можем проверить это предположение с помощью new_record? , который возвращает true если экземпляр не был сохранен.

1
2
3
4
thinkpad = build(:computer)
 
thinkpad.new_record?
thinkpad.quartermaster.new_record?

Пока мы на этом, с помощью явного вызова ассоциации вы также можете ссылаться на различные фабричные имена и изменять атрибуты на лету.

01
02
03
04
05
06
07
08
09
10
11
12
FactoryGirl.define do
 
  factory :quartermaster do
    name ‘Q’
  end
 
  factory :computer do
    model ‘Custom Lenovo ThinkPad W Series’
    association :hacker, factory: :quartermaster, skills: ‘Hacking’
    end
 
end

Давайте закроем эту главу примером, который является полиморфным .

01
02
03
04
05
06
07
08
09
10
11
class Spy < ActiveRecord::Base
  belongs_to :spyable, polymorpic: true
end
 
class MIFive < ActiveRecord::Base
  has_many :spies, as: :spyable
end
 
class MISix < ActiveRecord::Base
  has_many :spies, as: :spyable
end
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
FactoryGirl.define do
 
  factory :mifive do
    name ‘Military Intelligence, Section 5’
    principal_activity ‘Domestic counter-intelligence’
  end
 
  factory :misix do
    name ‘Military Intelligence, Section 6’
    principal_activity ‘Foreign counter-intelligence’
 
  end
 
  factory :mifive_spy, class: Spy do
    name ‘005’
    association :spyable, factory: :mifive
  end
 
  factory :misix_spy, class: Spy do
      name ‘006’
    association :spyable, factory: :misix
  end
 
end
 
# MI5 agents
mifive = create(:mifive)
mifive_spy = create(:mifive_spy)
mifive.spies << mifive_spy
 
mifive.name # => «Military Intelligence, Section 5»
mifive_spy.name # => ‘005’
mifive.spies.length # => 1
mifive.spies.first.name # => ‘005’
 
 
# MI6 agents
misix = create(:misix)
misix_spy_01 = create(:misix_spy, name: ‘007’)
misix_spy_02 = create(:misix_spy)
misix.spies << misix_spy_01
misix.spies << misix_spy_02
 
misix.name # => «Military Intelligence, Section 6»
misix.spies.length # => 2
misix_spy_01.name # => ‘007’
misix_spy_02.name # => ‘006’
misix.spies.first.name # => ‘007’

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

Псевдонимы для ваших фабрик позволяют вам быть более выразительными в контексте, в котором вы используете ваши фабричные объекты. Вам нужно только предоставить хэш альтернативных имен, который лучше описывает отношения между связанными объектами.

Допустим, у вас есть фабрика :agent фабрика :law_enforcement_vehicle . Разве не было бы неплохо назвать агента как :owner в контексте этих автомобилей? В приведенном ниже примере я сравнил его с примером без псевдонима.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
FactoryGirl.define do
 
  factory :agent, aliases: [:owner] do
    name ‘Fox Mulder’
    job ‘Chasing bad dudes’
    special_skills ‘Investigation and intelligence’
     
    factory :double_O_seven do
      name ‘James Bond’
    end
  end
  
  factory :law_enforcement_vehicle do
    name ‘Oldsmobile Achieva’
    kind ‘Compact car’
    :owner
  end
 
  factory :spy_car do
    name ‘Aston Martin DB9’
    kind ‘Sports car’
    double_O_seven
  end
 
end

Не забудьте добавить двоеточие перед псевдонимом factory ( :owner ), когда вы используете их для ассоциаций на ваших фабриках. В этих случаях документация и многие посты блога используют их без двоеточий. Все, что вы получите, это, вероятно, NoMethodError потому что вам сейчас не хватает метода установки для этого псевдонима. (Я бы лучше открыл запрос на извлечение.) В первый раз, когда я столкнулся с этим, он озадачил меня и потребовал немного, чтобы пройти его. Не забывайте иногда выборочно не доверять документации и сообщениям в блогах. С уважением, конечно.

Я думаю, вы согласитесь, что используя псевдонимы, он не только лучше читается, но и дает вам или человеку, который идет за вами, немного больше информации о рассматриваемых объектах. Да, вам нужно использовать множественное число :aliases также, если у вас есть только один псевдоним.

Вы могли бы написать это немного по-другому — намного более многословно.

01
02
03
04
05
06
07
08
09
10
11
factory :agent, aliases: [:mulder] do
  name ‘Fox Mulder’
  job ‘Chasing bad dudes’
  special_skills ‘Investigation and intelligence’
end
 
factory :law_enforcement_vehicle do
  name ‘Oldsmobile Achieva’
  kind ‘Compact car’
  association :owner, factory: :agent
end

Ну, не так ли аккуратно, не так ли?

Конечно, вы можете использовать эти псевдонимы и для «сборки» фабричных объектов сразу.

1
2
fbi_agent = create(:mulder)
fbi_agent.name # => ‘Fox Mulder’

В контексте комментариев a :user может называться :commenter , в случае a :crime a :user может быть псевдонимом a :suspect и т. Д. На самом деле это не ракетостроение, а скорее удобный синтаксический сахар, который уменьшает искушение к дублированию.

Это одна из моих любимых вещей о Factory Girl. Короче говоря, черты являются лего-подобными блоками для построения ваших фабрик и сочетания поведения. Это разделенные запятыми списки признаков / атрибутов символов, которые вы хотите добавить к определенной фабрике, и они также определены в файлах вашей фабрики.

На мой взгляд, trait — это самая мощная и удобная функция, позволяющая сохранять заводские данные СУХИМЫМИ и в то же время выразительными. Это позволяет объединять группы атрибутов вместе, присваивать им отдельные имена и повторно использовать их, где вам угодно. Помните, когда я призывал вас определять заводские объекты с голыми костями? Черты помогут вам достичь именно этого, не жертвуя при этом удобством.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
FactoryGirl.define do
     
  factory :spy_car do
    model ‘Aston Martin DB9’
    top_speed ‘295 km/h’
    build_date ‘2015’
    ejection_seat true
 
    trait :submarine do
      ejection_seat false
      water_resistant ‘100 m’
      submarine_capabilities true
      air_independent_propulsion true
    end
     
    trait :weaponized do
      rockets true
      number_of_rockets ’12’
      machine_gun true
      rate_of_fire ‘1,500 RPM’
      tank_armour true
    end
     
    trait :cloaked do
      active_camouflage true
      radar_signature ‘reduced’
      engine ‘silenced’
    end
     
    trait :night_vision do
      infrared_sensors true
      heads_up_display true
    end
  end
 
end

Как вы можете видеть, если вы хотите изменить некоторые атрибуты, которые распределены по нескольким объектам, вы можете сделать это сейчас в одном центральном месте. Нет необходимости в дробовике. Управление состоянием через черты не может быть более удобным.

С этой настройкой вы можете создавать довольно сложные шпионские машины, смешивая различные наборы атрибутов, как вам угодно — без дублирования чего-либо путем создания всевозможных новых фабрик, которые учитывают все различные варианты, которые вам нужны.

1
2
3
invisible_spy_car = create(:spy_car, :cloaked, :night_vision)
diving_spy_car = create(:spy_car, :submarine, :cloaked)
tank_spy_car = create(:spy_car, :weaponized, :night_vision)

Вы можете использовать черты с create , build , build_stubbed и attributes_for . Если Q становится умным, вы также можете переопределить отдельные атрибуты одновременно, передав хеш.

1
build(:spy_car, :submarine, ejection_seat: true)

Для комбинаций признаков, которые очень часто встречаются на определенной фабрике, вы также можете создавать дочерние фабрики с именами, которые лучше всего представляют различные комбинации наборов данных. Таким образом, вы объединяете их характеристики только один раз, а не все время, когда вы создаете тестовые данные.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
FactoryGir.define do
 
  factory :spy_car do
    model ‘Aston Martin DB9’
    top_speed ‘295 km/h’
    build_date ‘2015’
    ejection_seat true
 
    trait :submarine do
      …
    end
     
    trait :weaponized do
      …
    end
     
    trait :cloaked do
      …
    end
     
    trait :night_vision do
      …
    end
  end
 
    factory :invisible_spy_car, traits: [:cloaked, :night_vision]
    factory :diving_spy_car, traits: [:submarine, :cloaked]
    factory :tank_spy_car, traits: [:weaponized, :night_vision]
    factory :ultimate_spy_car, traits: [:cloaked, :night_vision, :submarine, :weaponized]
 
end

Это позволяет вам создавать эти объекты намного более кратко, и это также более читабельно.

1
2
build_stubbed(:invisible_spy_car)
create(:ultimate_spy_car)

Вместо того:

1
2
build_stubbed(:spy_car, :cloaked, :night_vision)
create(:spy_car, :cloaked, :night_vision, :submarine, :weaponized)

Читается намного лучше, нет? Особенно, когда имена переменных не задействованы.

Вы даже можете повторно использовать черты в качестве атрибутов на других чертах и ​​фабриках. Если вы определяете одни и те же атрибуты для нескольких признаков, последний, конечно, получает приоритет.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
FactoryGirl.define do
 
  factory :spy_car do
    model ‘Aston Martin DB9’
    top_speed ‘295 km/h’
    build_date ‘2015’
    ejection_seat true
 
    trait :submarine do
      …
    end
     
    trait :weaponized do
      …
    end
     
    trait :cloaked do
      …
    end
     
    trait :night_vision do
      …
    end
 
    trait :mobile_surveillance do
      cloaked
      night_vision
      signal_detector true
      signal_analyzer true
      wifi_war_driver true
      license_plate_reader true
      mini_drone true
    end
  end
 
  factory :ultimate_spy_car, parent: :spy_car do
    car_plane true
    submarine
    weaponized
    mobile_surveillance
  end
 
end

Обратите внимание на черту mobile_surveillance , которая использует черты night_vision и night_vision — в основном, как атрибут. Кроме того, фабрика ultimate_spy_car , которую я на этот раз отделил от определения фабрики spy_car для забавы, повторно использует все черты плюс дополнительный атрибут, который заставляет его летать. Чистая магия кино — или, может быть, мне стоит сказать «Фабричная девушка».

create_list и build_list также могут использовать черты. Второй параметр должен быть числом заводских экземпляров, которое вы хотите.

1
2
create_list(:spy_car, 3, :night_vision)
build_list(:spy_car, 4, :submarine, :cloaked)

Не было бы здорово использовать ассоциации с чертами? Конечно, вы можете аккуратно упаковать обратные вызовы и ассоциации. Duh!

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
FactoryGirl.define do
 
  factory :cartridge do
    kind ‘Small calliber pistol ammunition’
    caliber ‘7.65’
    projectile ‘Lead’
    gun
   
    factory :golden_cartridge do
      projectile ‘Gold’
      association :gun, :golden
    end
  end
   
  factory :gun do
    name ‘Walther PPK’
    ammunition ‘7.65mm Browning’
    caliber ‘7.65’
   
    transient do
      magazine_size 10
    end
   
    trait :golden do
      name ‘Custom Lazar’
      ammunition ’23-carat gold bullet’
    end
   
    trait :with_ammo do
      after(:create) do |gun, evaluator|
        create_list(:cartridge, evaluator.magazine_size, gun: gun)
      end
    end
   
    trait :with_golden_ammo do
      after(:create) do |golden_gun, evaluator|
        create_list(:golden_cartridge, evaluator.magazine_size, gun: golden_gun)
      end
    end
  end
end

Как их использовать, должно быть скучно сейчас.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cartridge = create(:cartridge)
cartridge.projectile # => ‘Lead’
cartridge.gun.name # => ‘Walther PPK’
cartridge.gun.ammunition # => ‘7.65mm Browning’
cartridge.gun.caliber # => ‘7.65’
 
golden_cartridge = create(:golden_cartridge)
golden_cartridge.projectile # => ‘Gold’
golden_cartridge.gun.name # => ‘Custom Lazar’
golden_cartridge.gun.ammunition # => ’23-carat gold bullet’
golden_cartridge.gun.caliber # => ‘7.65’
 
gun_with_ammo = create(:gun, :with_ammo)
gun_with_ammo.name # => ‘Walther PPK’
gun_with_ammo.ammunition # => ‘7.65mm Browning’
gun_with_ammo.cartridges.length # => 10
gun_with_ammo.cartridges.first.projectile # => ‘Lead’
gun_with_ammo.cartridges.first.caliber # => ‘7.65’
 
golden_gun_with_golden_ammo = create(:gun, :golden, :with_golden_ammo)
golden_gun_with_golden_ammo.name # => ‘Custom Lazar’
golden_gun_with_golden_ammo.ammunition # => ’24-carat gold bullet’
golden_gun_with_golden_ammo.cartridges.length # => 10
golden_gun_with_golden_ammo.cartridges.first.projectile # => ‘Gold’
golden_gun_with_golden_ammo.cartridges.first.caliber # => ‘7.65’

Последние слова мудрости: изменения — ваш постоянный спутник — необходимость менять атрибуты или типы данных происходит постоянно. Дизайнерские решения, подобные этим, развиваются. Черты облегчат эту проблему и помогут вам управлять вашими наборами данных.

Представьте, что вы использовали хэш опций для создания экземпляров, и это требование полностью изменилось. Сколько потенциальных мест в ваших тестах может сломаться и теперь будет нуждаться во внимании? Откровенно говоря, trait является очень эффективным инструментом для устранения дублирования в вашем наборе тестов. Но при всем этом удобстве не ленитесь и забудьте о своих юнит-тестах на столбцах, которые представлены вашими чертами! Таким образом вы предоставляете им ту же степень заботы, что и атрибуты «голые кости», необходимые для действительных объектов.

В Factory Girl есть еще кое-что, и я уверен, что вы теперь более чем хорошо подготовлены, чтобы собирать кусочки, когда они вам нужны. Весело играть с этим драгоценным камнем. Я надеюсь, что ваши привычки TDD выиграют от этого.