schild.rb 13 KB

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