Skip to main content

Configuration

Driver specific configuration option and instructions can be found on their individual pages.

Configuration is done in a bobgen.yaml file in the same directory. A different configuration file can be passed with the -c or --config flag.

The configuration is marshalled in to the Config struct.

// Config for the running of the commands
type Config struct {
// Struct tags to generate
Tags []string `yaml:"tags"`
// Disable generating factory for models.
NoFactory bool `yaml:"no_factory"`
// Disable back referencing in the loaded relationship structs
NoBackReferencing bool `yaml:"no_back_referencing"`
// Delete the output folder (rm -rf) before generation to ensure sanity
Wipe bool `yaml:"wipe"`
// Decides the casing for go structure tag names. camel, title or snake (default snake)
StructTagCasing string `yaml:"struct_tag_casing"`
// Relationship struct tag name
RelationTag string `yaml:"relation_tag"`
// List of column names that should have tags values set to '-' (ignored during parsing)
TagIgnore []string `yaml:"tag_ignore"`

Types drivers.Types `yaml:"types"` // register custom types
Aliases Aliases `yaml:"aliases"` // customize aliases
Constraints Constraints `yaml:"constraints"` // define additional constraints
Relationships Relationships `yaml:"relationships"` // define additional relationships

Replacements []Replace `yaml:"replacements"`
Inflections Inflections `yaml:"inflections"`

// Customize the generator name in the top level comment of generated files
// >> Code generated by **GENERATOR NAME**. DO NOT EDIT.
// defaults to "BobGen [driver] [version]"
Generator string `yaml:"generator"`
}
NameDescriptionDefault
tagsStruct tags to generate[]
no_factoryDisable generation factories for modelsfalse
no_back_referencingIf this is set to true, when relationships are loaded, the parent is not added to the loaded object's relationsfalse
wipeIf to delete the output folder before generationfalse
struct_tag_casingDecides the casing for go structure tag names. camel, title or snake (default snake)"snake"
relation_tagStruct tag for the relationship object"-"
tag_ignoreList of column names that should have tags values set to '-'[]
typesRegister custom types. See more"{}
aliasesCustomize aliases. See more{}
constraintsDefine additional constraints. See more{}
relationshipsDefine additional relationships. See more{}
replacementsDefine replacements for types. See more[]
inflectionsDefine inflections for pluralization. See more{}
generatorCustomize the generator name in the top level comment of generated files""

Aliases

Names are automatically generated for you. If you name your database entities properly you will likely have descriptive names generated in the end. However in the case where the names in your database are bad AND unchangeable, or bob's inference doesn't understand the names you do have (even though they are good and correct) you can use aliases to change the name of your tables, columns and relationships in the generated Go code.

Note: It is not required to provide all parts of all names. Anything left out will be inferred by default.

# Although team_names works fine without configuration, we use it here for illustrative purposes
aliases:
team_names:
up_plural: "TeamNames"
up_singular: "TeamName"
down_plural: "teamNames"
down_singular: "teamName"
columns: # Columns can be aliased by name
uuid: "ID"
relationships: # Relationships can be aliased by name
team_id_fkey: "Owner"

Types

Custom types can be registered with the types key.
This will also allow you to edit the configuration of existing types, for example, to change how it is randomized by default.

When defining a type, you should provide the randomExpr. This is an expression that returns a random value of the type. It is used in factory.random[T]().

There are things to note about writing the random expression:

  • The expression must return a value of type T
  • There is an existing variable of f which is of type *faker.Faker

To prevent generating a test for the random expression, set no_randomization_test to true. This is useful for low-cardinality types like bool.

When testing the random expression, the value returned is compared to generated previous values using go-cmp. Sometimes, it is necessary to pass options, and we can do that with the cmp_options and cmp_options_imports fields.

