Pop — Interfaces#
Interface catalog#
dialect#
- Package:
github.com/gobuffalo/pop/v6 - File:
dialect.go:28 - Methods:
// Composed from crudable, fizzable, quotable, plus: Name() string DefaultDriver() string URL() string MigrationURL() string Details() *ConnectionDetails TranslateSQL(string) string CreateDB() error DropDB() error DumpSchema(io.Writer) error LoadSchema(io.Reader) error Lock(func() error) error TruncateAll(*Connection) error - Purpose: Master portability seam for all database-specific behavior. A
Connectionholds onedialectand delegates all CRUD execution, schema management, identifier quoting, and Fizz DSL translation to it. - Implementations:
postgresql(dialect_postgresql.go),mysql(dialect_mysql.go),mariadb(dialect_mariadb.go),sqlite(dialect_sqlite.go),cockroach(dialect_cockroach.go) — all registered viainit()into the privatenewConnectionregistry. - Design quality: Well-segregated.
dialectitself composes three smaller sub-interfaces (crudable,fizzable,quotable), each with a single, coherent responsibility. The composite is reasonably sized (12 methods ondialectproper, plus the 9 inherited) and cleanly follows the Interface Segregation Principle — callers that only need quoting acceptquotable, not the entiredialect.
crudable#
- Package:
github.com/gobuffalo/pop/v6 - File:
dialect.go:10 - Methods:
SelectOne(*Connection, *Model, Query) error SelectMany(*Connection, *Model, Query) error Create(*Connection, *Model, columns.Columns) error Update(*Connection, *Model, columns.Columns) error UpdateQuery(*Connection, *Model, columns.Columns, Query) (int64, error) Destroy(*Connection, *Model) error Delete(*Connection, *Model, Query) error - Purpose: Isolates the database-specific CRUD SQL generation from the generic ORM logic.
Connectionpasses its richModelandQueryvalues in; each dialect implementation compiles the correct SQL syntax (e.g.,RETURNING idfor PostgreSQL vs.LastInsertId()for MySQL) and executes it via thestore. - Implementations: All five dialect structs satisfy this via embedding in
dialect. - Design quality: Granular and well-focused. Seven methods map directly to the seven fundamental SQL operations. No method is redundant; each dialect must provide real implementations.
fizzable#
- Package:
github.com/gobuffalo/pop/v6 - File:
dialect.go:20 - Methods:
FizzTranslator() fizz.Translator - Purpose: Returns a
gobuffalo/fizzTranslatorcapable of converting Fizz DDL (a DB-agnostic schema DSL) into the native SQL for the dialect. Used exclusively by theMigrator. - Implementations: All five dialects.
- Design quality: Exemplary single-method interface. Completely separable concern; the Migrator only needs
fizzable, not the fulldialect.
quotable#
- Package:
github.com/gobuffalo/pop/v6 - File:
dialect.go:24 - Methods:
Quote(key string) string - Purpose: Returns a properly-quoted SQL identifier (e.g.,
"users"for PostgreSQL,`users`for MySQL). Used by thecolumnssub-package and query builders wherever identifier quoting is needed. - Implementations: All five dialects.
- Design quality: Textbook single-responsibility interface. The
columnssub-package also defines a privatequoterinterface with the same signature (columns/columns.go:132), showing clean internal use of the same abstraction.
store#
- Package:
github.com/gobuffalo/pop/v6 - File:
store.go:12 - Methods:
Select(interface{}, string, ...interface{}) error Get(interface{}, string, ...interface{}) error NamedExec(string, interface{}) (sql.Result, error) NamedQuery(query string, arg interface{}) (*sqlx.Rows, error) Exec(string, ...interface{}) (sql.Result, error) PrepareNamed(string) (*sqlx.NamedStmt, error) Transaction() (*Tx, error) Rollback() error Commit() error Close() error SelectContext(context.Context, ...) error GetContext(context.Context, ...) error NamedExecContext(context.Context, ...) (sql.Result, error) NamedQueryContext(context.Context, ...) (*sqlx.Rows, error) ExecContext(context.Context, ...) (sql.Result, error) PrepareNamedContext(context.Context, ...) (*sqlx.NamedStmt, error) TransactionContext(context.Context) (*Tx, error) TransactionContextOptions(context.Context, *sql.TxOptions) (*Tx, error) - Purpose: Thin abstraction over
*sqlx.DB/*sqlx.Tx. Enables thecontextStoredecorator (which wraps every non-context method to inject a storedcontext.Context) and the instrumented-driver overlay, all without changing the publicConnectionAPI. - Implementations:
dB(wraps*sqlx.DB),contextStore(embeddingstore+ctx, overriding non-context methods),*Tx(wraps*sqlx.Tx). - Design quality: Broad but necessary. The duplication of every method in both context and non-context variants is the deliberate cost of backward compatibility — adding
ctxto public methods would break existing callers. ThecontextStoredecorator technique cleanly solves the problem. 18 methods is large; a stricter ISP application might split read/write/lifecycle concerns, but for an ORM abstraction this is acceptable.
Association#
- Package:
github.com/gobuffalo/pop/v6/associations - File:
associations/association.go:13 - Methods:
Kind() reflect.Kind Interface() interface{} Constraint() (string, []interface{}) InnerAssociations() InnerAssociations Skipped() bool - Purpose: Defines the contract for all relationship types (BelongsTo, HasMany, HasOne, ManyToMany). The eager-loading path in the root package queries a slice of
Associationvalues and dispatches based on capability interfaces (AssociationBeforeCreatable,AssociationAfterCreatable, etc.). - Implementations:
belongsToAssociation,hasManyAssociation,hasOneAssociation,manyToManyAssociation— each embedsassociationSkipableandassociationCompositehelper structs to satisfySkipped()andInnerAssociations()without code repetition. - Design quality: Well-designed base interface. It is intentionally minimal; richer capabilities are expressed through extending interfaces rather than a fat
Association. The use of helper embedding structs to share default implementations is idiomatic.
AssociationBeforeCreatable / AssociationAfterCreatable / AssociationCreatableStatement#
- Package:
github.com/gobuffalo/pop/v6/associations - File:
associations/association.go:60,68,77 - Methods (BeforeCreatable):
BeforeInterface() interface{} BeforeSetup() error Association // embedded - Methods (AfterCreatable):
AfterInterface() interface{} AfterSetup() error AfterProcess() AssociationStatement Association // embedded - Methods (CreatableStatement):
Statements() []AssociationStatement Association // embedded - Purpose: Capability segregation for the create lifecycle. BelongsTo associations implement
BeforeCreatable(must be persisted before the parent record), HasMany/HasOne implementAfterCreatable, and ManyToMany implementsCreatableStatement(produces raw join-table INSERTs). Each path is dispatched via type assertion in the create executor. - Implementations:
belongsToAssociation→BeforeCreatable;hasManyAssociation,hasOneAssociation→AfterCreatable;manyToManyAssociation→CreatableStatement. - Design quality: Excellent application of ISP. Instead of a monolithic
Associationwith nullable lifecycle methods, the design adds capability via optional interfaces tested with type assertions. This is a canonical Go pattern for extensible type systems.
TableNameAble / TableNameAbleWithContext#
- Package:
github.com/gobuffalo/pop/v6 - File:
model.go:92,99 - Methods:
// TableNameAble TableName() string // TableNameAbleWithContext TableName(ctx context.Context) string - Purpose: Allows user model structs to override the default pluralized table name. Pop first checks for
TableNameAbleWithContext(higher priority), thenTableNameAble, then falls back togobuffalo/flectpluralization. These are the primary extension points for end users. - Implementations: User-provided model types; no library-internal implementations.
- Design quality: Clean, minimal consumer-defined interfaces. The context variant is a forward-compatible addition that doesn’t break the original interface. The priority chain (WithContext > plain > default) is a standard Go optional interface dispatch.
Lifecycle callback interfaces (callbacks.go)#
- Package:
github.com/gobuffalo/pop/v6 - File:
callbacks.go - Interfaces:
AfterFindable { AfterFind(*Connection) error } AfterEagerFindable { AfterEagerFind(*Connection) error } BeforeSaveable { BeforeSave(*Connection) error } BeforeCreateable { BeforeCreate(*Connection) error } BeforeUpdateable { BeforeUpdate(*Connection) error } BeforeDestroyable { BeforeDestroy(*Connection) error } BeforeValidateable { BeforeValidate(*Connection) error } AfterDestroyable { AfterDestroy(*Connection) error } AfterUpdateable { AfterUpdate(*Connection) error } AfterCreateable { AfterCreate(*Connection) error } AfterSaveable { AfterSave(*Connection) error } - Purpose: ActiveRecord-style lifecycle hooks. Model structs opt into specific hooks by implementing the corresponding interface. Pop checks for each interface via type assertion in the model dispatch methods (
beforeCreate,afterSave, etc.) and calls them at the appropriate points in the CRUD pipeline. - Implementations: User-provided model types.
- Design quality: Maximally segregated — eleven single-method interfaces, each independently optional. This is ideal ISP: a model can implement only
BeforeCreateablewithout carrying the weight of all other hooks. The naming convention (-ablesuffix) is consistent and self-documenting.
Validation interfaces (validations.go — private)#
- Package:
github.com/gobuffalo/pop/v6 - File:
validations.go - Interfaces (unexported):
validateable { Validate(*Connection) (*validate.Errors, error) } validateCreateable { ValidateCreate(*Connection) (*validate.Errors, error) } validateSaveable { ValidateSave(*Connection) (*validate.Errors, error) } validateUpdateable { ValidateUpdate(*Connection) (*validate.Errors, error) } beforeValidatable { BeforeValidations(*Connection) error } - Purpose: Optional validation hooks that integrate with
gobuffalo/validate. Pop calls these duringValidateAndCreate,ValidateAndSave, andValidateAndUpdate. User model structs implement whichever subset they need. - Implementations: User-provided model types.
- Design quality: Same ISP philosophy as callbacks. Keeping them unexported is slightly unusual — it means the Go doc won’t show them — but the public
ValidateAndSave/ValidateAndCreatefunctions document which methods to implement in their own doc comments.
PaginationParams#
- Package:
github.com/gobuffalo/pop/v6 - File:
paginator.go:68 - Methods:
Get(key string) string - Purpose: Abstracts the query-string source for pagination parameters.
NewPaginatorFromParamsaccepts this interface, whichurl.Valuessatisfies natively. Allows the paginator to be constructed from any key-value provider (HTTP query string, form values, custom map). - Implementations:
url.Values(stdlib satisfies it directly), any custom map wrapper. - Design quality: Single-method interface that unlocks composition with stdlib. A clean example of defining an interface against what the consumer needs, not what providers already have.
Interface patterns#
Size distribution: The vast majority of interfaces are single-method (ISP applied aggressively). The two genuinely multi-method interfaces are
dialect(~21 methods via composition) andstore(18 methods) — both justified by their role as adapters over third-party drivers. TheAssociationinterface (5 methods) is the mid-range case. Average across the full set is approximately 2–3 methods.Embedding:
dialectembedscrudable,fizzable, andquotable— the canonical Go pattern for composing larger interfaces from focused ones.AssociationBeforeCreatable,AssociationAfterCreatable, andAssociationCreatableStatementall embedAssociation, building a capability hierarchy.contextStoreembeds thestoreinterface as a field to override only non-context methods while delegating the rest.Implicit satisfaction: All user-facing interfaces (callbacks, validation hooks,
TableNameAble) are designed to be satisfied implicitly by user model structs — Pop tests for them with type assertions at runtime, never requiring explicitvar _ InterfaceName = (*MyModel)(nil)declarations. Internal interfaces (dialect,store,crudable) are satisfied only by library-internal types, never exposed as constraints users must meet.stdlib interfaces used:
io.Writerandio.Readerappear indialect.DumpSchema/LoadSchema;sql.Resultis returned from store methods;context.Contextis threaded through the context variants ofstore.url.ValuessatisfiesPaginationParams. Nofmt.Stringer,io.Closer, orsort.Interfaceimplementations are prominent, thoughPaginator.String()is provided as a convenience.
Key abstractions#
dialect— The central portability interface. Its three-layer composition (crudable+fizzable+quotable) is the architectural decision that makes multi-database support maintainable. Everything that differs between databases lives behind this seam; everything generic (query building, model mapping, migration bookkeeping) lives in front of it.store— The connection-level abstraction that enables context propagation without API breakage. ThecontextStoredecorator pattern built on this interface is a sophisticated, non-obvious technique for threadingcontext.Contextthrough a library that predates widespread context adoption.Association— The base of a capability-extension hierarchy. By keeping the base interface small and adding capabilities viaAssociationBeforeCreatable/AfterCreatable/CreatableStatement, Pop achieves a flexible, ordered create lifecycle across four association types without a single switch statement or nil-check.Lifecycle callback interfaces (
BeforeCreateable,AfterSaveable, etc.) — The ORM’s primary extension point for end users. Eleven single-method interfaces, each independently optional, implement the ActiveRecord callback pattern in idiomatic Go. This is arguably the best example in the codebase of ISP applied at the consumer-facing API level.TableNameAble/TableNameAbleWithContext— Simple but foundational. These two interfaces govern the single most common customization users need (override the table name). Their priority chain (context-aware > context-free > default) demonstrates backward-compatible interface evolution in Go.
Interface-driven extensibility#
Pop uses interfaces for extensibility at three distinct levels:
Driver-level (dialect + store): New databases are added by implementing
dialectand registering a factory viainit(). This is the framework’s deepest extension point and is used internally (five dialects ship with pop). External contributors can add dialects by satisfying the fulldialectinterface.Association-level: New association types can be added by implementing
Associationand optionally the lifecycle sub-interfaces (BeforeCreatable,AfterCreatable,CreatableStatement). TheassociationBuilderfunction type and the builder registry inassociations/make this straightforward.Model-level (user-facing): End users extend model behavior by implementing any combination of:
TableNameAble/TableNameAbleWithContext— custom table namingBeforeCreateable,AfterSaveable, etc. — lifecycle callbacks (11 hooks)validateable,validateCreateable, etc. — validation hooks (5 hooks)
This “implement what you need” model is the dominant extensibility pattern in Pop and requires no registration, no configuration, and no code generation — just interface satisfaction.