Andy
Andy

Reputation: 14184

Why doesn't the Grails findOrSaveBy* dynamic finder accept a second parameter for creation?

Coming from a Rails background I was happy to see that Grails 2.0.x supports the dynamic finders of the form Model.findOrSaveBy* and Model.findOrCreateBy*. However, the functionality is extremely artificially limited -- according to the documentation, the method accepts n parameters, where n is the exact number of attributes listed in the method call. So for example:

class Car {
  String color
  String license

  static constraints = {
    license blank: false, unique: true
    color blank: false, inList: ["red", "black"]
  }
}

/* If there exists a Car(color: red, license: "ABCDEF") */

// WORKS
Car.findOrSaveByColor("red")  // will find the car
Car.findOrSaveByLicense("ABCDEF")  // will find the car
Car.findOrSaveByLicenseAndColor("UVWXYZ", "black")  // will persist a new car

// DOES NOT WORK
Car.findOrSaveByLicense("UVWXYZ")  // will fail because the color is not provided
Car.findOrSaveByLicense("UVWXYZ", color: "black")  // will fail due to an extra parameter
Car.findOrSaveByLicenseAndColor("ABCDEF", "black")  // will fail due to persisting a new instance because the color does not match, which then has a unique collision

I only care about locating via the unique license value, but if the object does not exist, I need to populate it with all required attributes. Rails allows you to do this via a Hash parameter, as follows:

// Using findOrSaveBy* because Grails:"save"::Rails:"create" and "create" does not persist in Grails
Car.findOrSaveByLicense("ABCDEF", color: "red")  // will find a car with that license plate regardless of color or persist a new entry with the license and color "red"

Is there a reason this functionality was not implemented in Grails? It seems to me this greatly limits the usefulness of these dynamic finders. I guess I could add a methodMissing to the domain classes which intercepts the call and delegates to something like:

def car = Car.findByLicense("UVWXYZ") ?: new Car(license: "UVWXYZ", color: "red").save(flush: true)

but it just seems very repetitive. Any suggestions? Thanks.

Upvotes: 2

Views: 319

Answers (1)

16dots
16dots

Reputation: 3070

Based on your example, an easy solution would be giving a default value to color in your domain class:

class Car {
  String color = "red"
  String license

  static constraints = {
    license blank: false, unique: true
    color blank: false, inList: ["red", "black"]
  }
}

Then the following should work:

Car.findOrSaveByLicense("UVWXYZ")

Upvotes: 1

Related Questions