*: remove newly superfluous static types on attributes
[nit.git] / contrib / opportunity / src / opportunity_model.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License
14
15 # Model for the data of Opportunity
16 module opportunity_model
17
18 import sqlite3
19 import sha1
20 import serialization
21
22 # A SQLiteDB object for `Opportunity`
23 class OpportunityDB
24 super Sqlite3DB
25
26 init open(x) do
27 super
28
29 create_db
30 end
31
32 # Creates the tables and triggers for Opportunity (SQLite3 DB)
33 fun create_db do
34 assert create_table("IF NOT EXISTS meetups (id CHAR(40) PRIMARY KEY, name TEXT, date TEXT, place TEXT, answer_mode INTEGER DEFAULT 0);") else
35 print_error error or else "?"
36 end
37 assert create_table("IF NOT EXISTS people(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, surname TEXT);") else
38 print_error error or else "?"
39 end
40 assert create_table("IF NOT EXISTS answers(id INTEGER PRIMARY KEY AUTOINCREMENT, meetup_id CHAR(40), name TEXT, FOREIGN KEY(meetup_id) REFERENCES meetups(id));") else
41 print_error error or else "?"
42 end
43 assert create_table("IF NOT EXISTS part_answers(id_part INTEGER, id_ans INTEGER, value INTEGER, FOREIGN KEY(id_part) REFERENCES people(id), FOREIGN KEY(id_ans) REFERENCES answers(id));") else
44 print_error error or else "?"
45 end
46 #NOTE: The following triggers could be replaced by ON DELETE CASCADE clauses
47 # Thing is, SQLite does not seem to support those operations (well, not by default, it seems
48 # we must re-compile the lib to support it. So, well, let's just create triggers heh.
49 assert execute("CREATE TRIGGER IF NOT EXISTS answers_clean AFTER DELETE ON meetups BEGIN DELETE FROM answers WHERE answers.meetup_id=OLD.id;END;") else
50 print_error error or else "?"
51 end
52 assert execute("CREATE TRIGGER IF NOT EXISTS ans_clean AFTER DELETE ON answers BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_ans;END;") else
53 print_error error or else "?"
54 end
55 assert execute("CREATE TRIGGER IF NOT EXISTS ppl_clean AFTER DELETE ON people BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_part;END;")
56 end
57
58 # Find a `People` by its id, or `null` if it could not be found
59 fun find_people_by_id(id: Int): nullable People do
60 var req = select("* from people where id={id};")
61 for i in req do
62 return new People.from_db(i[0].to_i, i[1].to_s, i[2].to_s)
63 end
64 return null
65 end
66
67 # Find a `Meetup` by its id or `null` if it could not be found
68 fun find_meetup_by_id(id: String): nullable Meetup do
69 var req = select("* FROM meetups where id={id.to_sql_string};")
70 for i in req do
71 return new Meetup.from_db(i[0].to_s, i[1].to_s, i[2].to_s, i[3].to_s, i[4].to_i)
72 end
73 return null
74 end
75
76 # Find an `Answer` by its `id` or `null` if it could not be found
77 fun find_answer_by_id(id: Int): nullable Answer do
78 var req = select("* FROM answers WHERE id={id};")
79 for i in req do
80 return new Answer.from_db(i[0].to_i, i[2].to_s)
81 end
82 return null
83 end
84
85 # Change an Answer `ansid` for someone with an id `pid` to `resp`
86 #
87 # Returns `true` if the request was sucessful, false otherwise
88 fun change_answer(pid: Int, ansid: Int, resp: Int): Bool do
89 var p = find_people_by_id(pid)
90 if p == null then
91 print_error "Opportunity error while updating answer {ansid}:{pid}"
92 return false
93 end
94 var a = find_answer_by_id(ansid)
95 if a == null then
96 print_error "Opportunity error while updating answer {ansid}:{pid}"
97 return false
98 end
99 p.answers[a] = resp
100 if p.commit(self) then return true
101 return false
102 end
103
104 # Removes a person in the Database by its `id`
105 #
106 # Returns true if sucessful, false otherwise
107 fun remove_people_by_id(id: Int): Bool do
108 var rq = execute("DELETE FROM people WHERE id = {id};")
109 if not rq then
110 print_error "Opportunity error deleting people {id}"
111 print_error error or else "Unknown error"
112 return false
113 end
114 return true
115 end
116 end
117
118 # Any kind of Database Object that can be persisted to the database
119 abstract class DBObject
120
121 # Commits the modifications done to the Object in the database
122 fun commit(db: OpportunityDB): Bool is abstract
123 end
124
125 # A Meetup participant, linked to the DB
126 class People
127 super DBObject
128
129 # ID in the Database, -1 if not set
130 var id = -1
131 # Name of the participant
132 var name: String
133 # Surname of the participant
134 var surname: String
135 # Map of the answers of a Meetup and the answers of the participant
136 # 0 = No
137 # 1 = Maybe
138 # 2 = Yes
139 var answers: Map[Answer, Int] = new HashMap[Answer, Int]
140
141 # To be used internally when fetching the `People` in Database
142 private init from_db(id: Int, name, surname: String) do
143 init(name, surname)
144 self.id = id
145 end
146
147 # Changes an answer `ans` (or adds it)
148 fun answer=(ans: Answer, resp: Int) do
149 answers[ans] = resp
150 end
151
152 # Loads the answers for a Meetup
153 #
154 # NOTE: If `self` does not exist in the Database, no answers will be fetched
155 fun load_answers(db: OpportunityDB, meetup: Meetup) do
156 self.answers = new HashMap[Answer, Int]
157 var req = db.select("answers.id, answers.name, part_answers.value FROM part_answers, answers WHERE part_answers.id_part={id} AND answers.id=part_answers.id_ans AND answers.meetup_id={meetup.id.html_escape.to_sql_string} GROUP BY answers.id;")
158 for i in req do
159 var ans = new Answer.from_db(i[0].to_i, i[1].to_s)
160 answers[ans] = i[2].to_i
161 end
162 end
163
164 redef fun to_s do return "{surname} {name}"
165
166 redef fun commit(db) do
167 if id == -1 then
168 if not db.execute("INSERT INTO people (name,surname) VALUES ({name.html_escape.to_sql_string}, {surname.html_escape.to_sql_string});") then
169 print_error "Opportunity error while adding people {self}"
170 print_error db.error or else "Unknown error"
171 return false
172 end
173 id = db.last_insert_rowid
174 else
175 if not db.execute("UPDATE people SET name={name.html_escape.to_sql_string}, surname={surname.html_escape.to_sql_string} WHERE ID={id};") then
176 print_error "Opportunity error while updating people {self}"
177 print_error db.error or else "Unknown error"
178 return false
179 end
180 end
181 for i,j in answers do
182 if i.id == -1 then i.commit(db)
183 var val = j
184 var s = db.select("* FROM part_answers WHERE id_part={id} AND id_ans={i.id}")
185 if s != null and s.iterator.is_ok then
186 if not db.execute("UPDATE part_answers SET value={j} WHERE id_part={id} AND id_ans={i.id};") then
187 print_error "Opportunity error while updating part_answers {id}|{i.id} = {j}"
188 print_error db.error or else "Unknown error"
189 return false
190 end
191 continue
192 end
193 if not db.execute("INSERT INTO part_answers(id_part, id_ans, value) VALUES ({id},{i.id},{val});") then
194 print_error "Opportunity error while adding part_answers {id}|{i.id}|{j}"
195 print_error db.error or else "Unknown error"
196 return false
197 end
198 end
199 return true
200 end
201 end
202
203 # A `Meetup` is an opportunity of meeting, linked to the database
204 class Meetup
205 super DBObject
206
207 # ID of the meetup, SHA-1 of the informations that are contained
208 var id: String = ""
209 # Name for the meetup
210 var name: String
211 # SQLite-formatted date : YYYY:DD:MM HH:MM:SS
212 var date: String
213 # Place of the meetup
214 var place: String
215 # Mode of answering to the meetup (atm supports with or without Maybe)
216 var answer_mode: Int
217
218 # Builds the object with all the informations found in the database
219 private init from_db(id, name, date, place: String, mode: Int) do
220 self.id = id
221 init(name, date, place, mode)
222 end
223
224 # Gets the answers bound to the current `Meetup`
225 fun answers(db: OpportunityDB): Array[Answer] do
226 if id == "" then
227 return new Array[Answer]
228 end
229 var res = db.select("id, name FROM answers WHERE meetup_id={id.to_sql_string}")
230 var ans = new Array[Answer]
231 for i in res do
232 ans.add new Answer.from_db(i[0].to_i, i[1].to_s)
233 end
234 return ans
235 end
236
237 # Gets the list of the participants of a `Meetup`
238 fun participants(db: OpportunityDB): Array[People] do
239 var resp = db.select("people.* FROM people, meetups, answers, part_answers WHERE meetups.id={id.to_sql_string} AND answers.meetup_id={id.to_sql_string} AND part_answers.id_ans=answers.id AND people.id=part_answers.id_part GROUP BY people.id;")
240 var arr = new Array[People]
241 for i in resp do
242 arr.add (new People.from_db(i[0].to_i, i[1].to_s, i[2].to_s))
243 end
244 return arr
245 end
246
247 redef fun commit(db) do
248 if id == "" then
249 var time = get_time
250 var tmpid = (name + date + place + time.to_s).sha1.hexdigest
251 if not db.execute("INSERT INTO meetups (id, name, date, place, answer_mode) VALUES({tmpid.to_sql_string}, {name.html_escape.to_sql_string}, {date.html_escape.to_sql_string}, {place.html_escape.to_sql_string}, {answer_mode});") then
252 print_error "Opportunity error recording entry Meetup {self}"
253 print_error db.error or else "Unknown error"
254 return false
255 end
256 id = tmpid
257 return true
258 else
259 return db.execute("UPDATE meetups SET name={name.html_escape.to_sql_string}, date={date.html_escape.to_sql_string}, place={place.html_escape.to_sql_string}, answer_mode={answer_mode} WHERE ID={id.to_sql_string};")
260 end
261 end
262
263 redef fun to_s do return "Event: {name}, date: {date}, place: {place}"
264 end
265
266 # An answer linked to a Meetup in the database
267 class Answer
268 super DBObject
269
270 # Name of the answer (title)
271 var name: String
272 # Id in the database, -1 if not set
273 var id = -1
274 # Meetup the answer is linked to (null while it is not added in the database or set via API)
275 var meetup: nullable Meetup = null is writable
276
277 # To be used internally when fetching the object from Database
278 private init from_db(id: Int, name: String) do
279 init name
280 self.id = id
281 end
282
283 redef fun hash do
284 if id != -1 then return id
285 return super
286 end
287
288 # Loads the Meetup associated to `self`
289 #
290 # REQUIRE: is loaded in database
291 fun load_meetup(db: OpportunityDB): Meetup do
292 assert id != -1
293 var res = db.select("meetups.* FROM meetups, answers WHERE answers.id={id} AND answers.meetup_id=meetups.id;")
294 for i in res do
295 return new Meetup.from_db(i[0].to_s, i[1].to_s, i[2].to_s, i[3].to_s, i[4].to_i)
296 end
297 # If no Meetup could be loaded, the contract was not respected
298 abort
299 end
300
301 # Counts the number of positive or maybe answers
302 fun count(db: OpportunityDB): Int do
303 if id == -1 then return -1
304 var count = 0
305 var res = db.select("part_answers.value FROM part_answers WHERE part_answers.id_ans={id};")
306 if meetup == null then meetup = load_meetup(db)
307 for i in res do
308 if meetup.answer_mode == 0 then
309 count += i[0].to_i
310 else
311 if i[0].to_i == 2 then count += 1
312 end
313 end
314 return count
315 end
316
317 # Counts the score for this particular answer
318 fun score(db: OpportunityDB): Int do
319 if id == -1 then return -1
320 var score = 0
321 var res = db.select("part_answers.value FROM part_answers WHERE part_answers.id_ans={id};")
322 for i in res do
323 score += i[0].to_i
324 end
325 return score
326 end
327
328 redef fun commit(db) do
329 var m = meetup
330 if m == null then return false
331 if m.id == "" then
332 if not m.commit(db) then
333 print_error "Opportunity error when creating meetup {m}"
334 return false
335 end
336 end
337 if id == -1 then
338 if not db.execute("INSERT INTO answers (name, meetup_id) VALUES({name.html_escape.to_sql_string}, {m.id.to_sql_string});") then
339 print_error "Opportunity error creating {self} in database"
340 print_error db.error or else "Unknown error"
341 return false
342 end
343 id = db.last_insert_rowid
344 else
345 if not db.execute("UPDATE answers SET name=({name.html_escape.to_sql_string}) WHERE meetup_id={m.id.to_sql_string};") then
346 print_error "Opportunity error updating {self} in database"
347 print_error db.error or else "Unknown error"
348 return false
349 end
350 end
351 return true
352 end
353
354 redef fun to_s do return name
355 end