schild.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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. # "Fehlt"-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. self.geschlecht == 3 ? self.fachklasse.bezeichnung : self.fachklasse.beschreibung_w
  208. end
  209. # gibt +true+ zurück, wenn Schüler volljährig.
  210. def volljaehrig?
  211. self.volljaehrig == "+"
  212. end
  213. # gibt an, ob der Schüler zu einem Zeitpunkt *datum* volljährig war.
  214. def volljaehrig_bei?(datum)
  215. geb, datum = self.geburtsdatum.to_date, datum.to_date
  216. (datum.year - geb.year - ((datum.month > geb.month || (datum.month == geb.month && datum.day >= geb.day)) ? 0 : 1)) >= 18
  217. end
  218. # fragt ab, ob in Schild ein Foto als hinterlegt eingetragen ist.
  219. def foto_vorhanden?
  220. self.foto_vorhanden == "+"
  221. end
  222. # gibt, wenn vorhanden, ein Foto als jpg-String zurück, ansonsten nil.
  223. def foto
  224. self.foto_vorhanden? ? self.schuelerfoto.foto : nil
  225. end
  226. end
  227. # Dient als Assoziation für Schüler und deren Klassenbezeichnung etc.
  228. class Fachklasse < Schild::Fachklasse
  229. include SchildTypeSaver
  230. end
  231. # Assoziation für Lehrer, hauptsächlich für Klassenlehrer
  232. class Klassenlehrer < Schild::Klassenlehrer
  233. include SchildTypeSaver
  234. end
  235. # Ist die Assoziation, die Halbjahre, sog. Abschnitte zurückgibt.
  236. class Abschnitt < Schild::Abschnitt
  237. include SchildTypeSaver
  238. dataset_module do
  239. # filtert den Datensatz nach Jahr
  240. def jahr(i)
  241. where(:Jahr => i)
  242. end
  243. # filtert den Datensatz nach Halbjahr
  244. def halbjahr(i,j)
  245. jahr(i).where(:Abschnitt => j)
  246. end
  247. # filtert und gibt den Datensatz als Abschnitt des aktuellen Halbjahrs zurück
  248. def akt_halbjahr
  249. halbjahr(Time.new.year-1, 1).first
  250. end
  251. end
  252. # Hilfsmethode für die folgenden Methoden
  253. def faecher_nach_id(id)
  254. noten.select{ |n| n.fach.Fachgruppe_ID == id && n.AufZeugnis == '+' }.sort_by{ |n| n.fach.SortierungS2 }
  255. end
  256. # wählt alle berufsübergreifenden Fächer des gewählten Schülers in angegeben Halbjahr.
  257. def berufsuebergreifend
  258. faecher_nach_id 10
  259. end
  260. # wählt alle berufsbezogenen Fächer des gewählten Schülers in angegeben Halbjahr.
  261. def berufsbezogen
  262. faecher_nach_id 20
  263. end
  264. # wählt alle Fächer des Differenzierungsbreichs des gewählten Schülers in angegeben Halbjahr.
  265. def differenzierungsbereich
  266. faecher_nach_id 30
  267. end
  268. # wählt alle Fächergruppen aus.
  269. def faechergruppen
  270. [berufsuebergreifend, berufsbezogen, differenzierungsbereich]
  271. end
  272. # gibt den Namen des Klassenlehrers mit gekürztem Vornamen.
  273. def v_name_klassenlehrer
  274. v = klassenlehrer.vorname
  275. n = klassenlehrer.nachname
  276. "#{v[0]}. #{n}"
  277. end
  278. # gibt "Klassenlehrer" entsprechend Geschlecht zurück
  279. def klassenlehrer_in
  280. klassenlehrer.geschlecht == "3" ? "Klassenlehrer" : "Klassenlehrerin"
  281. end
  282. # gibt das aktuelle Schuljahr als String im Format "2014/15" zurück.
  283. def schuljahr
  284. jahr = self.jahr
  285. "#{jahr}/#{jahr-1999}"
  286. end
  287. end
  288. # Assoziation für Noten
  289. class Noten < Schild::Noten
  290. include SchildTypeSaver
  291. include NotenHelfer
  292. # note in String umwandeln
  293. def note
  294. note_s self.noten_krz
  295. end
  296. # Bezeichnung des Fachs
  297. def bezeichnung
  298. fach.bezeichnung
  299. end
  300. # Die Fachgruppen ID des Fachs
  301. def fachgruppe_ID
  302. fach.fachgruppe_id
  303. end
  304. end
  305. # Assoziation für Fächer
  306. class Faecher < Schild::Faecher
  307. include SchildTypeSaver
  308. end
  309. # Assoziation für BK-Abschlussdaten
  310. class BKAbschluss < Schild::BKAbschluss
  311. include SchildTypeSaver
  312. # Ist der Schüler zugelassen?
  313. def zulassung?
  314. self.Zulassung == "+"
  315. end
  316. # Ist der Schüler für den Berufsabschluss zugelassen?
  317. def zulassung_ba?
  318. self.ZulassungBA == "+"
  319. end
  320. # Hat der Schüler den Berufsabschluss bestanden?
  321. def bestanden_ba?
  322. self.BestandenBA == "+"
  323. end
  324. end
  325. # Assoziation für die jeweiligen BK-Prüfungsfächer
  326. class BKAbschlussFaecher < Schild::BKAbschlussFaecher
  327. include SchildTypeSaver
  328. include NotenHelfer
  329. # Wurde das Fach schriftlich geprüft?
  330. def fach_schriftlich?
  331. self.FachSchriftlich == "+"
  332. end
  333. # Wurde das Fach mündlich geprüft?
  334. def fach_muendlich?
  335. self.MdlPruefung == "+"
  336. end
  337. def note(notenart=:note_abschluss_ba)
  338. note_s send(notenart)
  339. end
  340. end
  341. # Assoziation für Abi-Abschlussdaten
  342. class AbiAbschluss < Schild::AbiAbschluss
  343. include SchildTypeSaver
  344. end
  345. # Assoziation für die jeweiligen Abi-Prüfungsfächer
  346. class AbiAbschlussFaecher < Schild::AbiAbschlussFaecher
  347. include SchildTypeSaver
  348. include NotenHelfer
  349. def note(notenart)
  350. note_s send(notenart)
  351. end
  352. end
  353. class Sprachenfolge < Schild::Sprachenfolge
  354. include SchildTypeSaver
  355. end
  356. class Vermerke < Schild::Vermerke
  357. include SchildTypeSaver
  358. end
  359. class Schuelerfotos < Schild::Schuelerfotos
  360. include SchildTypeSaver
  361. end
  362. # Schul-Tabelle mit vereinfachtem Zugriff auf Datenfelder.
  363. class Schule < Schild::Schule
  364. include SchildTypeSaver
  365. # gibt die Schulnummer zurück
  366. def self.schulnummer
  367. self.first.schul_nr
  368. end
  369. def self.v_name_schulleiter
  370. "#{self.first.schulleiter_vorname[0]}. #{self.first.schulleiter_name}"
  371. end
  372. def self.schulleiter_in
  373. self.first.schulleiter_geschlecht == 3 ? "Schulleiter" : "Schulleiterin"
  374. end
  375. def self.ort
  376. self.first.ort
  377. end
  378. end
  379. # Tabelle der Schuld-Benutzer zum Abgleichen der Daten
  380. class Nutzer < Schild::Nutzer
  381. include SchildTypeSaver
  382. def name
  383. self.us_name
  384. end
  385. def login
  386. self.us_login_name
  387. end
  388. def passwort
  389. self.us_password
  390. end
  391. alias :password :passwort
  392. def passwort?(passwort='')
  393. crypt(passwort) == self.passwort
  394. end
  395. alias :password? :passwort?
  396. def crypt(passwort)
  397. passwort.codepoints.map{|c| ((c/16)*32+15-c).chr}.join('')
  398. end
  399. end
  400. end