source: trunk/grails-app/domain/dbnp/studycapturing/Study.groovy @ 861

Last change on this file since 861 was 861, checked in by keesvb, 10 years ago

added test for Sample name uniqueness at the validate() stage

  • Property svn:keywords set to Author Rev Date
File size: 10.0 KB
Line 
1package dbnp.studycapturing
2
3import dbnp.user.User
4
5/**
6 * Domain class describing the basic entity in the study capture part: the Study class.
7 *
8 * Revision information:
9 * $Rev: 861 $
10 * $Author: keesvb $
11 * $Date: 2010-08-30 13:45:44 +0000 (ma, 30 aug 2010) $
12 */
13class Study extends TemplateEntity {
14        static searchable = {
15        [only: ['title', 'Description']]
16    }
17
18        User owner   // The owner of the study. A new study is automatically owned by its creator.
19        String title        // The title of the study
20        String code             // currently used as the external study ID, e.g. to reference a study in a SAM module
21        Date dateCreated
22        Date lastUpdated
23        Date startDate
24    List subjects
25        List events
26        List samplingEvents
27        List eventGroups
28        List samples
29        List assays
30
31        static hasMany = [
32                editors: User,   // Users with read/write access to the study
33                readers: User,   // Users with only read access to the study
34                subjects: Subject,
35                samplingEvents: SamplingEvent,
36                events: Event,
37                eventGroups: EventGroup,
38                samples: Sample,
39                assays: Assay,
40                persons: StudyPerson,
41                publications: Publication
42        ]
43
44        static constraints = {
45                owner(nullable: true, blank: true)
46                code(nullable:false, blank:true,unique:true) 
47        }
48
49        static mapping = {
50                researchQuestion type: 'text'
51                description type: 'text'
52                autoTimestamp true
53        }
54
55        // The external study ID is currently defined as the code of the study.
56        // It is used from within dbNP submodules to refer to particular study in this GSCF instance.
57        def getExternalStudyId() { code }
58
59        /**
60         * return the domain fields for this domain class
61         * @return List
62         */
63        static List<TemplateField> giveDomainFields() { return Study.domainFields }
64
65        static final List<TemplateField> domainFields = [
66                new TemplateField(
67                        name: 'title',
68                        type: TemplateFieldType.STRING),
69                new TemplateField(
70                        name: 'code',
71                        type: TemplateFieldType.STRING,
72                        preferredIdentifier:true,
73                        comment: 'Fill out the code by which many people will recognize your study'),
74                new TemplateField(
75                        name: 'startDate',
76                        type: TemplateFieldType.DATE,
77                        comment: 'Fill out the official start date or date of first action')
78        ]
79
80        /**
81         * return the title of this study
82         */
83        def String toString() {
84                return code + " - " + title
85        }
86
87        /**
88         * returns all events and sampling events that do not belong to a group
89         */
90        def Set<Event> getOrphanEvents() {
91                def orphans =   events.findAll { event -> !event.belongsToGroup(eventGroups) } +
92                                                samplingEvents.findAll { event -> !event.belongsToGroup(eventGroups) }
93
94                return orphans
95        }
96
97        /**
98         * Return the unique Subject templates that are used in this study
99         */
100        def Set<Template> giveSubjectTemplates() {
101                TemplateEntity.giveTemplates(subjects)
102        }
103
104        /**
105         * Return all subjects for a specific template
106         * @param Template
107         * @return ArrayList
108         */
109        def ArrayList<Subject> giveSubjectsForTemplate(Template template) {
110                subjects.findAll { it.template.equals(template) }
111        }
112
113        /**
114         * Return all unique assay templates
115         * @return Set
116         */
117        Set<Template> giveAllAssayTemplates() {
118                TemplateEntity.giveTemplates(( (assays) ? assays : [] ))
119        }
120
121        /**
122         * Return all assays for a particular template
123         * @return ArrayList
124         */
125        def ArrayList giveAssaysForTemplate(Template template) {
126                assays.findAll { it.template.equals(template) }
127        }
128
129        /**
130         * Return the unique Event and SamplingEvent templates that are used in this study
131         */
132        Set<Template> giveAllEventTemplates() {
133                // For some reason, giveAllEventTemplates() + giveAllSamplingEventTemplates()
134                // gives trouble when asking .size() to the result
135                // So we also use giveTemplates here
136                TemplateEntity.giveTemplates( ((events) ? events : []) + ((samplingEvents) ? samplingEvents : []) )
137        }
138
139
140        /**
141         * Return all events and samplingEvenets for a specific template
142         * @param Template
143         * @return ArrayList
144         */
145        def ArrayList giveEventsForTemplate(Template template) {
146                def events = events.findAll { it.template.equals(template) }
147                def samplingEvents = samplingEvents.findAll { it.template.equals(template) }
148
149                return (events) ? events : samplingEvents
150        }
151
152        /**
153         * Return the unique Event templates that are used in this study
154         */
155        Set<Template> giveEventTemplates() {
156                TemplateEntity.giveTemplates(events)
157        }
158
159        /**
160         * Return the unique SamplingEvent templates that are used in this study
161         */
162        Set<Template> giveSamplingEventTemplates() {
163                TemplateEntity.giveTemplates(samplingEvents)
164        }
165
166        /**
167         * Returns the unique Sample templates that are used in the study
168         */
169        Set<Template> giveSampleTemplates() {
170                TemplateEntity.giveTemplates(samples)
171        }
172
173        /**
174         * Return all samples for a specific template
175         * @param Template
176         * @return ArrayList
177         */
178        def ArrayList<Subject> giveSamplesForTemplate(Template template) {
179                samples.findAll { it.template.equals(template) }
180        }
181
182        /**
183         * Returns the template of the study
184         */
185        Template giveStudyTemplate() {
186                return this.template
187        }
188
189
190        /**
191         * Delete a specific subject from this study, including all its relations
192         * @param subject The subject to be deleted
193         * @return A String which contains a (user-readable) message describing the changes to the database
194         */
195        String deleteSubject(Subject subject) {
196                String msg = "Subject ${subject.name} was deleted"
197
198                // Delete the subject from the event groups it was referenced in
199                this.eventGroups.each {
200                        if (it.subjects.contains(subject)) {
201                                it.removeFromSubjects(subject)
202                                msg += ", deleted from event group '${it.name}'"
203                        }
204                }
205
206                // Delete the samples that have this subject as parent
207                this.samples.findAll { it.parentSubject.equals(subject) }.each {
208                        // This should remove the sample itself too, because of the cascading belongsTo relation
209                        this.removeFromSamples(it)
210                        // But apparently it needs an explicit delete() too
211                        it.delete()
212                        msg += ", sample '${it.name}' was deleted"
213                }
214
215                // This should remove the subject itself too, because of the cascading belongsTo relation
216                this.removeFromSubjects(subject)
217                // But apparently it needs an explicit delete() too
218                subject.delete()
219
220                return msg
221        }
222
223        /**
224         * Delete an event from the study, including all its relations
225         * @param Event
226         * @return String
227         */
228        String deleteEvent(Event event) {
229                String msg = "Event ${event} was deleted"
230
231                // remove event from the study
232                this.removeFromEvents(event)
233
234                // remove event from eventGroups
235                this.eventGroups.each() { eventGroup ->
236                        eventGroup.removeFromEvents(event)
237                }
238
239                return msg
240        }
241
242        /**
243         * Delete a samplingEvent from the study, including all its relations
244         * @param SamplingEvent
245         * @return String
246         */
247        String deleteSamplingEvent(SamplingEvent samplingEvent) {
248                String msg = "SamplingEvent ${samplingEvent} was deleted"
249
250                // remove event from eventGroups
251                this.eventGroups.each() { eventGroup ->
252                        eventGroup.removeFromSamplingEvents(samplingEvent)
253                }
254
255                // Delete the samples that have this sampling event as parent
256                this.samples.findAll { it.parentEvent.equals(samplingEvent) }.each {
257                        // This should remove the sample itself too, because of the cascading belongsTo relation
258                        this.removeFromSamples(it)
259                        // But apparently it needs an explicit delete() too
260                        it.delete()
261                        msg += ", sample '${it.name}' was deleted"
262                }
263
264                // Remove event from the study
265                // This should remove the event group itself too, because of the cascading belongsTo relation
266                this.removeFromSamplingEvents(samplingEvent)
267
268                // But apparently it needs an explicit delete() too
269                // (Which can be verified by outcommenting this line, then SampleTests.testDeleteViaParentSamplingEvent fails
270                samplingEvent.delete()
271
272                return msg
273        }
274       
275        /**
276         * Delete an eventGroup from the study, including all its relations
277         * @param EventGroup
278         * @return String
279         */
280        String deleteEventGroup(EventGroup eventGroup) {
281                String msg = "EventGroup ${eventGroup} was deleted"
282
283                // If the event group contains sampling events
284                if (eventGroup.samplingEvents) {
285                        // remove all samples that originate from this eventGroup
286                        if (eventGroup.samplingEvents.size()) {
287                                // find all samples related to this eventGroup
288                                // - subject comparison is relatively straightforward and
289                                //   behaves as expected
290                                // - event comparison behaves strange, so now we compare
291                                //              1. database id's or,
292                                //              2. object identifiers or,
293                                //              3. objects itself
294                                //   this seems now to work as expected
295                                this.samples.findAll { sample ->
296                                        (
297                                                (eventGroup.subjects.findAll {
298                                                        it.equals(sample.parentSubject)
299                                                })
300                                                &&
301                                                (eventGroup.samplingEvents.findAll {
302                                                        (
303                                                                (it.id && sample.parentEvent.id && it.id==sample.parentEvent.id)
304                                                                ||
305                                                                (it.getIdentifier() == sample.parentEvent.getIdentifier())
306                                                                ||
307                                                                it.equals(sample.parentEvent)
308                                                        )
309                                                })
310                                        )
311                                }.each() {
312                                        // remove sample from study
313
314                                        // -------
315                                        // NOTE, the right samples are found, but the don't
316                                        // get deleted from the database!
317                                        // -------
318
319                                        println ".removing sample '${it.name}' from study '${this.title}'"
320                                        msg += ", sample '${it.name}' was deleted"
321                                        this.removeFromSamples( it )
322
323                                        // Also here, contrary to documentation, an extra delete() is needed
324                                        // otherwise date is not properly deleted!
325                                        it.delete()
326                                }
327                        }
328
329                        // remove all samplingEvents from this eventGroup
330                        eventGroup.samplingEvents.findAll{}.each() {
331                                eventGroup.removeFromSamplingEvents(it)
332                                println ".removed samplingEvent '${it.name}' from eventGroup '${eventGroup.name}'"
333                                msg += ", samplingEvent '${it.name}' was removed from eventGroup '${eventGroup.name}'"
334                        }
335                }
336
337                // If the event group contains subjects
338                if (eventGroup.subjects) {
339                        // remove all subject from this eventGroup
340                        eventGroup.subjects.findAll{}.each() {
341                                eventGroup.removeFromSubjects(it)
342                                println ".removed subject '${it.name}' from eventGroup '${eventGroup.name}'"
343                                msg += ", subject '${it.name}' was removed from eventGroup '${eventGroup.name}'"
344                        }
345                }
346
347                // remove the eventGroup from the study
348                println ".remove eventGroup '${eventGroup.name}' from study '${this.title}'"
349                this.removeFromEventGroups(eventGroup)
350
351                // Also here, contrary to documentation, an extra delete() is needed
352                // otherwise cascaded deletes are not properly performed
353                eventGroup.delete()
354
355                return msg
356        }
357}
Note: See TracBrowser for help on using the repository browser.