types:
pq.BoolArray:
# Imports for the type
imports: ['"github.com/lib/pq"']
# If true, a test for the random expression will not be generated
no_randomization_test: false
randomExpr: |-
arr := make(pq.BoolArray, f.IntBetween(1, 5))
for i := range arr {
arr[i] = f.Bool()
}
return any(arr).(T)
netip.Addr:
imports: ['"net/netip"']
random_expr: |-
var addr [4]byte
rand.Read(addr[:])
return any(netip.AddrFrom4(addr)).(T)
# ADDITIONAL imports for the random expression
random_expr_imports: ['"crypto/rand"']
# Pass options to the go-cmp.Equal function
cmp_options: ["cmpopts.EquateComparable(netip.Addr{})"]
# ADDITIONAL imports for the options
cmp_options_imports: ['"github.com/google/go-cmp/cmp/cmpopts"']
types.HStore:
imports: ['"github.com/stephenafamo/bob/types"']
randomExpr: |-
hs := make(types.HStore)
for i := 0; i < f.IntBetween(1, 5); i++ {
arr[random[string](f)] = randomNull[string](f)
}
return any(hs).(T)

Replacements

There exists the ability to override types that the driver has inferred. The way to accomplish this is through the config file.

replacements:
- tables: ["table_name"] # What tables to look inside. Matches all tables if empty

# The match is a drivers.Column struct, and matches on almost all fields.
# Notable exception for the unique bool. Matches are done
# with "logical and" meaning it must match all specified matchers.
# Boolean values are only checked if all the string specifiers match first,
# and they must always match.
#
# Note there is precedence for types.match, more specific things should appear
# further down in the config as once a matching rule is found it is executed
# immediately.
match:
name: "username" # Matches the column name
db_type: "varchar(255)" # Matches the database type
default: "NULL" # Matches the default value
comment: "The username" # Matches the column comment
nullable: true # Matches the nullable value. Defaults to false.
generated: false # Matches the generated value. Defaults to false.
autoincr: false # Matches the autoincr value. Defaults to false.

# The replace directive should either reference a pre-configured type, or a type
# defined in the `types` configuration.
replace: "mynull.String"

Constraints

It is possible to manually define additional constraints for your database. This is particularly useful if your database system or driver does not support constraints

This can also be used to create relationships since those are derived from foreign key constraints.

constraints:
pilots:
primary: # This will overwirte any existing primary key on the table
name: "pilots_pkey"
columns: [id]
uniques: # These will be added to existing unique constraints
- name: "pilot_name_unique"
columns: [name]
- name: "pilot_nickname_unique"
columns: [nickname]
foreign: # These will be added to existing foreign constraints
- name: "pilot_to_jets"
columns: [id]
foreign_table: "jets"
foreign_columns: [pilot_id]

Relationships

Relationships are automatically inferred from foreign key constraints. However, in certain cases, it is either not possible or not desireable to add a foreign key relationship.

We can manually describe relationships in the configuration:

relationships:
users: # The table name
- name: "custom_videos_relationship" # A unique identifier used to configure aliases
never_required: true # If true, the relationship would never be assumed to be required. This means that the factory will never auto-generate related objects for it.
sides:
- from: "users" # Name of the source of the relationship
to: "videos" # Table name of the other side of the relation
# mapping of columns from source to destination
columns:
- [id, user_id]

# Which side to modify, "from" or "to"
# If not set, it will try to "guess" which side to modify
# - if only one of the sides contains a primary key,
# it will choose to modify the other side
# - If (both or none) of them contains a primary key,
# it will try with "Unique" columns
# - If it still cannot choose, it defaults to "to"
modify: "" | from" | "to"

The configuration also allows us to describe relationships that span multiple tables. We achieve this by having multiple sides.

In this example configuration, we add a relationship of users to videos through teams. The generated user model with have a Videos relation.

relationships:
users:
- name: "users_to_videos_through_teams"
sides:
- from: "users"
to: "teams"
columns: [[team_id, id]]
- from: "teams"
to: "videos"
columns: [[id, team_id]]

The configuration also allows us to describe relationships that are not only based on matching columns but also columns with static values. For example, we may want to add a relationship to teams for verified members.

relationships:
users:
- name: "users_to_videos_through_teams"
sides:
- from: "teams"
to: "users"
columns: [[id, team_id]]
to_where:
- column: "verified"
sql_value: "true"
go_value: "true"

Inflections

With inflections, you can control the rules used to generate singular/plural variants. This is useful if a certain word or suffix is used multiple times and you do not wnat to create aliases for every instance.

inflections:
plural: # Rules to convert a suffix to its plural form
ium: ia
plural_exact: # Rul