My interpolation : I'm not sure I'm allowed to use it with Faker
Interpolation works with any expression. Even multiple statements. "#{ a = 1 + 1; b = 2 + 2; a + b}"
will produce "6"
. But don't do that, it's confusing.
name: "#{Faker::Verb.ing_form} service",
is equivalent to
name: Faker::Verb.ing_form.to_s + " service"
In a few places you're missing the interpolation.
bio = "Hi, I'm #{user.username}. Faker::Movie.quote"
That should be...
bio = "Hi, I'm #{user.username}. #{Faker::Movie.quote}"
In other places the interpolation is not necessary.
search_terms: ["#{Faker::Verb.ing_form}"]
Faker::Verb.ing_form
already returns a string and does not need to be interpolated.
search_terms: [Faker::Verb.ing_form]
I'd like to use the same username given in username and email, am I doing right?
No, you can't reference user
until it's already been created. If you try you will get something like undefined method `username' for nil:NilClass
. user
is nil
and nil
doesn't have a method called username
.
Instead you could make a user, set its email, then save.
user = User.new(
username: Faker::DcComics.hero,
password: "123456",
avatar: Faker::LoremFlickr.image(size: "900x900", search_terms: ['dccomics'])
)
user.email = "#{user.username}@gmail.com"
user.bio = "Hi, I'm #{user.username}. #{Faker::Movie.quote}"
user.save!
Or you can make the username first and store it in a variable to reference.
username = Faker::DcComics.hero
user = User.create!(
username: username,
password: "123456",
email: "#{username}@gmail.com",
bio: "Hi, I'm #{username}. #{Faker::Movie.quote}",
avatar: Faker::LoremFlickr.image(size: "900x900", search_terms: ['dccomics'])
)
My user model doesn't have an avatar in the DB table but has_one_attached :avatar, may I give it a seed?
I'm not super familiar with attachments in Rails, but I think it should work as you've written.
I'd like to give a random user_id from one of those created above to my service model, I don't really know how could I proceed...
The simple thing is to use sample
.
user = User.all.sample
We can make this a little better using pluck
to fetch just the ID.
user = User.pluck(:id).sample
However, this loads all Users. For a small test database that's not a problem. For production code that's a huge waste. Instead we can use order by random() limit 1
. In Rails 6 it's kinda clunky.
User.order(Arel.sql('RANDOM()')).pluck(:id).first
See this answer for an explanation.
And note, if you already have a User loaded, pass the User, not its ID. This avoids Service having to load the User again.
10.times do
user = User.order(Arel.sql('RANDOM()')).pluck(:id).first
service = Service.create!(
...
user: user
)
end
But there's a better way...
Related question about my service model (has_many_attached :photos). How can I generate several pictures for it?
And here we reach the real leap. Instead of generating static test data, use a Factory. FactoryBot makes it easy to define "factories" to generate test data using Faker. You're already halfway there. And FactoryBotRails lets FactoryBot understand how to Rails models.
A User factory would look like this.
FactoryBot.define do
factory :user do
username { Faker::DcComics.hero }
password { "123456" }
email { "#{username}@gmail.com" }
bio { "Hi, I'm #{username}. #{Faker::Movie.quote}" }
avatar { Faker::LoremFlickr.image(size: "900x900", search_terms: ['dccomics']) }
end
end
Note that we can make an email
based on the username
. Attributes can reference other attributes. That's because the attributes are actually little functions kinda like this:
class UserFactory do
def username
@username ||= Faker::DDcComics.hero
end
def email
@email ||= "#{username}@gmail.com"
end
end
And to make 10 of them...
users = FactoryBot.create_list(:user, 10)
You can override the defaults. If you want a user with a certain email address...
user = FactoryBot.create(:user, email: "c.kent@dailyplanet.com")
Since you can make users on the fly, you no longer need to seed specific data.
Now Service. Let's look at the Service factory. To make a Service we need Photos.
factory :service do
name { "#{Faker::Verb.ing_form} service" }
description { "I'll use my #{Faker::Superhero.power} to accomplish the mission" }
address { "#{Faker::Address.street_address}, #{Faker::Address.city}" }
user
# photos is presumably has-many_attached and so takes an Array.
photos {
[
Faker::LoremFlickr.image(
size: "900x900",
search_terms: [Faker::Verb.ing_form]
)
]
}
end
Have a look at user
. FactoryBot will automatically fill that in with a User made using your User factory. The problem of making a Service with a random User is solved.
Our final step is to DRY up our factories by defining one to make a photo.
factory :photo, class: Faker::LoremFlickr do
size { "900x900" }
search_terms { Faker::Lorem.words }
initialize_with do
Faker::LoremFlickr.image(attributes)
end
end
This is not a Rails model, so we need to teach FactoryBot how to make a photo by providing it with its class and how to initialize it. attributes
is a Hash containing size and search_terms.
Also note that I used more generic search terms. Factories which need specific search terms will provide their own.
We can make as many photos as we want.
photos = FactoryBot.build_list(:photos, 3)
Using the Photo factory we can DRY up the User and Service factories. Here it is all together.
FactoryBot.define do
factory :photo, class: Faker::LoremFlickr do
size { "900x900" }
search_terms { Faker::Lorem.words }
initialize_with do
Faker::LoremFlickr.image(attributes)
end
end
factory :user do
username { Faker::DcComics.hero }
password { "123456" }
email { "#{username}@gmail.com" }
bio { "Hi, I'm #{username}. #{Faker::Movie.quote}" }
avatar { build(:photo, search_terms: ['dccomics']) }
end
factory :service do
name { "#{Faker::Verb.ing_form} service" }
description { "I'll use my #{Faker::Superhero.power} to accomplish the mission" }
address { "#{Faker::Address.street_address}, #{Faker::Address.city}" }
user
photos {
build_list(:photo, 3, search_terms: [Faker::Verb.ing_form])
}
end
end
And now, when you need a service to test with, you can ask for one.
service = FactoryBot.create(:service)
Want a service with a different name?
service = FactoryBot.create(:service, name: "Universal Exports")
What about with no photos?
service = FactoryBot.create(:service, photos: [])
That's just scratching the surface of what FactoryBot can do.
Factories are far more flexible and convenient than seed files.