Age | Commit message (Collapse) | Author |
|
This makes it easier to write a shopping list for the required
ingredients.
|
|
The used `func(http.ResponseWriter, *http.Request)` return values made
the HTTP handler factory functions quite unreadable. Thus it is switched
to the http.Handler type.
|
|
The reason for the introduction of this dependency was that it was easy
to setup routes with HTTP method restrictions.
Since Go 1.22 this feature is part of the standard library. Method
restrictions are part of the patterns used to register routes [1].
[1]: https://pkg.go.dev/net/http#hdr-Patterns-ServeMux
|
|
Reducing global variables makes it easier to understand functions
independently of the rest of the code.
Adding the new model.DB type as a custom variant of the sql.DB type
makes it possible to write methods for the database which makes the code
way more readable.
|
|
This applies default Go code style recommendations.
|
|
To avoid not clickable recipes on the /recipes page a default name used
to be inserted on recipe creation.
This was not a proper fix for the problem and also was annoying that the
user first had to remove the default recipe name.
This commit removes this default name.
|
|
It is a very common pattern that some function needs to access the
database and wants to wrap all the actions into one transaction.
The advantage of a transaction is that it is ACID:
- atomic
- consistent
- isolated
- durable
In Go it is required to request a new transaction, execute functionality
on it and handle rollback or commit of this transaction based on the
success of the operation.
All this and the error handling can be written down in the
model.Transaction() function exactly once. The full signature of it is:
func Transaction(f func(*sql.Tx) error) error
It requires a function or closure passed as argument which takes the
transaction (*sql.Tx) and returns an error which might be nil.
This is very generic. It is applied to:
- injecting test data
- database migrations
- data read requests
- data write requests
|
|
This removes the redundant setup of a database/sql.Tx in each HTTP
handler.
|
|
When nesting objects like steps into other objects like recipes it is
required to pass a *sql.Tx value to the CRUD methods of the inner
object to be able to roll back the whole transaction.
The top level object used to be responsible for the creation of this
*sql.Tx inside its CRUD methods.
This is now moved to the caller of the CRUD methods (here the HTTP
handler function). The advantage is that all CRUD methods now accept a
*sql.Tx as only argument which makes those methods more consistent.
|
|
|
|
This reflects that these HTTP handler functions implement one of the
four CRUD methods create, read update and delete.
|
|
The old edit URL parameter allowed to select one different HTML
template. A more generic approach is to provide a view parameter which
allows to use multiple alternative HTML templates for the same data
defined by the Go struct.
This makes implementing additional pages like a confirm page for recipe
deletion easier.
|
|
When a HTML form is converted to JSON by JavaScript using `FormData()`,
`Object.fromEntries()` and `JSON.stringify` the data type is always
`string`. This does not match the Go struct definitions using multiple
types including e.g. `int`.
There are several options to solve this conflict:
1. use only strings in Go struct definitions
2. write custom functions to parse string-based JSONs to Go structs
3. implement custom functions in JS to use `number` type if possible
Option 3 seems to be a very clean solution. Nevertheless it is limited
by the fact that JSON anyway has a way more limited type system than Go.
So the types used in Go cannot be used and this would reduce this option
to a variant of option 2.
Option 2 requires significant effort per struct inside the model
package. Every object which is transferred via JSON and serialized into
Go structs would require a second struct definition with string types
and a conversion function. This does not scale.
Thus option 1 seems to be the best fit. The reasons for using types like
`int` or `bool` are:
- less memory consumption than `string` in most cases
- implicit data validation (e.g. enforcing positive numbers with `uint`)
- better compatibility with certain APIs which rely on e.g. `int`
The first argument is not so relevant in this use case. The amount of
required memory is still quite small for servers. Implicit data
validation is a good thing but not enough. There should anyway be
validation method which has to be called on CRUD methods and JSON
deserialization.
|
|
|
|
|