schild.rb 13 KB

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