Specify type of Date field - vapor

In my PostgreSQLModel vapor class I'm using a simple Migration extension to create the database table.
extension Order: Migration {
static func prepare(on connection: PostgreSQLConnection) -> Future<Void> {
return Database.create(self, on: connection) { builder in
try addProperties(to: builder)
try builder.addReference(from: \.stationID, to: \Station.id)
}
}
}
The problem I'm having is my Date fields are being created as timestamp without time zone whereas what I really want is just date. How do I specify this? Do I have to skip the addProperties and manually call addField on the builder myself for each column?

Not sure if it's the best way, but I ended up doing this:
extension Order: Migration {
static func prepare(on connection: PostgreSQLConnection) -> Future<Void> {
return Database.create(self, on: connection) { builder in
try addProperties(to: builder)
try builder.addReference(from: \.stationID, to: \Station.id)
builder.schema.addFields = builder.schema.addFields.map {
guard $0.type.type == .timestamp else { return $0 }
let defaultValue = $0.name == CodingKeys.created.rawValue ? "now()" : nil
let type = PostgreSQLColumn(type: .date, default: defaultValue)
return SchemaField<PostgreSQLDatabase>(name: $0.name, type: type, isOptional: $0.isOptional)
}
}
}
}

Related

How to get an enum from a String?

Minimal reproducible code:
abstract class FooEnum extends Enum {
// Some abstract methods...
}
enum One implements FooEnum { a, b }
enum Two implements FooEnum { x, y }
FooEnum getFooEnum(String string) {
// Too much boiler plate code, how to do it in a better way?
if (string == 'One.a') return One.a;
else if (...) // other cases.
}
Right now I'm doing it manually (error prone). So, how can I get an enum from a String?
If you only want to use pure dart without flutter or any packages you could do this:
FooEnum? getFooEnum(String string) {
final classValue = string.split('.');
if (classValue.length != 2) {
return null;
}
try {
switch (classValue[0]) {
case 'One':
return One.values.byName(classValue[1]);
case 'Two':
return Two.values.byName(classValue[1]);
}
} on ArgumentError {
return null;
}
return null;
}
With the collection package you could do this:
FooEnum? getFooEnum(String string) {
return (One.values.firstWhereOrNull((e) => e.toString() == string) ??
Two.values.firstWhereOrNull((e) => e.toString() == string)) as FooEnum?;
}
I haven't looked into why the cast is needed, but it was a quick way to fix the problem that occures without it.

Vapor 4 - Approaching an issue - Return type of a function

