I have the following class:
class User {
...
static hasMany = [data: MyData]
...
}
I would like to get user.data on a User object but filter the returned list using metaParams like in findAllBy (https://grails.github.io/grails-doc/latest/ref/Domain%20Classes/findAllBy.html).
Is this possible?
With a criteria query you can do something like this:
def id = /* some User.id here */
// http://grails.github.io/grails-doc/2.1.0/ref/Domain%20Classes/createCriteria.html
def data = User.createCriteria().list(max: 10, offset: 100) {
projections {
property 'data'
}
eq 'id', id
order 'something', 'desc'
}
If you end up with duplicate MyData instances, try using HQL instead. Like this:
// https://grails.github.io/grails-doc/latest/ref/Domain%20Classes/executeQuery.html
User.executeQuery 'select distinct u.data from User as u where u.id = :id', [id: id, max: 10, offset: 5]
Related
So i have the below module in an ElasticSearch concern for my Model in rails.
This is working, but how do I make each of the bool query(must, must_not, filter) accept nil or empty parameters?
Say if I pass an empty query_string it would get all the documents.
Then when I pass an empty size parameter it will return all sizes.
module ClassMethods
def home_page_search(query_string, size, start_date, end_date)
search({
query: {
bool: {
must: [
{
multi_match: {
query: query_string,
fields: [:brand, :name, :notes, :size_notes]
}
}
],
must_not: [
range: {
unavailable_dates: { gte: start_date, lte: end_date }
}
],
filter: [
{ term: { size: size } }
]
}
}
})
end
end
I solved a similar problem by constructing the query string on more of an as-needed basis, so I only included a clause if there was a search term for it. The query I sent to Elasticsearch only included the terms that were actually set by the user. For example:
if size.present?
query[:query][:bool][:filter] = { term: { size: size } }
end
(assuming the correct representation of the query, etc.)
I have a list with fields
I need to filter only on a specific parameter
Model:
class Doc {
static constraints = {
name()
parameter(inList: ["Список",
"Строка",
"Число"])
}
String name
List parameter
}
In index.gsp
<label for="query">Where:</label>
<g:select value="${params.parameter}" name="parameter" from="${parameterList}" noSelection="['':'All parameter']" />
In controller
def index(Integer max) {
params.max = Math.min(params.max ? params.int('max') : 2, 10)
def parameterList = Doc.parameter.list()
if (params.parameter != "" ) {
def item = parameterList.find { p -> inList(p.parameter, "%${params.parameter}%")}
}
[params: params, parameterList: parameterList]
}
That is, for example
There are data:
name: a1 parameter: [[list], [string]]
name: a2 parameter: [[list], [number]]
name: a3 parameter: [number]
I choose to filter by "number" and I only need to get two values of "a2" and "a3"
//your params data sample (not sure about this point)
def params=[
a1: ['123','234'],
a2: [123,234],
a3: 123,
a4: '123',
]
//the filter
def filter = "number"
//map from filter to filtering class
def filterClazz = [
number: Number.class,
string: CharSequence.class,
].get(filter)
//the filter logic
def filteredKeys = params.findAll{k,v->
//if value is collection then lookup if any element instance of our filter class
if (v instanceof Collection)return v.find{filterClazz.isInstance(it)}
//otherwise (not collection) just check if value is filter class
return filterClazz.isInstance(v);
}.collect{it.getKey()} //collect only key names
println filteredKeys
This is driving me nuts. 2.4.4 My integration tests all pass. Upgrading to 2.5.5 and I get errors like this all over the place:
No signature of method: Project.addToMonitorings() is applicable for argument types: (Monitoring) values: [Monitoring : (unsaved)] Possible solutions: getMonitorings()
I cannot seem to track down how to update the integration tests to make them pass again.
Example (current) Test:
class MonitoringServiceSpec extends Specification {
def monitoringService
TestDataFactory f // factory that builds objects so we can use them in other tests
def setup() {
f = new TestDataFactory()
}
void "results can be limited"() {
given:
Project p = f.getProject()
p.save(flush: true, failOnError: true)
def params = new EcosListParams(new GrailsParameterMap ([offset: 0, max:1, sortColumn: 'id', order: 'asc'], null))
when:
p.addToMonitorings(f.getMonitoring(p)).save(flush: true, failOnError: true)
p.refresh()
def results = monitoringService.getProjectMonitorings(params, p.id)
then:
results.totalCount == 2
results.size() == 1
}
...
}
I get this error everywhere in my app that have one-to-many relationships. They worked perfectly fine in 2.4.4.
Here is what I had to do to get it to work. The getMonitoring method in the data factory already added the Project to the object. It must implicitly do an addTo
import groovy.sql.Sql;
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap
import spock.lang.*
class MonitoringServiceSpec extends Specification {
def f = new TestDataFactory()
def proj
def sql
def dataSource
def monitoringService
def setup() {
sql = new Sql(dataSource);
proj = f.getProject().save()
Monitoring mon1 = f.getMonitoring(proj).save()
Monitoring mon2 = f.getMonitoring(proj).save()
// don't need addTo, the getMonitoring method above implicitly adds it to the project
proj.save(flush: true, failOnError: true).refresh()
}
void "results can be limited"() {
given:
def params = new EcosListParams(new GrailsParameterMap ([offset: 0, max:1, sortColumn: 'id', order: 'asc'], null))
when:
def results = monitoringService.getProjectMonitorings(params, proj.id)
then:
results.totalCount == 2
results.size() == 1
}
void "results can be offset"() {
given:
def params1 = new EcosListParams(new GrailsParameterMap ([offset: 0, max:1, sortColumn: 'id', order: 'asc'], null))
def params2 = new EcosListParams(new GrailsParameterMap ([offset: 1, max:1, sortColumn: 'id', order: 'asc'], null))
when:
def results1 = monitoringService.getProjectMonitorings(params1, proj.id)
def results2 = monitoringService.getProjectMonitorings(params2, proj.id)
then:
results1.size() > 0
results1.id != results2
}
}
TestDataFactory
Monitoring getMonitoring(Project p) {
HabitatObjectiveSuccess hos = HabitatObjectiveSuccess.list(max: 1).get(0);
return new Monitoring(visitDate: new Date(), notes: 'notes', created: new Date(), createdBy: getPerson(),
lastUpdated: new Date(), lastUpdatedBy: getPerson(), maintActivitiesOccurring: 2,
maintActivitiesOccurrText: 'maintActivitiesOccurrText', landownerObjectivesMet: 1,
landownerObjectivesMetText: 'landownerObjectivesMetText', speciesObjectivesMet: 1,
speciesObjectivesMetText: 'speciesObjectivesMetText', habitatObjectiveSuccess: hos,
habitatObjectiveSuccessText: 'habitatObjectiveSuccessText', project: p
)
}
Domain Setup
.
Study {
Long id
String name
Site site
USState state
...
}
.
Site {
Long id
String name
...
}
.
Resource {
Long id
String name
Boolean active
USState state
...
}
.
SiteResource {
id composite: ['site', 'resource']
Site site
Resource resource
}
I have 2 lists that get populated for the Study.
One is a list of currently associated Resources to the Site
Another is a list of available Resources that can be associated to the Site. These available Resources are determined by being Active = true and within the same USState as the Study
I'm trying to write a criteria to get this to work but cannot seem to get it. I need to show all Resources that are Active, in the same USState as the Study and not currently associated with the Site (the current associations are in the first table)
return Resource.createCriteria().list(sort: params.sort, order: params.order, max: params.max, offset: params.offset) {
if (params.searchText) {
ilike("name", "%${params.searchText}%")
}
eq("usState", params.state)
eq("active", true)
ne "id" , new DetachedCriteria(SiteResource).build {
'in' ("resource", params.associatedResources.resource.id)
'in' ("site", params.associatedResources.site.id)
}
}
I've also tried the following:
def siteResources = getSiteResourcesBySite(site).collect {
it.resource
}
def resources = resource.findAllByRefugeAndActiveNotInList(refuge, true, siteResources, [sort:"name"])
return resources.list(sort: params.sort, order: params.order, max: params.max, offset: params.offset)
I hope this made sense; I'm still trying to figure out how the DetachedQuery works. If there is a better way to accomplish this task please enlighten me!
Criteria builder was not needed. Here's my solution:
def site = siteService.getSite(params.siteId)
def stateCode = site.study.state.code
def state= State.findByCode(stateCode )
List<Resource> associatedResources = siteResourceService.getAssociatedResourcesBySite(site).collect { it.id }
params.state = state
params.site = site
...
def getUnassociatedResourcesByState(params, List associatedResources) {
params.max = Math.min(params.max ? Integer.parseInt(params.max) : 25, 100)
params.offset = params.offset ? params.offset : 0
params.sort = params.sort ? params.sort : 'name'
params.order = params.order == 'desc' ? params.order : 'asc'
return Resource.findAllByStateAndActiveAndIdNotInList(params.state, true, associatedResources, [sort: params.sort, order: params.order, max: params.max, offset: params.offset])
}
The domain models: type1, type2, type3 and plant.
$ rails g model type1 name:string
$ rails g model type2 name:string
$ rails g model type3 type1:references type2:references name:string
$ rails g model plant type1:references type2:references type3:references name:string
In a plant's grid panel, there will be three combobox columns: type1, type2 and type3. Type3 depends on type1 and type2. How to filter type3 combobox when any one of type1 and type2 is selected?
I'm new to Rails and Netzke, but this is how I would solve the problem.
First, if Type3 depends on Type1 and Type2, then only Type3 should be referenced in Plant.
So, instead of
rails g model plant type1:references type2:references type3:references name:string
use
rails g model plant type3:references name:string
You can always reference type1 and type2 by using Netzke's __ (double underline) notation. This is my version of Plants grid. I do not allow inline editing, except for most trivial models.
class Plants < Netzke::Basepack::Grid
def configure(c)
super
c.model = 'Plant'
c.persistence = true
c.columns = [
{ name: :type3__type1__name, header: 'Type 1'},
{ name: :type3__type2__name, header: 'Type 2'},
{ name: :type3__name, header: 'Type 3'},
{ name: :name, header: 'Plant Name' }
]
c.enable_edit_inline = false
c.enable_add_inline = false
end
def preconfigure_record_window(c)
super
c.form_config.klass = PlantForm
end
end
To connect the combo boxes you need to:
Define scope for type3 combo so that its data depends on type1 and type2 IDs.
#Example scope definition
scope: {type1_id: component_session[:type1_id],
type2_id: component_session[:type2_id]}
Define listeners for type1 and type2 combos (see js_configure method). Listeners will detect any change in type1 and type2 and prepare type3 to refresh its data next time it gets selected.
Endpoints and session variables are used for IDs exchange.
//JavaScript code
//Definition of listener function for type1 combo
var handleType1Change = function() {
//Type3 value is no longer valid
type3Combo.clearValue();
//Setting lastQuer to null will force data refresh for type3 combo
//next time it gets selected.
type3Combo.lastQuery = null;
//Call endpoint to define session variable with ID of type1 combo
this.selectType1({type1_id: type1Combo.value});
};
#Ruby code
#The endpoint is called from handleType1Chnage listener to
#set session variable with selected ID in type1 combo.
endpoint :select_type1 do |params, this|
component_session[:type1_id] = params[:type1_id]
end
This is my complete code for Plant form:
class PlantForm< Netzke::Basepack::Form
def configure(c)
super
c.model = 'Plant'
c.title = 'Plant'
c.items = [
{
field_label: 'Type1',
xtype: :combo,
store: Type1.select([:id, :name]).map { |x| [x.id, x.name] },
id: 'type1Combo',
#Sets the value for type1 in case change form is opened
value: record && record.id ? record.type3.type1_id : nil
},
{
field_label: 'Type2',
xtype: :combo,
store: Type2.select([:id, :name]).map { |x| [x.id, x.name] },
id: 'type2Combo',
#Sets the value for type2 in case change form is opened
value: record && record.id ? record.type3.type2_id : nil
},
{
field_label: 'Type3',
name: :type3__name,
id: 'type3Combo',
data_store: {auto_load: false},
scope: {type1_id: component_session[:type1_id],
type2_id: component_session[:type2_id]}
},
{ field_label: 'Name', name: :name }
]
end
js_configure do |c|
c.init_component = <<-JS
function() {
this.callParent();
var type1Combo = this.getComponent('type1Combo');
var type2Combo = this.getComponent('type2Combo');
var type3Combo = this.getComponent('type3Combo');
var handleType1Change = function() {
type3Combo.clearValue();
type3Combo.lastQuery = null; //force data refresh in type3 combo
this.selectType1({type1_id: type1Combo.value});
};
var handleType2Change = function() {
type3Combo.clearValue();
type3Combo.lastQuery = null;
this.selectType2({type2_id: type2Combo.value});
};
type1Combo.addListener('select', handleType1Change, this);
type2Combo.addListener('select', handleType2Change, this);
}
JS
end
endpoint :select_type1 do |params, this|
component_session[:type1_id] = params[:type1_id]
end
endpoint :select_type2 do |params, this|
component_session[:type2_id] = params[:type2_id]
end
end