Skip to content

Commit

Permalink
Add SettingOnly()
Browse files Browse the repository at this point in the history
  • Loading branch information
survivorbat committed Jul 4, 2023
1 parent 5d0a4b4 commit 3b0ea6a
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 66 deletions.
66 changes: 5 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,13 @@
[![Go package](https://github.com/survivorbat/gorm-like/actions/workflows/test.yaml/badge.svg)](https://github.com/survivorbat/gorm-like/actions/workflows/test.yaml)

I wanted to provide a map to a WHERE query and automatically turn it into a LIKE query if wildcards were present, this
plugin does just that. You can either do it for all queries or only for specific fields using the tag `gormlike:"true"`.
plugin does just that.

```go
package main

import (
gormlike "github.com/survivorbat/gorm-like"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

// Employee is the example for normal usage
type Employee struct {
Name string
}

// RestrictedEmployee is the example for gormlike.TaggedOnly()
type RestrictedEmployee struct {
// Can be LIKE-d on
Name string `gormlike:"true"`

// Can NOT be LIKE-d on
Job string
}

func main() {
// Normal usage
db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

filters := map[string]any{
"name": "%b%",
}

db.Use(gormlike.New())
db.Model(&Employee{}).Where(filters)

// With custom replacement character
db, _ = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

filters := map[string]any{
"name": "🍌b🍌",
}

db.Use(gormlike.New(gormlike.WithCharacter("🍌")))
db.Model(&Employee{}).Where(filters)

// Only uses LIKE-queries for tagged fields
db, _ = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

filters := map[string]any{
"name": "🍌b🍌",
}
By default, all queries are turned into like-queries if either a % or a given character is found, if you don't want this,
you have 2 options:

db.Use(gormlike.New(gormlike.TaggedOnly()))
db.Model(&RestrictedEmployee{}).Where(filters)
}
```

Is automatically turned into a query that looks like this:

```sql
SELECT * FROM employees WHERE name LIKE "%b%";
```
- `TaggedOnly()`: Will only change queries on fields that have the `gormlike:"true"` tag
- `SettingOnly()`: Will only change queries on `*gorm.DB` objects that have `.Set("gormlike", true)` set.

## ⬇️ Installation

Expand Down
2 changes: 2 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ func ExampleNew() {
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})

_ = db.Use(New())

_ = db.Use(New(WithCharacter("*")))
_ = db.Use(New(TaggedOnly()))
_ = db.Use(New(SettingOnly()))
}
16 changes: 13 additions & 3 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ func TaggedOnly() Option {
}
}

// New creates a new instance of the plugin that can be registered in gorm.
// SettingOnly makes it so that only queries with the setting 'gormlike' set to true can be turned into LIKE queries.
// This can be configured using db.Set("gormlike", true) on the query.
func SettingOnly() Option {
return func(like *gormLike) {
like.conditionalSetting = true
}
}

// New creates a new instance of the plugin that can be registered in gorm. Without any settings, all queries will be
// LIKE-d.
func New(opts ...Option) gorm.Plugin {
plugin := &gormLike{}

Expand All @@ -37,8 +46,9 @@ func New(opts ...Option) gorm.Plugin {
}

type gormLike struct {
replaceCharacter string
conditionalTag bool
replaceCharacter string
conditionalTag bool
conditionalSetting bool
}

func (d *gormLike) Name() string {
Expand Down
12 changes: 12 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import (
const tagName = "gormlike"

func (d *gormLike) queryCallback(db *gorm.DB) {
// If we only want to like queries that are explicitly set to true, we back out early if anything's amiss
if d.conditionalSetting {
value, ok := db.Get(tagName)
if !ok {
return
}

if boolValue, _ := value.(bool); !boolValue {
return
}
}

exp, ok := db.Statement.Clauses["WHERE"].Expression.(clause.Where)
if !ok {
return
Expand Down
91 changes: 89 additions & 2 deletions query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func TestDeepGorm_Initialize_TriggersLikingCorrectly(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
// Arrange
db := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())).Debug()
db := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name()))
_ = db.AutoMigrate(&ObjectA{})
plugin := New(testData.options...)

Expand Down Expand Up @@ -322,7 +322,7 @@ func TestDeepGorm_Initialize_TriggersLikingCorrectlyWithConditional(t *testing.T
t.Run(name, func(t *testing.T) {
t.Parallel()
// Arrange
db := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name())).Debug()
db := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name()))
_ = db.AutoMigrate(&ObjectB{})
plugin := New(TaggedOnly())

Expand All @@ -345,3 +345,90 @@ func TestDeepGorm_Initialize_TriggersLikingCorrectlyWithConditional(t *testing.T
})
}
}

func TestDeepGorm_Initialize_TriggersLikingCorrectlyWithSetting(t *testing.T) {
t.Parallel()

type ObjectB struct {
Name string
Other string
}

tests := map[string]struct {
filter map[string]any
query func(*gorm.DB) *gorm.DB
existing []ObjectB
expected []ObjectB
}{
"like with query set to true": {
filter: map[string]any{
"name": "jes%",
},
query: func(db *gorm.DB) *gorm.DB {
return db.Set(tagName, true)
},
existing: []ObjectB{{Name: "jessica", Other: "abc"}},
expected: []ObjectB{{Name: "jessica", Other: "abc"}},
},
"like with query set to false": {
filter: map[string]any{
"name": "jes%",
},
query: func(db *gorm.DB) *gorm.DB {
return db.Set(tagName, false)
},
existing: []ObjectB{{Name: "jessica", Other: "abc"}},
expected: []ObjectB{},
},
"like with query set to random value": {
filter: map[string]any{
"name": "jes%",
},
query: func(db *gorm.DB) *gorm.DB {
return db.Set(tagName, "yes")
},
existing: []ObjectB{{Name: "jessica", Other: "abc"}},
expected: []ObjectB{},
},
"like with query unset": {
filter: map[string]any{
"name": "jes%",
},
query: func(db *gorm.DB) *gorm.DB {
return db
},
existing: []ObjectB{{Name: "jessica", Other: "abc"}},
expected: []ObjectB{},
},
}

for name, testData := range tests {
testData := testData
t.Run(name, func(t *testing.T) {
t.Parallel()
// Arrange
db := gormtestutil.NewMemoryDatabase(t, gormtestutil.WithName(t.Name()))
_ = db.AutoMigrate(&ObjectB{})
plugin := New(SettingOnly())

if err := db.CreateInBatches(testData.existing, 10).Error; err != nil {
t.Error(err)
t.FailNow()
}

db = testData.query(db)

// Act
err := db.Use(plugin)

// Assert
assert.Nil(t, err)

var actual []ObjectB
err = db.Where(testData.filter).Find(&actual).Error
assert.Nil(t, err)

assert.Equal(t, testData.expected, actual)
})
}
}

0 comments on commit 3b0ea6a

Please sign in to comment.