I have a function that triggers when I hit a certain endpoint, that contains some sort of a condition inside
if room exists {
return room
} else {
create a new room and return it
}
The issue is, that sometimes I return an EventLoopFuture (if the room doesn't exist), and sometimes I return a Room type without an EventLoopFuture (if a room exists), so I am not quite sure what the return type of the function should be.
Now the way I fix it is with a hacky workaround, where I always return an EventLoopFuture, and if the room exists, I update it and return it, which is totally unnecessary as there's nothing to update.
Would the right way be to save the newly created room and return it separately, so the condition will always return a Room type and not an EventLoopFuture?
The original function:
func findEmpty(req:Request) throws -> EventLoopFuture<Room> {
guard let category = req.parameters.get("category") else { throw Abort(.badRequest) }
try checkIfAllowed(category: category)
return Room.query(on: req.db)
.filter(\.$category == category)
.all()
.flatMap { relevantRooms in
for room in relevantRooms {
for isOccupied in room.availablePositions {
if !isOccupied {
return room.update(on: req.db)
.map {
return room
}
}
}
}
let newRoom = Room(...//initializing room)
return newRoom.save(on: req.db)
.map {
return newRoom
}
}
}
With the introduction of async, you can choose to return either the Future or the resolved value. However, in most cases, the async pattern results in simpler code. If you do want to return a Future of your room, then it should be as simple as replacing your existing return room with return req.eventLoop.makeSucceededFuture(room) instead of the unnecessary update. so:
return room.update(on: req.db).map {
return room
}
Becomes:
return req.eventLoop.makeSucceededFuture(room)
Converting code using Future pattern to async is relatively straightforward (and if it isn't that's probably a sign you should leave it as Future). In your case:
func findEmpty(req:Request) async throws -> Room {
guard let category = req.parameters.get("category") else { throw Abort(.badRequest) }
try checkIfAllowed(category: category)
let relevantRooms = try await Room.query(on: req.db)
.filter(\.$category == category)
.all()
for room in relevantRooms {
for isOccupied in room.availablePositions {
if !isOccupied {
return room
}
}
}
let newRoom = Room(...//initializing room)
try await newRoom.save(on: req.db)
return newRoom
}
Just an observation, but your inner for could be replaced with something simpler:
if let unoccupied = room.availablePositions.first { !$0.isOccupied } {
return unoccupied
}

Project Reactor: achieving mapping input Mono conditionally

I am trying to implement the following simple imperative logic:
boolean saveImperative(final List list) {
final String existingList = readByNameImperative(list.getName());
if (Objects.isNull(existingList)) {
templateSaveImperative(existingList);
return true;
} else {
templateSaveImperative(existingList);
return false;
}
}
using Project Reactor in declarative way and this is what I was able to achieve:
#Test
public void testDeclarative() {
final Mono<List> list = createList("foo");
final Boolean result = save(list).block();
System.out.println(result);
}
private Mono<Boolean> save(final Mono<List> listMono) {
final Mono<List> existingListMono = listMono.cache()
.flatMap(list -> readByName(list.getName()));
// if
final Mono<List> savedListMono = existingListMono
.flatMap(existingList -> templateSave(Mono.just(existingList)));
final Mono<Boolean> trueResult = savedListMono.map(x -> true);
// else
return trueResult.switchIfEmpty(templateSave(existingListMono).map(x -> false));
}
private Mono<List> templateSave(final Mono<List> listMono) {
return listMono.map(list -> {
System.out.println("templateSave has been called");
return list;
});
}
private Mono<List> readByName(final String listName) {
if (listName != "list001") {
return Mono.empty();
}
return createList(listName);
}
private Mono<List> createList(final String name) {
final List list = List.builder().name(name).build();
return Mono.just(list);
}
#Value
#Builder
private static class List {
private final String name;
}
If I execute the test with list001, it will print:
templateSave has been called
true
as expected, but if I call it with foo, then I got
null
What I would be missing? I would expect an output like:
templateSave has been called
false
in that case.
final Mono<List> existingListMono = listMono.cache()
.flatMap(list -> readByName(list.getName()));
...in your save method, will take your existing list and flat map it using readByName().
Your readByName() method is the following:
private Mono<List> readByName(final String listName) {
if (listName != "list001") {
return Mono.empty();
}
return createList(listName);
}
(I don't believe it's related to this problem, but don't use == or != for comparing strings.)
Since your listName is foo, not list001, it returns an empty Mono - thus existingListMono becomes an empty Mono, and by implication so do savedListMono and trueResult.
When you call your switchIfEmpty() statement however, you pass in templateSave(existingListMono) - and since existingListMono is an empty Mono as above, the save() method returns an empty Mono.
...and when you block on an empty Mono you'll get null - hence the result.
As such, you may wish to use listMono instead of existingListMono in your return statement on the save() method, which will give you the result you're after:
trueResult.switchIfEmpty(templateSave(listMono).map(x -> false))

Enum from String

I have an Enum and a function to create it from a String because i couldn't find a built in way to do it
enum Visibility{VISIBLE,COLLAPSED,HIDDEN}
Visibility visibilityFromString(String value){
return Visibility.values.firstWhere((e)=>
e.toString().split('.')[1].toUpperCase()==value.toUpperCase());
}
//used as
Visibility x = visibilityFromString('COLLAPSED');
but it seems like i have to rewrite this function for every Enum i have, is there a way to write the same function where it takes the Enum type as parameter? i tried to but i figured out that i can't cast to Enum.
//is something with the following signiture actually possible?
dynamic enumFromString(Type enumType,String value){
}
Mirrors aren't always available, but fortunately you don't need them. This is reasonably compact and should do what you want.
enum Fruit { apple, banana }
// Convert to string
String str = Fruit.banana.toString();
// Convert to enum
Fruit f = Fruit.values.firstWhere((e) => e.toString() == 'Fruit.' + str);
assert(f == Fruit.banana); // it worked
Thanks to #frostymarvelous for correcting the answer
As from Dart version 2.15, you can lookup an enum value by name a lot more conveniently, using .values.byName or using .values.asNameMap():
enum Visibility {
visible, collapsed, hidden
}
void main() {
// Both calls output `true`
print(Visibility.values.byName('visible') == Visibility.visible);
print(Visibility.values.asNameMap()['visible'] == Visibility.visible);
}
You can read more about other enum improvements in the official Dart 2.15 announcement blog post.
My solution is identical to Rob C's solution but without string interpolation:
T enumFromString<T>(Iterable<T> values, String value) {
return values.firstWhere((type) => type.toString().split(".").last == value,
orElse: () => null);
}
Null safe example using firstWhereOrNull() from the collection package
static T? enumFromString<T>(Iterable<T> values, String value) {
return values.firstWhereOrNull((type) => type.toString().split(".").last == value);
}
Update:
void main() {
Day monday = Day.values.byName('monday'); // This is all you need
}
enum Day {
monday,
tuesday,
}
Old solution:
Your enum
enum Day {
monday,
tuesday,
}
Add this extension (need a import 'package:flutter/foundation.dart';)
extension EnumEx on String {
Day toEnum() => Day.values.firstWhere((d) => describeEnum(d) == toLowerCase());
}
Usage:
void main() {
String s = 'monday'; // String
Day monday = s.toEnum(); // Converted to enum
}
This is all so complicated I made a simple library that gets the job done:
https://pub.dev/packages/enum_to_string
import 'package:enum_to_string:enum_to_string.dart';
enum TestEnum { testValue1 };
convert(){
String result = EnumToString.parse(TestEnum.testValue1);
//result = 'testValue1'
String resultCamelCase = EnumToString.parseCamelCase(TestEnum.testValue1);
//result = 'Test Value 1'
final result = EnumToString.fromString(TestEnum.values, "testValue1");
// TestEnum.testValue1
}
Update: 2022/02/10
Dart v2.15 has implemented some additional enum methods that may solve your problems.
From here: https://medium.com/dartlang/dart-2-15-7e7a598e508a
Improved enums in the dart:core library
We’ve made a number of convenience additions to the enum APIs in the dart:core library (language issue #1511). You can now get the String value for each enum value using .name:
enum MyEnum {
one, two, three
}
void main() {
print(MyEnum.one.name); // Prints "one".
}
You can also look up an enum value by name:
print(MyEnum.values.byName('two') == MyEnum.two); // Prints "true".
Finally, you can get a map of all name-value pairs:
final map = MyEnum.values.asNameMap();
print(map['three'] == MyEnum.three); // Prints "true".
Using mirrors you could force some behaviour. I had two ideas in mind. Unfortunately Dart does not support typed functions:
import 'dart:mirrors';
enum Visibility {VISIBLE, COLLAPSED, HIDDEN}
class EnumFromString<T> {
T get(String value) {
return (reflectType(T) as ClassMirror).getField(#values).reflectee.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase());
}
}
dynamic enumFromString(String value, t) {
return (reflectType(t) as ClassMirror).getField(#values).reflectee.firstWhere((e)=>e.toString().split('.')[1].toUpperCase()==value.toUpperCase());
}
void main() {
var converter = new EnumFromString<Visibility>();
Visibility x = converter.get('COLLAPSED');
print(x);
Visibility y = enumFromString('HIDDEN', Visibility);
print(y);
}
Outputs:
Visibility.COLLAPSED
Visibility.HIDDEN
Collin Jackson's solution didn't work for me because Dart stringifies enums into EnumName.value rather than just value (for instance, Fruit.apple), and I was trying to convert the string value like apple rather than converting Fruit.apple from the get-go.
With that in mind, this is my solution for the enum from string problem
enum Fruit {apple, banana}
Fruit getFruitFromString(String fruit) {
fruit = 'Fruit.$fruit';
return Fruit.values.firstWhere((f)=> f.toString() == fruit, orElse: () => null);
}
Here is an alternative way to #mbartn's approach using extensions, extending the enum itself instead of String.
Faster, but more tedious
// We're adding a 'from' entry just to avoid having to use Fruit.apple['banana'],
// which looks confusing.
enum Fruit { from, apple, banana }
extension FruitIndex on Fruit {
// Overload the [] getter to get the name of the fruit.
operator[](String key) => (name){
switch(name) {
case 'banana': return Fruit.banana;
case 'apple': return Fruit.apple;
default: throw RangeError("enum Fruit contains no value '$name'");
}
}(key);
}
void main() {
Fruit f = Fruit.from["banana"];
print("$f is ${f.runtimeType}"); // Outputs: Fruit.banana is Fruit
}
Less tedius, but slower
If O(n) performance is acceptable you could also incorporate #Collin Jackson's answer:
// We're adding a 'from' entry just to avoid having to use Fruit.apple['banana']
// which looks confusing.
enum Fruit { from, apple, banana }
extension FruitIndex on Fruit {
// Overload the [] getter to get the name of the fruit.
operator[](String key) =>
Fruit.values.firstWhere((e) => e.toString() == 'Fruit.' + key);
}
void main() {
Fruit f = Fruit.from["banana"];
print("$f is ${f.runtimeType}"); // Outputs: Fruit.banana is Fruit
}
I use this function, I think it's simple and doesn't need any kind of 'hack':
T enumFromString<T>(List<T> values, String value) {
return values.firstWhere((v) => v.toString().split('.')[1] == value,
orElse: () => null);
}
You can use it like this:
enum Test {
value1,
value2,
}
var test = enumFromString(Test.value, 'value2') // Test.value2
With Dart 2.15 we can now do this which is much cleaner
// Convert to string
String fruitName = Fruit.banana.name;
// Convert back to enum
Fruit fruit = Fruit.values.byName(fruitName);
print(fruit); // Fruit.banana
assert(fruit == Fruit.banana);
Since Dart 2.17 you can solve this elegantly with Enhanced Enums.
(see https://stackoverflow.com/a/71412047/15760132 )
Just add a static method to your enum of choice, like this:
enum Example {
example1,
example2,
example3;
static Example? fromName(String name) {
for (Example enumVariant in Example.values) {
if (enumVariant.name == name) return enumVariant;
}
return null;
}
}
Then you can look for the enum like this:
Example? test = Example.fromName("example1");
print(test); // returns Example.example1
I improved Collin Jackson's answer using Dart 2.7 Extension Methods to make it more elegant.
enum Fruit { apple, banana }
extension EnumParser on String {
Fruit toFruit() {
return Fruit.values.firstWhere(
(e) => e.toString().toLowerCase() == 'fruit.$this'.toLowerCase(),
orElse: () => null); //return null if not found
}
}
main() {
Fruit apple = 'apple'.toFruit();
assert(apple == Fruit.apple); //true
}
I had the same problem with building objects from JSON. In JSON values are strings, but I wanted enum to validate if the value is correct. I wrote this helper which works with any enum, not a specified one:
class _EnumHelper {
var cache = {};
dynamic str2enum(e, s) {
var o = {};
if (!cache.containsKey(e)){
for (dynamic i in e) {
o[i.toString().split(".").last] = i;
}
cache[e] = o;
} else {
o = cache[e];
}
return o[s];
}
}
_EnumHelper enumHelper = _EnumHelper();
Usage:
enumHelper.str2enum(Category.values, json['category']);
PS. I did not use types on purpose here. enum is not type in Dart and treating it as one makes things complicated. Class is used solely for caching purposes.
Generalising #CopsOnRoad's solution to work for any enum type,
enum Language { en, ar }
extension StringExtension on String {
T toEnum<T>(List<T> list) => list.firstWhere((d) => d.toString() == this);
}
String langCode = Language.en.toString();
langCode.toEnum(Language.values);
Simplified version:
import 'package:flutter/foundation.dart';
static Fruit? valueOf(String value) {
return Fruit.values.where((e) => describeEnum(e) == value).first;
}
Using the method describeEnum helps you to avoid the usage of the split to get the name of the element.
You can write getEnum like below, getEnum will go through enum values and returns the first enum that is equal to the desired string.
Sample getEnum(String name) => Sample.values.firstWhere(
(v) => v.name.toLowerCase() == name.toLowerCase(),
orElse: () => throw Exception('Enum value not found.'),
);
enum SampleEnum { first, second, third }
UPDATE
also, you can use this:
final SampleEnum nameEnum = SampleEnum.values.byName('second'); // SampleEnum.second
Usage:
void main() {
print(getEnum('first'));
}
In the latest version of Dart, enum can support custom fields and methods. So the most modern way to do this, is to write a custom field for name/label, and a static parser function.
For example:
enum Foo {
a('FIRST'),
b('SECOND'),
c('THIRD'),
unknown('UNKNOWN'); // make sure the last element ends in `;`
final String label; // define a private field
const Foo(this.label); // constructor
static Foo fromString(String label) { // static parser method
return values.firstWhere(
(v) => v.label == label,
orElse: () => Foo.unknown,
);
}
}
Sample Usage:
final foo = Foo.fromString('FIRST'); // will get `Foo.a`
There are a couple of enums packages which allowed me to get just the enum string rather than the type.value string (Apple, not Fruit.Apple).
https://pub.dartlang.org/packages/built_value (this is more up to date)
https://pub.dartlang.org/packages/enums
void main() {
print(MyEnum.nr1.index); // prints 0
print(MyEnum.nr1.toString()); // prints nr1
print(MyEnum.valueOf("nr1").index); // prints 0
print(MyEnum.values[1].toString()) // prints nr2
print(MyEnum.values.last.index) // prints 2
print(MyEnum.values.last.myValue); // prints 15
}
Here is the function that converts given string to enum type:
EnumType enumTypeFromString(String typeString) => EnumType.values
.firstWhere((type) => type.toString() == "EnumType." + typeString);
And here is how you convert given enum type to string:
String enumTypeToString(EnumType type) => type.toString().split(".")[1];
Generalizing on #Pedro Sousa's excellent solution, and using the built-in describeEnum function:
extension StringExtension on String {
T toEnum<T extends Object>(List<T> values) {
return values.singleWhere((v) => this.equalsIgnoreCase(describeEnum(v)));
}
}
Usage:
enum TestEnum { none, test1, test2 }
final testEnum = "test1".toEnum(TestEnum.values);
expect(testEnum, TestEnum.test1);
import 'package:collection/collection.dart';
enum Page {
login,
profile,
contact,
}
Widget page(String key){
Page? link = Page.values.firstWhereOrNull((e) => e.toString().split('.').last == key);
switch (link) {
case Page.login:
return LoginView();
case Page.profile:
return const ProfileView();
case Page.contact:
return const ContactView();
default:
return const Empty();
}
}
#Collin Jackson has a very good answer IMO. I had used a for-in loop to achieve a similar result prior to finding this question. I am definitely switching to using the firstWhere method.
Expanding on his answer this is what I did to deal with removing the type from the value strings:
enum Fruit { apple, banana }
class EnumUtil {
static T fromStringEnum<T>(Iterable<T> values, String stringType) {
return values.firstWhere(
(f)=> "${f.toString().substring(f.toString().indexOf('.')+1)}".toString()
== stringType, orElse: () => null);
}
}
main() {
Fruit result = EnumUtil.fromStringEnum(Fruit.values, "apple");
assert(result == Fruit.apple);
}
Maybe someone will find this useful...
I had the same problem in one of my projects and existing solutions were not very clean and it didn't support advanced features like json serialization/deserialization.
Flutter natively doesn't currently support enum with values, however, I managed to develop a helper package Vnum using class and reflectors implementation to overcome this issue.
Address to the repository:
https://github.com/AmirKamali/Flutter_Vnum
To answer your problem using Vnum, you could implement your code as below:
#VnumDefinition
class Visibility extends Vnum<String> {
static const VISIBLE = const Visibility.define("VISIBLE");
static const COLLAPSED = const Visibility.define("COLLAPSED");
static const HIDDEN = const Visibility.define("HIDDEN");
const Visibility.define(String fromValue) : super.define(fromValue);
factory Visibility(String value) => Vnum.fromValue(value,Visibility);
}
You can use it like :
var visibility = Visibility('COLLAPSED');
print(visibility.value);
There's more documentation in the github repo, hope it helps you out.
When migrating to null-safety, the Iterable.firstWhere method no longer accepts orElse: () => null. Here is the implementation considering the null-safety:
import 'package:collection/collection.dart';
String enumToString(Object o) => o.toString().split('.').last;
T? enumFromString<T>(String key, List<T> values) => values.firstWhereOrNull((v) => key == enumToString(v!));
enum Fruit { orange, apple }
// Waiting for Dart static extensions
// Issue https://github.com/dart-lang/language/issues/723
// So we will be able to Fruit.parse(...)
extension Fruits on Fruit {
static Fruit? parse(String raw) {
return Fruit.values
.firstWhere((v) => v.asString() == raw, orElse: null);
}
String asString() {
return this.toString().split(".").last;
}
}
...
final fruit = Fruits.parse("orange"); // To enum
final value = fruit.asString(); // To string
I think my approach is slightly different, but might be more convenient in some cases. Finally, we have parse and tryParse for enum types:
import 'dart:mirrors';
class Enum {
static T parse<T>(String value) {
final T result = (reflectType(T) as ClassMirror).getField(#values)
.reflectee.firstWhere((v)=>v.toString().split('.').last.toLowerCase() == value.toLowerCase()) as T;
return result;
}
static T tryParse<T>(String value, { T defaultValue }) {
T result = defaultValue;
try {
result = parse<T>(value);
} catch(e){
print(e);
}
return result;
}
}
EDIT: this approach is NOT working in the Flutter applications, by default mirrors are blocked in the Flutter because it causes the generated packages to be very large.
enum in Dart just has too many limitations. The extension method could add methods to the instances, but not static methods.
I really wanted to be able to do something like MyType.parse(myString), so eventually resolved to use manually defined classes instead of enums. With some wiring, it is almost functionally equivalent to enum but could be modified more easily.
class OrderType {
final String string;
const OrderType._(this.string);
static const delivery = OrderType._('delivery');
static const pickup = OrderType._('pickup');
static const values = [delivery, pickup];
static OrderType parse(String value) {
switch (value) {
case 'delivery':
return OrderType.delivery;
break;
case 'pickup':
return OrderType.pickup;
break;
default:
print('got error, invalid order type $value');
return null;
}
}
#override
String toString() {
return 'OrderType.$string';
}
}
// parse from string
final OrderType type = OrderType.parse('delivery');
assert(type == OrderType.delivery);
assert(type.string == 'delivery');
another variant, how it might be solved:
enum MyEnum {
value1,
value2,
}
extension MyEnumX on MyEnum {
String get asString {
switch (this) {
case MyEnum.value1:
return _keyValue1;
case MyEnum.value2:
return _keyValue2;
}
throw Exception("unsupported type");
}
MyEnum fromString(String string) {
switch (string) {
case _keyValue1:
return MyEnum.value1;
case _keyValue2:
return MyEnum.value2;
}
throw Exception("unsupported type");
}
}
const String _keyValue1 = "value1";
const String _keyValue2 = "value2";
void main() {
String string = MyEnum.value1.asString;
MyEnum myEnum = MyEnum.value1.fromString(string);
}
enum HttpMethod { Connect, Delete, Get, Head, Options, Patch, Post, Put, Trace }
HttpMethod httpMethodFromString({#required String httpMethodName}) {
assert(httpMethodName != null);
if (httpMethodName is! String || httpMethodName.isEmpty) {
return null;
}
return HttpMethod.values.firstWhere(
(e) => e.toString() == httpMethodName,
orElse: () => null,
);
}
You can do something like this:
extension LanguagePreferenceForString on String {
LanguagePreferenceEntity toLanguagePrerence() {
switch (this) {
case "english":
return LanguagePreferenceEntity.english;
case "turkish":
return LanguagePreferenceEntity.turkish;
default:
return LanguagePreferenceEntity.english;
}
}
}

Are Rails-style chained queries available in Grails?

I am writing a scheduling feature in a new Grails 2.1.0 application. I am transitioning from a Ruby on Rails project, so much of my query strategy derives from Rails style. I have the following domain classes:
Schedule.groovy
class Schedule {
// Number of minutes between available appointment slots
int defaultAppointmentInterval
Time officeOpenStart
Time officeOpenEnd
Time lunchStart
Time lunchEnd
static hasMany = [inventorySlots: InventorySlot]
static constraints = {
// long validation rules
}
def boolean isAvailableAt(Date dateTime) {
def isAvailable = true
if (inventorySlots.isEmpty()) {
isAvailable = false
} else if (inventorySlotsSurroundingTime(dateTime).isEmpty()) {
isAvailable = false
}
isAvailable
}
def inventorySlotsSurroundingTime(Date dateTime) {
InventorySlot.surroundingTime(dateTime) {
and {
inventorySlot.schedule = this
}
}
}
}
InventorySlot.groovy
class InventorySlot {
Date startTime
static belongsTo = [schedule: Schedule]
static constraints = {
startTime nullable: false, blank: false
}
static mapping = {
tablePerHierarchy true
schedule lazy: false
}
static namedQueries = {}
def static surroundingTime(Date time) {
[UnboundedInventorySlot.surroundingTime(time), BoundedInventorySlot.surroundingTime(time)].flatten()
}
def endTime() {
return ((BoundedInventorySlot) this).endTime?: (UnboundedInventorySlot (this)).endTime()
}
}
UnboundedInventorySlot.groovy
class UnboundedInventorySlot extends InventorySlot {
static namedQueries = {
// surroundingTime { time ->
// le 'startTime', time
// ge 'startTime', time - 1
// }
}
#Override
def static surroundingTime(Date time) {
findAllByStartTimeLessThanEqualsAndStartTimeGreaterThanEquals(time, time - 1)
}
def Date endTime() {
def endTime
// If the office closing is defined, use that, otherwise use tonight # 23:59:59
endTime = schedule?.officeOpenEnd?: new DateTime(startTime + 1).withTimeAtStartOfDay().plusSeconds(-1).toDate()
return endTime
}
}
BoundedInventorySlot.groovy
class BoundedInventorySlot extends InventorySlot {
Date endTime
static constraints = {
endTime nullable: false, blank: false, validator: {val, obj ->
if (val.date != obj.startTime.date) { return ["invalid.differentDate", val.date] }
}
}
static namedQueries = {
// surroundingTime { time ->
// le 'startTime', time
// ge 'endTime', time
// }
}
#Override
def static surroundingTime(Date time) {
findAllByStartTimeLessThanEqualsAndEndTimeGreaterThanEquals(time, time)
}
#Override
def Date endTime() {
endTime
}
}
What I would like to do is to implement the Schedule#isAvailableAt(Date) method as follows:
def boolean isAvailableAt(Date dateTime) {
def isAvailable = true
if (inventorySlots.isEmpty()) {
isAvailable = false
} else if (inventorySlots.surroundingTime(dateTime).isEmpty()) {
isAvailable = false
}
isAvailable
}
where the inventorySlots.surroundingTime() invocation is essentially InventorySlot.surroundingTime() but instead of querying the universe of InventorySlots, it pre-filters on just the instances associated with the schedule instance. This is very common in Rails, but any searches for "chained query" or "collection query" in Grails doesn't seem to provide good documentation. Thanks for any help.
I can think of two approaches off the top of my head which would work:
A more complex dynamic finder:
InventorySlots.findAllByScheduleAndStartTimeLessThanEqualsAndEndTimeGreaterThanEquals(this, time, time -1)
You can chain named queries together, then use any of the autowired finders to run the actual query, so if you uncomment your named query:
static namedQueries = {
surroundingTime { time ->
le 'startTime', time
ge 'startTime', time - 1
}
}
You could simply call:
InventorySlots.surroundingTime(time).findAllBySchedule(this)
You might also want to look into where queries in Grails 2 if you are not a fan of the criteria builder syntax. They are more type safe than criteria queries, and can be chained in the same fashion.
Update: Unfortunately, I'm not familiar with the inner workings of how named queries work with polymorphism, and I presume trouble with that is why you commented that out. I think worst case though, you could build a query on the parent like this one:
surroundingTime { time ->
or {
and {
eq('class', BoundedInventorySlot.name)
le 'startTime', time
ge 'startTime', time
}
and {
eq('class', UnboundedInventorySlot.name)
le 'startTime', time
ge 'startTime', time - 1
}
}
}
***Update: Could you leverage the spread operator to simplify your task? i.e. Keep this code, but remove the .flatten() and call surroundingTime as a named query or where query.
def static surroundingTime(Date time) {
[UnboundedInventorySlot.surroundingTime(time), BoundedInventorySlot.surroundingTime(time)]
}
Then you could call:
Schedule.surroundingTime(time)*.findAllBySchedule(this).flatten()
Not ideal that the caller needs to know to combine the results, but an interesting approach maybe.
As a reference:
Query chaining in Grails' docs: http://grails.org/doc/latest/guide/GORM.html#querying
See the "Query Composition" part.
Adding an answer as this was the first search result. According to the docs, a where clause is a 'Detached Criteria'.
Example:
def query = Person.where {
lastName == "Simpson"
}
def bartQuery = query.where {
firstName == "Bart"
}
Person p = bartQuery.find()
Where Query Composition

Resources