schild.rb 13 KB

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