schild.rb 16 KB

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