schild.rb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. require 'schild/version'
  2. require 'sequel'
  3. # Das Schild Modul, das alle Klassen für die Datenbankanbindung bereitstellt
  4. module Schild
  5. # ist die Datenbank-Verbindung. Alle Daten können über diese Konstante abgerufen werden
  6. DB = Sequel.connect("#{ENV['S_ADAPTER']}://#{ENV['S_HOST']}/#{ENV['S_DB']}?user=#{ENV['S_USER']}&password=#{ENV['S_PASSWORD']}&zeroDateTimeBehavior=convertToNull")
  7. # Stellt die Schüler-Tabelle samt Assoziationen bereit.
  8. class Schueler < Sequel::Model(:schueler)
  9. many_to_one :fachklasse, :class => :Fachklasse, :key => :Fachklasse_ID
  10. one_to_many :abschnitte, :class => :Abschnitt
  11. one_to_one :bk_abschluss, :class => :BKAbschluss
  12. one_to_many :bk_abschluss_leistungen, :class => :BKAbschlussFaecher
  13. one_to_one :abi_abschluss, :class => :AbiAbschluss
  14. one_to_many :abi_abschluss_leistungen, :class => :AbiAbschlussFaecher
  15. one_to_many :vermerke, :class => :Vermerke
  16. one_to_one :schuelerfoto, :class => :Schuelerfotos
  17. end
  18. # Dient als Assoziation für Schüler und deren Klassenbezeichnung etc.
  19. class Fachklasse < Sequel::Model(:eigeneschule_fachklassen)
  20. one_to_many :schueler
  21. end
  22. # Assoziation für Lehrer, hauptsächlich für Klassenlehrer
  23. class Klassenlehrer < Sequel::Model(:k_lehrer)
  24. one_to_one :abschnitt, :primary_key=>:Kuerzel, :key=>:KlassenLehrer
  25. end
  26. # Ist die Assoziation, die Halbjahre, sog. Abschnitte zurückgibt.
  27. class Abschnitt < Sequel::Model(:schuelerlernabschnittsdaten)
  28. many_to_one :schueler, :class => :Schueler, :key => :Schueler_ID
  29. one_to_many :noten, :class => :Noten
  30. many_to_one :klassenlehrer, :class => :Klassenlehrer, :primary_key=>:Kuerzel, :key=>:KlassenLehrer
  31. end
  32. # Assoziation für Noten
  33. class Noten < Sequel::Model(:schuelerleistungsdaten)
  34. many_to_one :abschnitt, :class => :Abschnitt, :key => :Abschnitt_ID
  35. many_to_one :fach, :class => :Faecher, :key => :Fach_ID
  36. end
  37. # Assoziation für Fächer
  38. class Faecher < Sequel::Model(:eigeneschule_faecher)
  39. #siehe abi_...
  40. one_to_one :noten
  41. one_to_many :abi_abschluss_leistungen
  42. one_to_one :sprachenfolge, :class => :Sprachenfolge, :key => :Fach_ID
  43. end
  44. # Assoziation für BK-Abschluss des Schülers
  45. class BKAbschluss < Sequel::Model(:schuelerbkabschluss)
  46. one_to_one :schueler
  47. end
  48. # Assoziation für die Prüfungsfächer des Schülers
  49. class BKAbschlussFaecher < Sequel::Model(:schuelerbkfaecher)
  50. many_to_one :schueler
  51. end
  52. # Assoziation für Abi-Abschluss des Schülers
  53. class AbiAbschluss < Sequel::Model(:schuelerabitur)
  54. one_to_one :schueler
  55. end
  56. # Assoziation für die Abifächer des Schülers
  57. class AbiAbschlussFaecher < Sequel::Model(:schuelerabifaecher)
  58. many_to_one :schueler
  59. many_to_one :fach, :class => :Faecher, :key => :Fach_ID
  60. end
  61. # Assoziation für die bisher erreichten Sprachniveaus
  62. class Sprachenfolge < Sequel::Model(:schuelersprachenfolge)
  63. one_to_one :Faecher
  64. end
  65. # Vermerke von Schülern
  66. class Vermerke < Sequel::Model(:schuelervermerke)
  67. many_to_one :Schueler
  68. end
  69. # Schülerfotos als jpg
  70. class Schuelerfotos < Sequel::Model(:schuelerfotos)
  71. one_to_one :schueler
  72. end
  73. # Schul-Tabelle
  74. class Schule < Sequel::Model(:eigeneschule)
  75. end
  76. # Tabelle für Schild-Nutzer
  77. class Nutzer < Sequel::Model(:users)
  78. end
  79. end
  80. module SchildErweitert
  81. # erst Ruby 2.1.0 macht include zu einer public-Methode
  82. if Module.private_method_defined? :include
  83. class Module
  84. public :include
  85. end
  86. end
  87. # String und Symbol werden um snake_case ergänzt, das die Schild-Tabellen umbenennt
  88. # Legacy-Methoden aus alten Schild-Versionen wird teilweise auch unterstützt.
  89. module CoreExtensions
  90. module String
  91. def snake_case
  92. return downcase if match(/\A[A-Z]+\z/)
  93. gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
  94. gsub(/([a-z])([A-Z])/, '\1_\2').
  95. downcase
  96. end
  97. end
  98. module Symbol
  99. def snake_case
  100. to_s.snake_case
  101. end
  102. end
  103. module Schueler
  104. def entlassart
  105. return self.entlass_art if self.respond_to?(:entlass_art)
  106. end
  107. end
  108. module Fachklasse
  109. def dqr_niveau
  110. return self.DQR_Niveau if self.respond_to?(:DQR_Niveau)
  111. "Alte Schild-Version ohne DQR-Niveau"
  112. end
  113. end
  114. end
  115. # Schild hat teilweise nil in DB-Feldern. SchildTypeSaver gibt entweder einen
  116. # Leer-String zurück ("") oder bei strftime das 1899 Datum zurück.
  117. module SchildTypeSaver
  118. Symbol.include SchildErweitert::CoreExtensions::Symbol
  119. String.include CoreExtensions::String
  120. # bei include wird für jede Spalte in der Schild-Tabelle eine Ersatzmethode
  121. # erstellt, die bei nil ein Null-Objekt erstellt.
  122. def self.included(klass)
  123. klass.columns.each do |column|
  124. name = column.snake_case
  125. MethodLogger::Methods.add(klass, name)
  126. # allow_nil ist als Argument optional und lässt bei +true+ alle Ergebnisse durch
  127. define_method(("_"+name.to_s).to_sym) {public_send(column)}
  128. define_method(name) do |allow_nil=false|
  129. ret = public_send(column)
  130. if allow_nil || ret
  131. ret = ret.strip if ret.class == String
  132. ret
  133. else
  134. create_null_object(klass, column)
  135. end
  136. end
  137. end
  138. end
  139. def create_null_object(klass, column)
  140. k = Schild::DB.schema_type_class(klass.db_schema[column][:type])
  141. if k.class == Array
  142. # Sequel stellt :datetime als [Time, DateTime] dar, deswegen die Abfrage nach Array
  143. # Schild verwendet Time Objekte, wir machen das auch
  144. Time.new(1899)
  145. elsif k == Integer
  146. 0
  147. elsif k == Float
  148. 0.0
  149. else
  150. # alle anderen types werden als Klasse zurückgegeben
  151. k.new
  152. end
  153. end
  154. end
  155. # Halten wir Protokoll zu den erstellten Methoden
  156. # Ist brauchbar, wenn man z.B. noch extremer als der SchildTypeSaver arbeiten möchte
  157. module MethodLogger
  158. class Methods
  159. @@accessor_methods = {}
  160. def self.add(klass, meth)
  161. @@accessor_methods[klass] ||= []
  162. @@accessor_methods[klass] << meth
  163. end
  164. def self.list(klass)
  165. @@accessor_methods[klass]
  166. end
  167. end
  168. end
  169. # Mixin für Notenbezeichnungen
  170. module NotenHelfer
  171. # Notenbezeichnung als String
  172. def note_s(ziffer)
  173. case ziffer
  174. when "1", "1+", "1-"
  175. "sehr gut"
  176. when "2", "2+", "2-"
  177. "gut"
  178. when "3", "3+", "3-"
  179. "befriedigend"
  180. when "4", "4+", "4-"
  181. "ausreichend"
  182. when "5", "5+", "5-"
  183. "mangelhaft"
  184. when "6"
  185. "ungenügend"
  186. when 'NB'
  187. "––––––"
  188. when "E1"
  189. "mit besonderem Erfolg teilgenommen"
  190. when "E2"
  191. "mit Erfolg teilgenommen"
  192. when 'E3'
  193. "teilgenommen"
  194. end
  195. end
  196. end
  197. # Klassen sind Konstanten. Deswegen alle auslesen, die Klassen behalten und
  198. # dynamisch neue Klassen mit gleichem Namen erstellen.
  199. # Automatisch SchildTypeSaver einbinden.
  200. #
  201. # Sollen zusätzliche Methoden eingebunden werden, muss - wie unten Schueler
  202. # und andere Klassen - die neu erstelle Klasse gepatcht werden.
  203. # Die alten Methoden bleiben erhalten, d.h. auch die TypeSaver-Methoden.
  204. Schild.constants.map {|name| Schild.const_get(name)}.select {|o| o.is_a?(Class)}.each do |klass|
  205. name = Schild.const_get(klass.to_s).name.split("::").last
  206. klass = Class.new(klass) do
  207. include SchildTypeSaver
  208. end
  209. name = const_set(name, klass)
  210. end
  211. Fachklasse.include CoreExtensions::Fachklasse
  212. Schueler.include CoreExtensions::Schueler
  213. # Stellt die Schüler-Tabelle samt Assoziationen bereit.
  214. class Schueler
  215. # gibt das z.Zt. aktuelle Halbjahr zurück.
  216. def akt_halbjahr
  217. abschnitte.last
  218. end
  219. # gibt aus +jahr+ das Halbjahr +1+ oder +2+ zurück.
  220. def halbjahr(jahr, abschnitt)
  221. abschnitte_dataset.where(:jahr => jahr, :abschnitt => abschnitt).first
  222. end
  223. # gibt +Herr+ oder +Frau+ als Anrede für Schüler zurück.
  224. def anrede
  225. self.geschlecht == 3 ? "Herr" : "Frau"
  226. end
  227. # gibt die passende Bezeichnung zurück Schüler
  228. def schueler_in
  229. self.geschlecht == 3 ? "Schüler" : "Schülerin"
  230. end
  231. # gibt die passende Bezeichnung zurück Studierende
  232. def studierende_r
  233. self.geschlecht == 3 ? "Studierender" : "Studierende"
  234. end
  235. # gibt die jeweilige Berufsbezeichnung nach Geschlecht zurück.
  236. def berufsbezeichnung_mw
  237. return "Keine Fachklasse zugeordnet" if self.fachklasse.nil?
  238. self.geschlecht == 3 ? self.fachklasse.bezeichnung : self.fachklasse.beschreibung_w
  239. end
  240. # gibt +true+ zurück, wenn Schüler volljährig.
  241. def volljaehrig?
  242. self.volljaehrig == "+"
  243. end
  244. # gibt an, ob der Schüler zu einem Zeitpunkt *datum* volljährig war.
  245. def volljaehrig_bei?(datum)
  246. return false if datum.nil? || self.Geburtsdatum.nil?
  247. geb, datum = self.Geburtsdatum.to_date, datum.to_date
  248. (datum.year - geb.year - ((datum.month > geb.month || (datum.month == geb.month && datum.day >= geb.day)) ? 0 : 1)) >= 18
  249. end
  250. # fragt ab, ob in Schild ein Foto als hinterlegt eingetragen ist.
  251. def foto_vorhanden?
  252. self.foto_vorhanden == "+"
  253. end
  254. # gibt, wenn vorhanden, ein Foto als jpg-String zurück, ansonsten nil.
  255. def foto
  256. self.foto_vorhanden? ? self.schuelerfoto.foto : nil
  257. end
  258. end
  259. # Ist die Assoziation, die Halbjahre, sog. Abschnitte zurückgibt.
  260. class Abschnitt
  261. dataset_module do
  262. # filtert den Datensatz nach Jahr
  263. def jahr(i)
  264. where(:Jahr => i)
  265. end
  266. # filtert den Datensatz nach Halbjahr
  267. def halbjahr(i,j)
  268. jahr(i).where(:Abschnitt => j)
  269. end
  270. # filtert und gibt den Datensatz als Abschnitt des aktuellen Halbjahrs zurück
  271. def akt_halbjahr
  272. halbjahr(Time.new.year-1, 1).first
  273. end
  274. end
  275. # Hilfsmethode für die folgenden Methoden
  276. def faecher_nach_id(id)
  277. noten.select{ |n| n.fach.Fachgruppe_ID == id && n.AufZeugnis == '+' }.sort_by{ |n| n.fach.SortierungS2 }
  278. end
  279. # wählt alle berufsübergreifenden Fächer des gewählten Schülers in angegeben Halbjahr.
  280. def berufsuebergreifend
  281. faecher_nach_id 10
  282. end
  283. # wählt alle berufsbezogenen Fächer des gewählten Schülers in angegeben Halbjahr.
  284. def berufsbezogen
  285. faecher_nach_id 20
  286. end
  287. # wählt alle Fächer des Differenzierungsbreichs des gewählten Schülers in angegeben Halbjahr.
  288. def differenzierungsbereich
  289. faecher_nach_id 30
  290. end
  291. # wählt alle Fächergruppen aus.
  292. def faechergruppen
  293. [berufsuebergreifend, berufsbezogen, differenzierungsbereich]
  294. end
  295. # gibt den Namen des Klassenlehrers mit gekürztem Vornamen.
  296. def v_name_klassenlehrer
  297. return "Kein Klassenlehrer angelegt" if klassenlehrer.nil?
  298. v = klassenlehrer.vorname
  299. n = klassenlehrer.nachname
  300. "#{v[0]}. #{n}"
  301. end
  302. # gibt "Klassenlehrer" entsprechend Geschlecht zurück
  303. def klassenlehrer_in
  304. return "Kein Klassenlehrer angelegt" if klassenlehrer.nil?
  305. klassenlehrer.geschlecht == "3" ? "Klassenlehrer" : "Klassenlehrerin"
  306. end
  307. # gibt das aktuelle Schuljahr als String im Format "2014/15" zurück.
  308. def schuljahr
  309. jahr = self.jahr
  310. "#{jahr}/#{jahr-1999}"
  311. end
  312. end
  313. # Assoziation für Noten
  314. class Noten
  315. include NotenHelfer
  316. # note in String umwandeln
  317. def note
  318. note_s self.noten_krz
  319. end
  320. # Bezeichnung des Fachs
  321. def bezeichnung
  322. fach.bezeichnung
  323. end
  324. # Die Fachgruppen ID des Fachs
  325. def fachgruppe_ID
  326. fach.fachgruppe_id
  327. end
  328. end
  329. # Assoziation für BK-Abschlussdaten
  330. class BKAbschluss
  331. # Ist der Schüler zugelassen?
  332. def zulassung?
  333. self.Zulassung == "+"
  334. end
  335. # Ist der Schüler für den Berufsabschluss zugelassen?
  336. def zulassung_ba?
  337. self.ZulassungBA == "+"
  338. end
  339. # Hat der Schüler den Berufsabschluss bestanden?
  340. def bestanden_ba?
  341. self.BestandenBA == "+"
  342. end
  343. end
  344. # Assoziation für die jeweiligen BK-Prüfungsfächer
  345. class BKAbschlussFaecher
  346. include NotenHelfer
  347. # Wurde das Fach schriftlich geprüft?
  348. def fach_schriftlich?
  349. self.FachSchriftlich == "+"
  350. end
  351. # Wurde das Fach mündlich geprüft?
  352. def fach_muendlich?
  353. self.MdlPruefung == "+"
  354. end
  355. def note(notenart=:note_abschluss_ba)
  356. note_s send(notenart)
  357. end
  358. end
  359. # Assoziation für die jeweiligen Abi-Prüfungsfächer
  360. class AbiAbschlussFaecher
  361. include NotenHelfer
  362. def note(notenart)
  363. note_s send(notenart)
  364. end
  365. end
  366. # Schul-Tabelle mit vereinfachtem Zugriff auf Datenfelder mittel class-Methoden
  367. class Schule
  368. # gibt die Schulnummer zurück
  369. def self.schulnummer
  370. self.first.schul_nr
  371. end
  372. # gibt den Namen des Schulleiters als V. Name zurück
  373. def self.v_name_schulleiter
  374. "#{self.first.schulleiter_vorname[0]}. #{self.first.schulleiter_name}"
  375. end
  376. # gibt die männliche bzw. weibliche Form des Schulleiters zurück
  377. def self.schulleiter_in
  378. self.first.schulleiter_geschlecht == 3 ? "Schulleiter" : "Schulleiterin"
  379. end
  380. # gibt den Ort der Schule zurück
  381. def self.ort
  382. self.first.ort
  383. end
  384. end
  385. # Tabelle der Schuld-Benutzer zum Abgleichen der Daten
  386. class Nutzer
  387. # der Nutzername
  388. def name
  389. self.us_name
  390. end
  391. # der Login-Name des Nutzers
  392. def login
  393. self.us_login_name
  394. end
  395. # das Passwort des Nutzers
  396. def passwort
  397. self.us_password
  398. end
  399. alias :password :passwort
  400. # prüft, ob das angegebene Passwort mit dem gespeicherten Passwort übereinstimmt
  401. def passwort?(passwort='')
  402. crypt(passwort) == self.passwort
  403. end
  404. alias :password? :passwort?
  405. # ver- bzw. entschlüsselt einen String mit dem Schild-Passwortalgorithmus
  406. def crypt(passwort)
  407. passwort.codepoints.map{|c| ((c/16)*32+15-c).chr}.join('')
  408. end
  409. end
  410. end