schild.rb 13 KB

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