I have many queries that have the same logic and I decided to extract it into closure.
Here is an example:
Closure whereByProjectIdAndUser = { Criteria cr, Long projectId, User user ->
Long userId = user.id
Boolean isReviewer = user.isReviewer()
cr.isNull 'project.deletedAt'
cr.eq 'project.id', projectId
(cr | {
cr.eq 'owner.id', userId
if (isReviewer) {
cr & {
cr.eq 'reviewer.id', userId
cr.ne 'project.certificationStatus', ProjectCertificationStatus.None
}
}
})
}
#Transactional(readOnly = true)
RrmUtilization[] getAllByProjectIdAndUser(Long projectId, User user) {
BuildableCriteria cr = RrmUtilization.createCriteria()
RrmUtilization[] result = cr.list {
createAlias('project', 'project', JoinType.INNER_JOIN)
createAlias('project.owner', 'owner', JoinType.INNER_JOIN)
createAlias('project.reviewer', 'reviewer', JoinType.LEFT_OUTER_JOIN)
isNull 'deletedAt'
whereByProjectIdAndUser(cr, projectId, user)
} as RrmUtilization[]
result
}
This works just fine, however, I was trying to understand how can I avoid explicitly passing BuildableCriteria into the closure? Is there a way to have it implicitly passed through and get a nicer code?
Maybe there are some better approaches in general?
You can use the keyword this to access parameters in the enclosing class of the closure.
Groovy documentation: http://groovy-lang.org/closures.html#closure-owner
Related
I have a service that I'm trying to test. Inside this service is another UserAPIService that I want to mock. To mock it I'm doing the following:
given:
def userAPIServiceMock = mockFor(UserAPIService)
userAPIServiceMock.demand.createUser { def apiToken, firstname, lastname, email -> return true
}
service.userAPIService = userAPIServiceMock.createMock()
In the demand closure, I don't really care what arguments are passed to the createUser method. Is there a way I can say "regardless of the arguments passed to the createUser method, return true."
In other words, how can I change
userAPIServiceMock.demand.createUser { def apiToken, firstname, lastname, email -> return true
to
userAPIServiceMock.demand.createUser { [any arguments] -> return true
This is possible with Spock.
The syntax is as follows:
mock.method(*_) >> { args -> true }
Not sure how your Grails service needs to be mocked, but here's a full, general example in Spock:
interface Service {
boolean hej( String s, boolean b, char c )
}
class ExampleSpec extends Specification {
def "mock method with any number of args"() {
when:
def mock = Mock( Service )
mock.hej(*_) >> { args -> true }
then:
mock.hej( 'hi', true, 'a' as char ) == true
}
}
args is a List containing the actual arguments, which you can inspect in the closure and return the appropriate value.
Is there a built-in / easy way to set mappings between domain class properties and JSON strings that don't have exact matches for the property names?
For example, when I have a domain class:
class Person {
String jobTitle
String favoriteColor
static constraints = {
jobTitle(blank: false)
favoriteColor(blank: false)
}
}
And someone's giving me the following JSON:
{ "currentJob" : "secret agent", "the-color" : "red" }
I'd like to be able to still do this:
new Person(request.JSON).save()
Is there a way in groovy/grails for me to map currentJob -> jobTitle and the-color -> favorite color?
EDIT:
I've done a little experimenting, but I still haven't gotten it working. But I have found out a couple interesting things...
At first I tried overwriting the setProperty method:
#Override
setProperty(String name, Object value) {
if(this.hasProperty(name)) this[name] = value
else {
switch(name) {
'currentJob': this.jobTitle = value; break;
'the-color': this.favoriteColor = value; break;
}
}
}
But this doesn't work for two reasons: 1) setProperty is only called if there is a property that matches name and 2) "this[name] = value" calls setProperty, leading to an infinite recursive loop.
So then I thought, well screw it, I know what the incoming json string looks like (If only I could control it), I'll just get rid of the line that handles the scenario where the names match and I'll override hasProperty, maybe that will work:
#Override
void setProperty(String name, Object value) {
switch(name) {
'currentJob': this.jobTitle = value; break;
'the-color': this.favoriteColor = value; break;
}
}
#Override
boolean hasProperty(String name) {
if(name == "currentJob" || name == "the-color") return true
return false
}
But no, that didn't work either. By a random stroke of luck I discovered, that not only did I have to overwrite hasProperty(), but I also had to have an empty setter for the property.
void setCurrentJob(){ }
That hack worked for currentJob - I guess setProperty only gets called if hasProperty returns true and there is a setter for the property (Even if that setter is auto generated under the covers in grails). Unfortunately I can't make a function "setThe-Color" because of the dash, so this solution doesn't work for me.
Still stuck on this, any help would definitely be appreciated.
EDIT:
Overriding the void propertyMissing(String name, Object value){} method is called by this:
Person person = new Person()
person["currentJob"] = "programmer"
person["the-color"] = "red"
But not by this:
Person person = new Person(["currentJob":"programmer", "the-color":"red"])
I have a method which returns Iqueryable result, but the result is based on an if else condition, where if condition satisfies then I will use "AssetDetails" class object ,otherwise "UserandClientdetails" object.
Here is the code:
private IQueryable<?> GetAssetDetails(ShareViewModel item)
{
...
if (type == "Video")
{
if (type == "Video")
{
return from meta in my.Assets().OfType<Model.Video>()
join content in my.Contents() on meta.ContentId equals content.ID
join channel in my.Channels() on content.ChannelId equals channel.ID
where meta.ID == item.ID
select new AssetDetails
{
ContentTitle = content.Title,
ChannelName = channel.ChannelName,
...
};
}
else
{ return from meta in my.Assets().OfType<Model.Client>()
join country in db.Countries on meta.ResellerCountry equals country.ID
where meta.ID == item.ID
select new UserAndClientDetails
{
Name = meta.ResellerName,
UserName = meta.ResellerEmail,
..
};}
So how to decide type of Iqueyable here at runtime??
So, I was able to verify that this works, so I'll go ahead and post it as an answer.
You can return IQueryable instead of the generic IQueryable<>. That will accept any IQueryable<T>. However, IQueryable, since it has no direct inner type, is very limited. So, you'll still likely need to cast to IQueryable<> at some other point in your code to get anything done:
// Piece of code where you know you are working with `IQueryable<AssetDetails>`
IQueryable<AssetDetails> assetDetails = GetAssetDetails(someItem);
That's a little dangerous, though, as you're assuming that your code is working perfectly and the right type of thing is being returned. Better would be:
try
{
var assetDetails = (IQueryable<AssetDetails>)GetAssetDetails(someItem);
// do something with `assetDetails`
}
catch (InvalidCastException)
{
// recover gracefully
}
What about using a base class ?
public abstract class BaseDetails
{
// ...
}
public class AssetDetails : BaseDetails
{
// ...
}
public class UserAndClientDetails: BaseDetails
{
// ...
}
Then you method would be like :
private IQueryable<BaseDetails> GetAssetDetails(ShareViewModel item)
{
// return either IQueryable<AssetDetails> or IQueryable<UserAndClientDetails>
}
This example here is from the grails docs:
def emeaCriteria = {
eq "region", "EMEA"
}
def results = Airport.withCriteria {
emeaCriteria.delegate = delegate
emeaCriteria()
flights {
like "number", "BA%"
}
}
My webpage is passing back a checkbox group of ethnicities, returning the row ids. So what the server gets is:
ethnicity:[1, 4]
or if the user only picks one ethnicity:
ethnicity:4
def criteria = { params ->
//handle case where only one ethnicity is returned as just a string, not a list of strings
def list = params.ethnicty instanceof String ? [params.ethnicty] : params.ethnicity
if (list) {
inList('ethnicity', list)
}
}
I'm getting an error: java.lang.String cannot be cast to java.lang.Enum.
If I didn't have a list I think I could figure it out. The params are sending back string values, and they need to be converted to the enum class. But within the closure, how do you convert each entry into a list to the enum?
I figured it out through a combination of multiple website posts and with help from dmahapatro above.
def genderCriteria = {
if (params.gender) {
inList('gender', params.list('gender').collect { Gender.valueOf(it)} )
}
}
If a webpage passes back one or more enums (a single string or a list of strings) and you want criteria to check values from the list passed back, you have to provide a list of enum types (not strings or ints).
Here is my enum class for reference:
public enum Gender {
M('Male'),
F('Female'),
U('Unknown')
final String value
Gender(String value) {
this.value = value
}
public String toString() {
value
}
public String getKey() {
name()
}
public String getValue() {
value
}
}
And my criteria builder:
def c = MyDomain.createCriteria()
results = c.list {
genderCriteria.delegate = delegate
genderCriteria(params)
}
Even if no values are passed back for the gender field, it still works (because of the if statement in genderCriteria.
It may not be the best or cleanest solution, but it does work.
Assume a domain class called User. User class looks like this:
class User {
List<String> values
}
The collection values contains strings like "http://example.com/x", "http://google.com/y", "http://google.com/z" and so on...
Let's say we want to build a query which gets all the users that have specific string in the collection values (e.g. "google.com"). Something like this:
def criteria = User.createCriteria()
def results = criteria.listDistinct () {
and {
user.values.each { like('someField', '%${it}%') }
}
}
Any ideas?
I have found the answer by experimentation. The solution is:
def criteria = User.createCriteria()
def results = criteria.listDistinct () {
and {
user.values.each { like('someField', '%'+it+'%') }
}
}
I am not sure what you are doing with your suggested answer.
I have never seen that usage of each in the criteria query before.
This question has been asked many times before but never given an answer.
The problem is that you are queriyng a String association, which is not a domain class. If you would make your own String domain class for example ondrej.String { String strValue } then you would be able to do :
User.withCriteria {
values { ilike("strValue", "...") }
}
The problem is not having access to the value of the String object. The value of the String class is called value, but it is a char array, so I do not believe the following will work:
User.withCriteria {
values { ilike("value", "...") }
}
You could try using :
User.withCriteria {
values { ilike("toString", "...") }
}
or something else instead of toString ... I do not have the possibility to test this right now.
Hope that helps
After a lot of trying and researching, I found this will work with Grails 2.4.0, I don't know about older versions.
Cat.withCriteria {
createAlias('nicknames', 'n')
ilike 'n.elements', '%kitty%'
}
The trick is to use 'n.elements'