Repository: gobuffalo/buffalo
Pull request #2359 (2023-01-03 20:28:12)

For nulls.Time, decode empty value as NULL


### What is being done in this PR?
This PR adds a check during decoding which will return `nulls.Time` as a NULL value (i.e. its value for `Valid` will be false). Without this check, the value of nulls.Time{} will be:
```
{
  Time: 0001-01-01 00:00:00 +0000 UTC,
  Valid: true,
}
```
This ends up storing the value `0001-01-01 00:00:00 +0000 UTC` in the database, as opposed to a NULL value.

### What are the main choices made to get to this solution?
One option was to make the model `Bindable` and write a custom `Bind()` method which could handle empty values, but the default Bind handler does so much that bypassing it seemed extreme. 

### What was discovered while working on it? (Optional)
> Type in here a description of the discoveries made while working on this PR.

### List the manual test cases you've covered before sending this PR:
Fizz (using Postgres):
```
create_table("people") {
	t.Column("id", "uuid", {primary: true})
	t.Column("name", "string", {"size": 128})
	t.Column("birthday", "date", {"null": true})
	t.Timestamps()
}
```
Model:
```
type Person struct {
	ID uuid.UUID `json:"id" db:"id"`
	Name  string `json:"name" db:"name"`
	Birthday nulls.Time `json:"birthday" db:"birthday"`
	CreatedAt time.Time `json:"created_at" db:"created_at"`
	UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
```
Plush form:
```
<%= f.InputTag("Birthday", {"type":"date"}) %>
```



Patch:
diff --git a/binding/decoders/null_time.go b/binding/decoders/null_time.go
index 1456965a..6d1cf0a2 100644
--- a/binding/decoders/null_time.go
+++ b/binding/decoders/null_time.go
@@ -7,6 +7,15 @@ func NullTimeDecoderFn() func([]string) (interface{}, error) {
 	return func(vals []string) (interface{}, error) {
 		var ti nulls.Time
 
+		// If vals is empty, return a nulls.Time with Valid = false (i.e. NULL).
+		// The parseTime() function called below does this check as well, but
+		// because it doesn't return an error in the case where vals is empty,
+		// we have no way to determine from its response that the nulls.Time
+		// should actually be NULL.
+		if len(vals) == 0 || vals[0] == "" {
+			return ti, nil
+		}
+
 		t, err := parseTime(vals)
 		if err != nil {
 			return ti, err


Test patch:
diff --git a/binding/decoders/null_time_test.go b/binding/decoders/null_time_test.go
index 7c6e2804..756581fd 100644
--- a/binding/decoders/null_time_test.go
+++ b/binding/decoders/null_time_test.go
@@ -14,23 +14,28 @@ func Test_NullTimeCustomDecoder_Decode(t *testing.T) {
 	testCases := []struct {
 		input     string
 		expected  time.Time
+		expValid  bool
 		expectErr bool
 	}{
 		{
 			input:    "2017-01-01",
 			expected: time.Date(2017, time.January, 1, 0, 0, 0, 0, time.UTC),
+			expValid: true,
 		},
 		{
 			input:    "2018-07-13T15:34",
 			expected: time.Date(2018, time.July, 13, 15, 34, 0, 0, time.UTC),
+			expValid: true,
 		},
 		{
 			input:    "2018-20-10T30:15",
 			expected: time.Time{},
+			expValid: false,
 		},
 		{
 			input:    "",
 			expected: time.Time{},
+			expValid: false,
 		},
 	}
 
@@ -47,5 +52,6 @@ func Test_NullTimeCustomDecoder_Decode(t *testing.T) {
 		}
 
 		r.Equal(testCase.expected, nt.Time)
+		r.Equal(testCase.expValid, nt.Valid)
 	}
 }