Ruby
Ruby on Rails
Quelques astuces avec FactoryBot
Voir toutes les astuces disponibles en un coup d'œil
- Générer un identifiant unique sur une instance
- Utiliser un alias pour nommer une factory
- Créer une factory avec une relation one-to-many
- Créer une factory avec une relation many-to-many
- Outrepasser les validations à la création
- Utiliser un nom de factory personnalisé
- Simuler l’usage de ActiveStorage
Générer un identifiant unique sur une instance
# spec/fixtures/usernames.rb
USERNAMES = ['hermione.granger',
'ron.weasley',
'harry.potter',
'fred.weasley',
'georges.weasley',
'luna.lovegood',
'ginny.weasley',
'draco.malfoy',
'albus.dumbledore',
'hannah.abbott',
'godric.gryffindor',
'helga.hufflepuff',
'neville.longbottom',
'remus.lupin']
# spec/factory/user.rb
require 'fixtures/usernames'
FactoryBot.define do
factory :user do
sequence :email do |n|
username = USERNAMES[n] || "wizard_#{n}"
"#{username}@hogwarts.wiz"
end
end
end
Utiliser un alias pour nommer une factory
Cette technique sert dans les cas où la classe associée à la factory spécifie une relation à une autre classe également associée à une factory.
# spec/factory/user.rb
FactoryBot.define do
factory :user, aliases: [:owner] do
email
end
end
Créer une factory avec une relation one-to-many
Cette technique sert bien dans les cas où la classe spécifie une association one-to-many avec le helper has_many
.
# spec/factory/user.rb
FactoryBot.define do
factory :user do
trait :with_books do
items { create_list :book, 3 }
end
end
end
Créer une factory avec une relation many-to-many
Avec des factories associées qui n’existent pas et doivent être créées en même temps :
# spec/factory/user.rb
FactoryBot.define do
factory :user do
trait :with_bedrooms do
after :create do |user|
user.bedrooms = create_list :bedroom, 3
end
end
end
end
Avec des factories associées existantes :
# spec/factory/user.rb
FactoryBot.define do
factory :user do
transient do
taught_by nil
end
after(:create) do |user, factory|
if taught_by
create(:mentorship, user: user, professor: factory.taught_by)
end
end
end
end
Outrepasser les validations à la création
Pour faciliter l’écriture et la lecture d’une spec, on a parfois besoin de forcer la création d’une instance de factory sans passer par toutes les validations de la classe associée.
# spec/factory/user.rb
FactoryBot.define do
factory :user do
# …
to_create { |instance| instance.save(validate: false) }
end
end
Utiliser un nom de factory personnalisé
# spec/factory/user.rb
FactoryBot.define do
factory :bare_user, class: User do
# …
end
Simuler l’usage de ActiveStorage
# spec/factory/user.rb
FactoryBot.define do
factory :user do
# …
after :build do |user|
user.image.attach(
io: File.open('spec', 'fixtures', 'files', 'YOUR_FILE.jpg'),
filename: 'YOUR_FILE.jpg',
content_type: 'image/jpeg',
)
end
end
end
Quelques astuces avec ActiveStorage
# SEE CONFIGURATION ON: https://github.com/rails/rails/blob/main/guides/source/configuring.md#configuring-active-storage
# Delete variations when config.active_storage.track_variants = true
my_model_instance.image.service.delete_prefixed(my_model_instance.image.key)
# List all variations on a model
# Here, `my_model_attachment_name` refers to the name I give to the attachment with the `:has_one_attached` macro.
# cf. https://edgeguides.rubyonrails.org/active_storage_overview.html
my_model_attachment_name = "image"
my_model_variations_transformations = MyModel.attachment_reflections[my_model_attachment_name].variants.keys.map do |variant_name|
MyModel.first.public_send(my_model_attachment_name).variant(variant_name).variation.transformations
end
# Example of an output
# =>
# [{:format=>"jpg", :resize_to_fit=>[400, 400], :saver=>{:quality=>30}},
# {:format=>"jpg", :resize_to_fit=>[600, 600], :saver=>{:quality=>30}},
# {:format=>"jpg", :resize_to_limit=>[800, 800], :saver=>{:quality=>70}}]
# List all existing variation digests
current_variant_variation_digests = my_model_variations_transformations.map do |transformations|
# Use the same digest as ActiveRecord::Variation
# cf. https://github.com/rails/rails/blob/main/activestorage/app/models/active_storage/variation.rb#L78
OpenSSL::Digest::SHA1.base64digest Marshal.dump(transformations.symbolize_keys)
end
# Count unused old variants
all_variation_digests = ActiveStorage::VariantRecord.all.distinct.pluck(:variation_digest)
useless_digests = all_variation_digests - current_variant_variation_digests
unused_old_variants = ActiveStorage::VariantRecord.where(variation_digest: useless_digests)
unused_old_variants_count = unused_old_variants.count
# Delete unused variant records and their files from my storage
unused_old_variants.destroy_all
# Only delete files of unused blobs on your storage
useless_digests.each do |digest|
ActiveStorage::Blob.service.delete(digest)
end
# Use cached public files with S3
# Add this to config/storage.yml
# public: true,
# upload:
# cache_control: public, max-age=<%= whatever I want %>, immutable
# If this is only for the variants, destroy all the variants and reupload them using variant.processed.url
# If this is for the original attachments and the variants, purge the attachments (cf. https://github.com/rails/rails/blob/3ea99f53fafbcacfda58b11e2c0537fc043742f2/activestorage/lib/active_storage/attached/one.rb#L7)
# RSpec config
config.before do
ActiveStorage::Current.urls_options = {
host: "example.com",
}
end