schild.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  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. module CoreExtensions
  89. module String
  90. def snake_case
  91. return downcase if match(/\A[A-Z]+\z/)
  92. gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
  93. gsub(/([a-z])([A-Z])/, '\1_\2').
  94. downcase
  95. end
  96. end
  97. module Symbol
  98. def snake_case
  99. to_s.snake_case
  100. end
  101. end
  102. module Schueler
  103. def entlassart
  104. return self.entlass_art if self.respond_to?(:entlass_art)
  105. end
  106. end
  107. module Fachklasse
  108. def dqr_niveau
  109. return self.DQR_Niveau if self.respond_to?(:DQR_Niveau)
  110. "Alte Schild-Version ohne DQR-Niveau"
  111. end
  112. end
  113. end
  114. # Schild hat teilweise nil in DB-Feldern. SchildTypeSaver gibt entweder einen
  115. # Leer-String zurück ("") oder bei strftime das 1899 Datum zurück.
  116. module SchildTypeSaver
  117. Symbol.include SchildErweitert::CoreExtensions::Symbol
  118. String.include CoreExtensions::String
  119. # bei include wird für jede Spalte in der Schild-Tabelle eine Ersatzmethode
  120. # erstellt, die bei nil ein Null-Objekt erstellt.
  121. def self.included(klass)
  122. klass.columns.each do |column|
  123. name = column.snake_case
  124. MethodLogger::Methods.add(klass, name)
  125. # allow_nil ist als Argument optional und lässt bei +true+ alle Ergebnisse durch
  126. define_method(("_"+name.to_s).to_sym) {public_send(column)}
  127. define_method(name) do |allow_nil=false|
  128. ret = public_send(column)
  129. if allow_nil || ret
  130. ret = ret.strip if ret.class == String
  131. ret
  132. else
  133. create_null_object(klass, column)
  134. end
  135. end
  136. end
  137. end
  138. def create_null_object(klass, column)
  139. k = Schild::DB.schema_type_class(klass.db_schema[column][:type])
  140. if k.class == Array
  141. # Sequel stellt :datetime als [Time, DateTime] dar, deswegen die Abfrage nach Array
  142. # Schild verwendet Time Objekte, wir machen das auch
  143. Time.new(1899)
  144. elsif k == Integer
  145. 0
  146. elsif k == Float
  147. 0.0
  148. else
  149. # alle anderen types werden als Klasse zurückgegeben
  150. k.new
  151. end
  152. end
  153. end
  154. # Halten wir Protokoll zu den erstellten Methoden
  155. # Ist brauchbar, wenn man z.B. noch extremer als der SchildTypeSaver arbeiten möchte
  156. module MethodLogger
  157. class Methods
  158. @@accessor_methods = {}
  159. def self.add(klass, meth)
  160. @@accessor_methods[klass] ||= []
  161. @@accessor_methods[klass] << meth
  162. end
  163. def self.list(klass)
  164. @@accessor_methods[klass]
  165. end
  166. end
  167. end
  168. # Mixin für Notenbezeichnungen
  169. module NotenHelfer
  170. # Notenbezeichnung als String
  171. def note_s(ziffer)
  172. case ziffer
  173. when "1", "1+", "1-"
  174. "sehr gut"
  175. when "2", "2+", "2-"
  176. "gut"
  177. when "3", "3+", "3-"
  178. "befriedigend"
  179. when "4", "4+", "4-"
  180. "ausreichend"
  181. when "5", "5+", "5-"
  182. "mangelhaft"
  183. when "6"
  184. "ungenügend"
  185. when 'NB'
  186. "––––––"
  187. when "E1"
  188. "mit besonderem Erfolg teilgenommen"
  189. when "E2"
  190. "mit Erfolg teilgenommen"
  191. when 'E3'
  192. "teilgenommen"
  193. end
  194. end
  195. end
  196. # Klassen sind Konstanten. Deswegen alle auslesen, die Klassen behalten und
  197. # dynamisch neue Klassen mit gleichem Namen erstellen.
  198. # Automatisch SchildTypeSaver einbinden.
  199. #
  200. # Sollen zusätzliche Methoden eingebunden werden, muss - wie unten Schueler
  201. # und andere Klassen - die neu erstelle Klasse gepatcht werden.
  202. # Die alten Methoden bleiben erhalten, d.h. auch die TypeSaver-Methoden.
  203. Schild.constants.map {|name| Schild.const_get(name)}.select {|o| o.is_a?(Class)}.each do |klass|
  204. name = Schild.const_get(klass.to_s).name.split("::").last
  205. klass = Class.new(klass) do
  206. include SchildTypeSaver
  207. end
  208. name = const_set(name, klass)
  209. end
  210. Fachklasse.include CoreExtensions::Fachklasse
  211. Schueler.include CoreExtensions::Schueler
  212. # Stellt die Schüler-Tabelle samt Assoziationen bereit.
  213. class Schueler
  214. # gibt das z.Zt. aktuelle Halbjahr zurück.
  215. def akt_halbjahr
  216. abschnitte.last
  217. end
  218. # gibt aus +jahr+ das Halbjahr +1+ oder +2+ zurück.
  219. def halbjahr(jahr, abschnitt)
  220. abschnitte_dataset.where(:jahr => jahr, :abschnitt => abschnitt).first
  221. end
  222. # gibt +Herr+ oder +Frau+ als Anrede für Schüler zurück.
  223. def anrede
  224. self.geschlecht == 3 ? "Herr" : "Frau"
  225. end
  226. # gibt die passende Bezeichnung zurück Schüler
  227. def schueler_in
  228. self.geschlecht == 3 ? "Schüler" : "Schülerin"
  229. end
  230. # gibt die passende Bezeichnung zurück Studierende
  231. def studierende_r
  232. self.geschlecht == 3 ? "Studierender" : "Studierende"
  233. end
  234. # gibt die jeweilige Berufsbezeichnung nach Geschlecht zurück.
  235. def berufsbezeichnung_mw
  236. return "Keine Fachklasse zugeordnet" if self.fachklasse.nil?
  237. self.geschlecht == 3 ? self.fachklasse.bezeichnung : self.fachklasse.beschreibung_w
  238. end
  239. # gibt +true+ zurück, wenn Schüler volljährig.
  240. def volljaehrig?
  241. self.volljaehrig == "+"
  242. end
  243. # gibt an, ob der Schüler zu einem Zeitpunkt *datum* volljährig war.
  244. def volljaehrig_bei?(datum)
  245. return false if datum.nil? || self.Geburtsdatum.nil?
  246. geb, datum = self.Geburtsdatum.to_date, datum.to_date
  247. (datum.year - geb.year - ((datum.month > geb.month || (datum.month == geb.month && datum.day >= geb.day)) ? 0 : 1)) >= 18
  248. end
  249. # fragt ab, ob in Schild ein Foto als hinterlegt eingetragen ist.
  250. def foto_vorhanden?
  251. self.foto_vorhanden == "+"
  252. end
  253. # gibt, wenn vorhanden, ein Foto als jpg-String zurück, ansonsten nil.
  254. def foto
  255. self.foto_vorhanden? ? self.schuelerfoto.foto : nil
  256. end
  257. end
  258. # Ist die Assoziation, die Halbjahre, sog. Abschnitte zurückgibt.
  259. class Abschnitt
  260. dataset_module do
  261. # filtert den Datensatz nach Jahr
  262. def jahr(i)
  263. where(:Jahr => i)
  264. end
  265. # filtert den Datensatz nach Halbjahr
  266. def halbjahr(i,j)
  267. jahr(i).where(:Abschnitt => j)
  268. end
  269. # filtert und gibt den Datensatz als Abschnitt des aktuellen Halbjahrs zurück
  270. def akt_halbjahr
  271. halbjahr(Time.new.year-1, 1).first
  272. end
  273. end
  274. # Hilfsmethode für die folgenden Methoden
  275. def faecher_nach_id(id)
  276. noten.select{ |n| n.fach.Fachgruppe_ID == id && n.AufZeugnis == '+' }.sort_by{ |n| n.fach.SortierungS2 }
  277. end
  278. # wählt alle berufsübergreifenden Fächer des gewählten Schülers in angegeben Halbjahr.
  279. def berufsuebergreifend
  280. faecher_nach_id 10
  281. end
  282. # wählt alle berufsbezogenen Fächer des gewählten Schülers in angegeben Halbjahr.
  283. def berufsbezogen
  284. faecher_nach_id 20
  285. end
  286. # wählt alle Fächer des Differenzierungsbreichs des gewählten Schülers in angegeben Halbjahr.
  287. def differenzierungsbereich
  288. faecher_nach_id 30
  289. end
  290. # wählt alle Fächergruppen aus.
  291. def faechergruppen
  292. [berufsuebergreifend, berufsbezogen, differenzierungsbereich]
  293. end
  294. # gibt den Namen des Klassenlehrers mit gekürztem Vornamen.
  295. def v_name_klassenlehrer
  296. return "Kein Klassenlehrer angelegt" if klassenlehrer.nil?
  297. v = klassenlehrer.vorname
  298. n = klassenlehrer.nachname
  299. "#{v[0]}. #{n}"
  300. end
  301. # gibt "Klassenlehrer" entsprechend Geschlecht zurück
  302. def klassenlehrer_in
  303. return "Kein Klassenlehrer angelegt" if klassenlehrer.nil?
  304. klassenlehrer.geschlecht == "3" ? "Klassenlehrer" : "Klassenlehrerin"
  305. end
  306. # gibt das aktuelle Schuljahr als String im Format "2014/15" zurück.
  307. def schuljahr
  308. jahr = self.jahr
  309. "#{jahr}/#{jahr-1999}"
  310. end
  311. end
  312. # Assoziation für Noten
  313. class Noten
  314. include NotenHelfer
  315. # note in String umwandeln
  316. def note
  317. note_s self.noten_krz
  318. end
  319. # Bezeichnung des Fachs
  320. def bezeichnung
  321. fach.bezeichnung
  322. end
  323. # Die Fachgruppen ID des Fachs
  324. def fachgruppe_ID
  325. fach.fachgruppe_id
  326. end
  327. end
  328. # Assoziation für BK-Abschlussdaten
  329. class BKAbschluss
  330. # Ist der Schüler zugelassen?
  331. def zulassung?
  332. self.Zulassung == "+"
  333. end
  334. # Ist der Schüler für den Berufsabschluss zugelassen?
  335. def zulassung_ba?
  336. self.ZulassungBA == "+"
  337. end
  338. # Hat der Schüler den Berufsabschluss bestanden?
  339. def bestanden_ba?
  340. self.BestandenBA == "+"
  341. end
  342. end
  343. # Assoziation für die jeweiligen BK-Prüfungsfächer
  344. class BKAbschlussFaecher
  345. include NotenHelfer
  346. # Wurde das Fach schriftlich geprüft?
  347. def fach_schriftlich?
  348. self.FachSchriftlich == "+"
  349. end
  350. # Wurde das Fach mündlich geprüft?
  351. def fach_muendlich?
  352. self.MdlPruefung == "+"
  353. end
  354. def note(notenart=:note_abschluss_ba)
  355. note_s send(notenart)
  356. end
  357. end
  358. # Assoziation für die jeweiligen Abi-Prüfungsfächer
  359. class AbiAbschlussFaecher
  360. include NotenHelfer
  361. def note(notenart)
  362. note_s send(notenart)
  363. end
  364. end
  365. # Schul-Tabelle mit vereinfachtem Zugriff auf Datenfelder.
  366. class Schule
  367. # gibt die Schulnummer zurück
  368. def self.schulnummer
  369. self.first.schul_nr
  370. end
  371. def self.v_name_schulleiter
  372. "#{self.first.schulleiter_vorname[0]}. #{self.first.schulleiter_name}"
  373. end
  374. def self.schulleiter_in
  375. self.first.schulleiter_geschlecht == 3 ? "Schulleiter" : "Schulleiterin"
  376. end
  377. def self.ort
  378. self.first.ort
  379. end
  380. end
  381. # Tabelle der Schuld-Benutzer zum Abgleichen der Daten
  382. class Nutzer
  383. def name
  384. self.us_name
  385. end
  386. def login
  387. self.us_login_name
  388. end
  389. def passwort
  390. self.us_password
  391. end
  392. alias :password :passwort
  393. def passwort?(passwort='')
  394. crypt(passwort) == self.passwort
  395. end
  396. alias :password? :passwort?
  397. def crypt(passwort)
  398. passwort.codepoints.map{|c| ((c/16)*32+15-c).chr}.join('')
  399. end
  400. end
  401. end