schild.rb 13 KB

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