[81] | 1 | package dbnp.studycapturing |
---|
| 2 | |
---|
[106] | 3 | import dbnp.data.Term |
---|
| 4 | |
---|
[84] | 5 | /** |
---|
| 6 | * The Sample class describes an actual sample which results from a SamplingEvent. |
---|
[822] | 7 | * |
---|
| 8 | * Revision information: |
---|
| 9 | * $Rev: 1373 $ |
---|
| 10 | * $Author: business@keesvanbochove.nl $ |
---|
| 11 | * $Date: 2011-01-12 13:42:06 +0000 (wo, 12 jan 2011) $ |
---|
[84] | 12 | */ |
---|
[232] | 13 | class Sample extends TemplateEntity { |
---|
[667] | 14 | // uncommented due to searchable issue |
---|
| 15 | // @see http://jira.codehaus.org/browse/GRAILSPLUGINS-1577 |
---|
| 16 | //static searchable = { [only: ['name']] } |
---|
[654] | 17 | |
---|
[1034] | 18 | static belongsTo = [ |
---|
[1169] | 19 | // A Sample always belongs to one study. |
---|
[1034] | 20 | parent : Study, |
---|
[1169] | 21 | |
---|
| 22 | // A Sample optionally has a parent Subject from which it was taken, this Subject should be in the same parent study. |
---|
[1034] | 23 | parentSubject : Subject, |
---|
[1169] | 24 | |
---|
| 25 | // Also, it has a parent SamplingEvent describing the actual sampling, also within the same parent study. |
---|
[1034] | 26 | parentEvent : SamplingEvent, |
---|
[1169] | 27 | |
---|
| 28 | // And it has a parent EventGroup which tied it to its parent subject and parent event |
---|
[1034] | 29 | parentEventGroup: EventGroup |
---|
[1169] | 30 | |
---|
| 31 | // We can't have parentAssay since a Sample can belong to multiple Assays |
---|
[1034] | 32 | ] |
---|
[654] | 33 | |
---|
[652] | 34 | String name // should be unique with respect to the parent study (which can be inferred) |
---|
| 35 | Term material // material of the sample (should normally be bound to the BRENDA ontology) |
---|
[224] | 36 | |
---|
[397] | 37 | /** |
---|
| 38 | * return the domain fields for this domain class |
---|
| 39 | * @return List |
---|
| 40 | */ |
---|
[506] | 41 | static List<TemplateField> giveDomainFields() { return Sample.domainFields } |
---|
[754] | 42 | |
---|
| 43 | // We have to specify an ontology list for the material property. However, at compile time, this ontology does of course not exist. |
---|
| 44 | // Therefore, the ontology is added at runtime in the bootstrap, possibly downloading the ontology properties if it is not present in the database yet. |
---|
[540] | 45 | static List<TemplateField> domainFields = [ |
---|
| 46 | new TemplateField( |
---|
| 47 | name: 'name', |
---|
| 48 | type: TemplateFieldType.STRING, |
---|
[654] | 49 | preferredIdentifier: true, |
---|
| 50 | required: true |
---|
[540] | 51 | ), |
---|
| 52 | new TemplateField( |
---|
| 53 | name: 'material', |
---|
[1314] | 54 | type: TemplateFieldType.ONTOLOGYTERM, |
---|
| 55 | comment: "The material is based on the BRENDA tissue / enzyme source ontology, a structured controlled vocabulary for the source of an enzyme. It comprises terms for tissues, cell lines, cell types and cell cultures from uni- and multicellular organisms. If a material is missing, please add it by using 'add more'" |
---|
[540] | 56 | ) |
---|
| 57 | ] |
---|
[375] | 58 | |
---|
[232] | 59 | static constraints = { |
---|
[754] | 60 | // The parent subject is optional, e.g. in a biobank of samples the subject could be unknown or non-existing. |
---|
[288] | 61 | parentSubject(nullable:true) |
---|
[1034] | 62 | |
---|
[960] | 63 | // The same holds for parentEvent |
---|
[1169] | 64 | parentEvent(nullable:true) |
---|
[1034] | 65 | |
---|
| 66 | // and for parentEventGroup |
---|
| 67 | parentEventGroup(nullable:true) |
---|
[1169] | 68 | |
---|
[754] | 69 | // The material domain field is optional |
---|
[654] | 70 | material(nullable: true) |
---|
| 71 | |
---|
[754] | 72 | // Check if the externalSampleId (currently defined as name) is really unique within each parent study of this sample. |
---|
[654] | 73 | // This feature is tested by integration test SampleTests.testSampleUniqueNameConstraint |
---|
[689] | 74 | name(unique:['parent']) |
---|
[861] | 75 | |
---|
| 76 | // Same, but also when the other sample is not even in the database |
---|
| 77 | // This feature is tested by integration test SampleTests.testSampleUniqueNameConstraintAtValidate |
---|
| 78 | name(validator: { field, obj, errors -> |
---|
| 79 | // 'obj' refers to the actual Sample object |
---|
| 80 | |
---|
| 81 | // define a boolean |
---|
| 82 | def error = false |
---|
| 83 | |
---|
[1373] | 84 | // check whether obj.parent.samples is not null at this stage to avoid null pointer exception |
---|
[861] | 85 | if (obj.parent) { |
---|
| 86 | |
---|
[1373] | 87 | if (obj.parent.samples) { |
---|
| 88 | |
---|
| 89 | // check if there is exactly one sample with this name in the study (this one) |
---|
| 90 | if (obj.parent.samples.findAll{ it.name == obj.name}.size() > 1) { |
---|
| 91 | error = true |
---|
| 92 | errors.rejectValue( |
---|
| 93 | 'name', |
---|
| 94 | 'sample.UniqueNameViolation', |
---|
| 95 | [obj.name, obj.parent] as Object[], |
---|
| 96 | 'Sample name {0} appears multiple times in study {1}' |
---|
| 97 | ) |
---|
| 98 | } |
---|
[861] | 99 | } |
---|
| 100 | } |
---|
| 101 | else { |
---|
| 102 | // if there is no parent study defined, fail immediately |
---|
| 103 | error = true |
---|
| 104 | } |
---|
| 105 | |
---|
| 106 | // got an error, or not? |
---|
| 107 | return (!error) |
---|
| 108 | }) |
---|
[232] | 109 | } |
---|
[81] | 110 | |
---|
[1036] | 111 | static mapping = { |
---|
| 112 | sort "name" |
---|
[1198] | 113 | |
---|
| 114 | // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754 |
---|
| 115 | templateTextFields type: 'text' |
---|
[1036] | 116 | } |
---|
| 117 | |
---|
[288] | 118 | static getSamplesFor( event ) { |
---|
[540] | 119 | return Sample.findAll( 'from Sample s where s.parentEvent =:event', [event:event] ) |
---|
[288] | 120 | } |
---|
[651] | 121 | |
---|
[697] | 122 | def String toString() { |
---|
| 123 | return name |
---|
| 124 | } |
---|
[81] | 125 | } |
---|