schild.rb 12 KB

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