This is a CLI tool that generates factories for models generated by sqlboiler.
- Install
sqlboiler - Install your database driver for
sqlboiler. - Generate your models. Link
- Install
boilingfactory:go install github.com/stephenafamo/boilingfactory - Generate factory:
go run github.com/stephenafamo/boilingfactory psql
// Create a new model
license, _ := factory.CreateLicense()
// create with options
pilot, _ := factory.CreatePilot(
// Set a Column with a value
factory.PilotID(120),
// Use a callback with the xxFunc variants
factory.PilotNameFunc(func() (string, error) {
var randomName string
// somehow generate a new name
return randomName, nil
}),
// RELATIONSHIPS
// Attach an existing model
factory.PilotWithLicense(license),
// Build models dynamically
factory.PilotWithNewJets(5),
)
// Insert can be used with any existing model
// Before Inserting: It checks if there are any foreign keys that are not
// set and do not have a default and creates models for it
// After Inserting: It saves any related models found in the relationship struct
_ = factory.InsertPilot(ctx, db, pilot)
// Set defaults on the factory
factory.SetBasePilotMod(factory.PilotMods{
// Attach an existing model
factory.PilotWithNewLicense(),
// Build models automatically
factory.PilotWithNewJets(5),
})
factory.SetBaseJetMod(factory.JetMods{
factory.JetColor("blue"),
})
// Now any new pilot will be created with a new license and 5 blue jets
pilot2, _ := factory.CreateAndInsertPilot(ctx, db)The program accepts these flags to overwrite any configuration.
--sqlboiler-models: The package of your generated models. Needed to import them properly in the factory. DEFAULT:current/go/module/models.--config: Configuration file path. DEFAULT:sqlboiler.toml--outputor-o: The name of the folder to output to. DEFAULT:factory--pkgnameor-p: The name you wish to assign to your generated package. DEFAULT:factory--wipe: Delete the output folder (rm -rf) before generation to ensure sanity. DEFAULTfalse--version: Print the versiondebugord: Debug mode prints stack traces on error. DEFAULTfalse
They can also be set in the config file, or as environment variables
To attempt to match your generated model options, the defualt the sqlboiler configuration files are used: sqlboiler.toml or json or yaml.
NOTE: If you have customized the output folder or pkgname in your sqlboiler config file and you are passing the same file to boilingfactory, you should overwrite them using the -o and p flags respectively.
Mods are central to the design of the factories. Each model has its own mod type, which is an interface that applies a change to the model.
You can set base mods for each model on the factory, and pass additional mods when creating an individual object.
type PilotMod interface {
Apply(*models.Pilot) error
}There are generated mods for changing the column or relationship of a model, and some helper types to make it easier to implement custom mods.
For every column in a model, several fuctions are created:
One take a value and returns a mod that sets the column's value,
factory.PilotName("Stephen") // returns a PilotModThe other takes a callback function that is called to generate new values. This plays very nicely with generating random data.
factory.PilotNameFunc(func () (string, error) {
return randomdata.SillyName(), nil
})For every relationship in the model, several functions are created. Each of these generated functional mods do the following:
- They place the relationship in the
.Rstruct of both the local model and the relation.- If it is a to-one relationship, this will overwrite any currently set models
- For a to-many relationship,
AddXXXmethods are generated which will appended relations to the existing ones instead of overwriting.
- They set the value of the foreign key of the local model or related model as applicable.
One Mod is generated to set the relationship passing an already created model:
factory.PilotWithLicense(licenseModel) // adds the license relationshipAs with the column mods, there is also a variant that takes a callback:
factory.PilotWithLicenseFunc(f func() (*models.License, error) {
var license *models.License // do some fancy generation
return license, nil
})Finally, there is a variant that creates the relation on the fly using a supplied factory.
Pass nil as the factory to use the default global factory. See the section on Multiple Factories.
You can pass some extra mods that would be applied to the created relations.
// Using the default factory
factory.PilotWithNewLicense(nil, factory.LicenseNumber("random"))
var myCustomFactory *factory.Factory
factory.PilotWithNewLicense(myCustomFactory, factory.LicenseNumber("random"))NOTE: In addition, a To-Many relationship will also have Add relationship mods:
factory.PilotWithJets(jetModelSlice) // Overwrites all current relationships with this
factory.PilotAddJets(jetModelSlice) // Adds the jets to the current onesThere are also helper types to easily create mods.
The xxxModFunc type helps convert a function like func(*models.Pilot) error to a Mod
type PilotModFunc func(*models.Pilot) error
func (f PilotModFunc) Apply(n *models.Pilot) error {
return f(n)
}The xxxMods type takes a list of mods and applies them as a single mod
type PilotMods []PilotMod
func (mods PilotMods) Apply(n *models.Pilot) error {
for _, f := range mods {
err := f.Apply(n)
if err != nil {
return err
}
}
return nil
}If you needed to, you could implement your own mods
Models are created using the Create function.
These functions can take a variable number of Mods which it would apply to a fresh object before returning. If there are any base mods defined on the factory, the base mods will be applied first.
Multiple models can be created with the CreatePlural() function. For example, if we have a Jet models, CreateJet and CreateJets will be generated for use.
pilot, err := factory.CreatePilot()
pilotSlice, err := factory.CreatePilots(10)NOTE: This does not save the model in the database. To do that you should use the Insert or CreateAndInsert functions.
The Insert() function saves the model in the database.
- For every foreign key, if the value is not set and the field does not have a default value, a related model is built (using the default mods) and attached to the model before insertion.
- For dependent relations, models in the
.Rfield are also inserted. So if you have a mod that builds and attaches related models, this will also save them.
Multiple models can be inserted with the InsertPlural() function. For example, if we have a Jet models, InsertJet and InsertJets will be generated for use.
err := factory.InsertPilot(ctx, db, pilot)
err := factory.InsertPilots(ctx, db, pilotSlice)For convenience, there are the CreateAndInsert functions that do both creation and insertion in a single step. These functions also accept a variable number of Mods.
pilot, err := factory.CreateAndInsertPilot(ctx, db)
pilotSlice, err := factory.CreateAndInsertPilots(ctx, db, 10)There is a global factory which is used when the Create or Insert methods are called,
but we can also create a custom Factory object and call the methods on it.
var myCustomFactory = &factory.Factory{}
pilot, _ := myCustomFactory.CreatePilot()
jet, _ := myCustomFactory.CreateAndInsertJet(ctx, db)These methods works the same way as the package functions. Defaults can also be set on custom factories.
You can set default values for models by setting a base mod to be applied to all created models in a factory.
// For the global factory
factory.SetBasePilotMod(
factory.PilotName("Stephen"),
)
pilot, _ := factory.CreatePilot() // pilot.Name is "Stephen"
// For a custom factory
var myCustomFactory = &factory.Factory{}
myCustomFactory.SetBasePilotMod(
factory.PilotName("John"),
)
pilot2, _ := factory.CreatePilot() // pilot2.Name is "John"PRs welcome.