I'm writing a service in Grails 3.
Let's say I wrote a method like this for a 'PostsService'
By default it should sort the categories by their id.
List<Category> getCategories(List<Post> posts) {
List<Category> categories = ... // logic to get categories
categories.sort { it.id }
return categories
}
What if I wanted to add flexibility for sorting. For example, later we want to sort by year instead.
I want to add the sort Closure as an optional argument.
I tried this.
List<Category> getCategories(List<Post> posts, Closure sortClosure = null) {
List<Category> categories = ... // logic to get categories
if (sortClosure == null) {
categories.sort { it.id }
} else {
// Trying to use the sortClosure. Not sure if these will work.
// sortClosure << categories.sort
// categories.sort sortClosure
}
return categories
}
I try to run the method with the Closure
List<Category> categories = postsService.getCategories(posts, { it.year })
But I'm running into errors. It throws an internal server error at this line.
List<Category> categories = postsService.getCategories(posts, { it.year })
Class java.lang.NoSuchMethodError
Message null
Caused by PostsController$_select_closure5: method (Ljava/lang/Object;Ljava/lang/Object;)V not found
What am I doing wrong here? Can I use a Closure this way?
One of the resources I've been looking at
Related
I have such entities, stored in database using GORM (I omitted irrelevant fields):
class Payment {
static hasMany = [paymentEntries: PaymentEntry]
}
class PaymentEntry {
static hasOne = [category: PaymentCategory]
static belongsTo = [payment: Payment]
static constraints = {
category(nullable: true)
}
}
class PaymentCategory {
static hasMany = [payments: PaymentEntry, rules: PaymentRule]
String name
}
So we have Payments on the top, which can have many PaymentEntryies, and each PaymentEntry can belong to one PaymentCategory.
I need to select Payments, which meet some conditions, but only the ones that also have PaymentEntries belonging to specific categories. Currently I do it in 3 queries:
Select categories which fit to part of category name, or just omit category query (in such case we want all the payments regardless of their entries category):
private static List<PaymentCategory> getCategories(String category) {
def result = null
if (category != null && category.length() > 0) {
def c = PaymentCategory.createCriteria()
result = c {
ilike("name", "%${category}%")
}
}
result
}
Select PaymentEntries ids, based on PaymentCategory:
private static List<Long> paymentEntryIds(String category) {
def categories = getCategories(category)
def c = PaymentEntry.createCriteria()
def result = new ArrayList()
// If category is selected, but there is no matching category, return empty list
if (!categorySelectedButNoMatch(category, categories)) {
result = c {
if (category == null) {
isNull("category")
} else if (categories != null && categories.size() > 0) {
inList("category", categories)
}
projections {
property("id")
}
}
}
result
}
Finally select Payments, limited to the ones that contain specific PaymentEntries:
def paymentEntriesIds = paymentEntryIds(selectedCategory)
def c = Payment.createCriteria()
def result = new ArrayList()
// If there are no payment entries matching category criteria, we return empty list anyway, so there
// is no need querying payments.
if (paymentEntriesIds.size() > 0) {
result = c {
paymentEntries {
inList("id", paymentEntriesIds)
}
if (importEntryId != null) {
eq("importEntry.id", importEntryId)
}
if (query != null) {
ilike("description", query)
}
// Omitted ordering and paging
}
}
result
This works, but it runs 3 queries to the database. I'm pretty sure this code could be cleaner, and it could be done in less queries. All the ideas on how to improve it are welcome.
You can use at least 3 different methods: detached queries, subqueries of where queries and subqueries of HQL.
Example of DetachedQuery with criteria looks like:
def subquery = new DetachedCriteria(PaymentCategory).build {
projections {
groupProperty 'id'
}
ilike("name", "%${category}%")
}
Then you can use this subquery in another query:
return PaymentEntry.createCriteria().list {
category {
inList "id", subquery
}
}
I didn't test it on your domain exactly, this should just show the idea. Refer to DetachedCriteria part of GORM documentation: https://grails.github.io/grails-doc/latest/guide/single.html#detachedCriteria
We use it for some very complicated queries. Sometimes createAlias will be required to correctly work with associations.
I am new with grails and am developing a web application.
I have a List of long values which is getting from ids of Domain class objects.
Initially this List is like [1,2,3]. I need to use this list of values in my service class for saving the associations.
But the same List is getting in service class as [49,50,51]
Why this difference of 48 is occurred? and how can i get the List as same as i sent.
Controller class:
def createQuestion(CreateQuestionCommand createQuestionCmd) {
if( createQuestionCmd.hasErrors() ) {
render(view:"create_question", model:[createQuestionCmd:createQuestionCmd , tags:Tag.list()])
} else {
Question question = new Question()
question.title=createQuestionCmd.title
question.description=createQuestionCmd.description
List tags= createQuestionCmd.tags
question = questionService.create(question,tags)
render(view: "question_submitted")
}
}
service class:
def create(Question question, List<Long> tagId) {
List<Tag> tagList=getTagsById(tagId)
question.save( failOnError:true )
Iterator itr=tagList.iterator();
while(itr.hasNext()) {
Tag tag=itr.next()
question.addToTags(tag).save()
}
}
def getTagsById(List tagId){
Iterator itr=tagId.iterator();
List<Tag> tags
while(itr.hasNext()) {
long id=itr.next()
println "value of id is : "
println id
println id.getClass().getName()
Tag tag=Tag.findById(id)
tags.add(tag)
}
return tags
}
CreateQuestionCmd.tags are List<String> and you are trying to place that to List<Long>
Just pass the object to service and create question object.In groovy we create the object in map format.In java only, we need iterator to loop collection.In groovy we use each closure to loop the collection. Try it will work out. Any problem in the following code let me know.I will help.
Controller class:
def createQuestion(CreateQuestionCommand createQuestionCmd) {
if(createQuestionCmd.hasErrors() ) {
render(view:"create_question", model:[createQuestionCmd:createQuestionCmd , tags:Tag.list()])
} else {
questionService.create(createQuestionCmd)
render(view: "question_submitted")
}
}
service class:
def create(def createQuestionCmd) {
def question = new Question(title:createQuestionCmd.title,description:createQuestionCmd.description)
question.save(flush:true)
List tagIds= createQuestionCmd.tags
tagIds.each{
def tag=Tag.findById(id)
question.addToTags(tag).save(flush:true)
}
}
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>
}
class Client {
String name
static hasMany = [courses:Course]
}
class Course {
String name
static belongsTo = [client:Client]
}
I have this and I want to get all Clients that has a Course with name = "blabla"
I was trying to do : Clients.findWhere(Course.any { course -> course.name = "math" })
You can do this with criteria:
Client.withCriteria {
courses {
eq('name', 'math')
}
}
I believe that the following where query is equivalent to the above criteria:
Client.where { courses.name == 'math' }
or you may find you need another closure:
Client.where {
courses {
name == 'math'
}
}
but I rarely use where queries myself so I'm not 100% sure of that.
There are probably a lot of different syntactical expressions to achieve the same thing. I can say definitively that this works in my project though.
def ls = Client.list {
courses {
eq('name','math')
}
}
Working in Grails 2.2
I have a situation where I need to be able to handle an unknown number of CommitteeMembers in the view. These need to be both created and displayed.
Each one has the usual attributes - name, address, contact information, userid.
I understand that if I name form fields the same name, Grails will return a collection for me to iterate over. In this case, however, I am faced with this situation:
cm_firstname
cm_lastname
cm_address
cm_email
cm_userid
So does this mean I will be given collections of each of these fields? That is not as useful as there is no way to corelate the various firstnames with the correct lastnames, etc.
I am enjoying Grails and am looking forward to your feedback.
You can use Grails Command objects to do this work for you. Here's an example in a SO question. Basically you will have a single collection of CommitteeMembers that will be populated in your controller thorugh data binding.
As #Gregg says, in the view you need the fields to have an index.
class MyDomain {
String name
}
class MyDomainCommand {
List<MyDomain> instances = ListUtils.lazyList([], FactoryUtils.instantiateFactory(MyDomain))
}
class MyController {
def save() {
MyDomainCommand command = new MyDomainCommand()
bindData(command, params, [include: 'instances'])
}
}
I'll tell you what I do, which may or may not be the best option. I do this mainly because I don't like data binding.
For your case as an example, I would name my fields: "cm.firstName, cm.lastName, cm.address, cm.email, cm.userId".
If you are in a service:
GrailsWebRequest webUtils = WebUtils.retrieveGrailsWebRequest()
List committeeMembers = [].withDefault {new GrailsParameterMap([:], webUtils.getCurrentRequest())}
In a controller:
List committeeMembers = [].withDefault {new GrailsParameterMap([:], request)}
Then
params.cm.each { k, v ->
if (v instanceof String[]) {
v.eachWithIndex { val, idx ->
committeeMembers[idx]."$k" = val
}
}
else {
committeeMembers[0]."$k" = v
}
}
Then you can do:
committeeMembers.each {
<Create from it.firstName, it.lastName, etc>
}