schild.rb 16 KB

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