Awesome
BoilingFactory
This is a CLI tool that generates factories for models generated by sqlboiler
.
Installation
- 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
A little taste
// 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)
Configuration
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
--output
or-o
: The name of the folder to output to. DEFAULT:factory
--pkgname
or-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 versiondebug
ord
: 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
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.
Column 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 PilotMod
The 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
})
Relationship Mods
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
.R
struct 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,
AddXXX
methods 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 relationship
As 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 ones
Mod helper types
There 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
Usage
Creating models
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.
Inserting models
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
.R
field 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)
Create and Insert
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)
Multiple Factories
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.
Setting Defaults
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"
Contributing
PRs welcome.