schild.rb 12 KB

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