Ruby Weblog Archiv 2006 Winfried Mueller www.reintechnisch.de |
Wer Skriptsprachen und Objektorientierung mag, findet in Ruby eine ästhetische Ausdrucksmöglichkeit seines Könnens. Plattformübergreifend.
04.12.2006 Benutzerverwaltung im Unix-System
Ich brauchte ein paar Werkzeuge, um Benutzeraccounts in einem Unix-System anzulegen, Passwörter skriptgesteuert zu definieren und abzufragen, ob ein Account existiert. Hier ist der grob getestete Programmcode dazu (linux_user.rb):
require 'expect' require 'open3' ENV["LANGUAGE"]="en" LINUX_USER_CMD_TIMEOUT = 5 class IO def expect_raise( rxp, timeout ) if (r = expect( rxp, timeout )) == nil raise "command timeout" else r end end end def passwd( user, pw ) Open3::popen3( "/usr/bin/passwd #{user}" ) do |stdin, stdout, stderr| r = stderr.expect_raise( /(Enter.*password:)|(unknown user)/, LINUX_USER_CMD_TIMEOUT ) if r[0] =~ /unknown/ raise "user unknown" end stdin.print "#{pw}\n" stderr.expect_raise( /Retype.*password:/, LINUX_USER_CMD_TIMEOUT ) stdin.print "#{pw}\n" stderr.expect_raise( /updated successfully/, LINUX_USER_CMD_TIMEOUT ) end end def useradd( user, shell ) r = `/usr/sbin/useradd -m -s #{shell} #{user} 2>&1` if $? >> 8 != 0 raise "#{r}" end end def user_exist?( user ) File.foreach( "/etc/passwd" ) do |line| if line =~ /^#{user}/ return true end end return false end
ENV["LANGUAGE"]="en" sorgt dafür, dass die Rückgabe aller Unix-Kommandos auch in Englisch erfolgt und nicht angepasst auf die eingestellte Sprache. Das ist wichtig, weil sonst passwd nicht korrekt funktioniert. Ich habe übrigens absichtlich auf das externe Kommando passwd aufgesetzt und ändere nicht selber die passwd/shadow-Datei, um den Code auf unterschiedlichsten Plattformen kompatibel zu halten.
LINUX_USER_CMD_TIMEOUT = 5 ist eine Festlegung in Sekunden, wie lange maximal auf eine Antwort von Kommandos gewartet wird.
Die Klasse IO wurde um expect_raise erweitert. Es wirft eine Exception ab, falls nicht die erwartete Rückantwort kommt. Das macht expect ja nicht, sondern gibt dann lediglich nil zurück. Dann müsste ich jedesmal auf nil testen, was mich an Fehlerbehandlung aus alten C-Zeiten erinnert.
Open3 braucht es übrigens bei passwd, weil hier die Ausgabe über stderr erfolgt. Warum auch immer.
01.12.2006 GemJack - Ruby Gem Online Dokumentation
GemJack ist eine nette Homepage, auf der man alle Dokumentationen von Ruby-Gem-Paketen lesen kann. Das hilft vor allem, wenn man erstmal schnell eine Doku lesen will, ehe man sich ein Gem-Paket installiert.
01.12.2006 Probleme mit open3/popen3
Mit einem Ruby-Skript wollte ich eigentlich automatisiert neue Benutzer in einem Linux-System anlegen. Dazu verwendet man in der Regel adduser oder useradd. Leider ist adduser nur interaktiv benutzbar. Ich dachte mir jedoch, dass ich das mit popen3 und expect.rb gut hinbekomme. Das man popen3 braucht und popen nicht ausreicht, zeigte mir ein erster Test. Das Program adduser ruft nämlich passwd auf, welches seine Passwortabfrage auf stderr ausgibt und nicht auf stdout.
Nach stundenlangem rumprobieren hab ich die Sache jetzt erstmal aufgegeben. Es scheint ein Problem mit der Pufferung von Streams zu sein. Da adduser andere Programme aufruft, hat man irgendwie keinen sauberen direkten Kontakt zu deren stdin und stdout. Hier mal ein Testprogramm:
require "open3" ENV["LANGUAGE"]="en" CMD_TIMEOUT = 5 user = "ruby_test1" pass = "Test" `deluser #{user}` Open3::popen3( "/usr/sbin/adduser #{user}" ) do |stdin, stdout, stderr| Thread.new { loop do print stderr.getc.chr $stdout.flush end } Thread.new { loop do print stdout.getc.chr $stdout.flush end } loop do a = gets.chomp stdin.print "#{a}\n" end end
Damit sollt man adduser wie gewohnt ausführen können. Auf einem Ubuntu-Dapper klappt auch alles soweit. Wenn man die Fragen nach "Full Name, Room Number..." jeweils mit Enter übergeht, kommt man bis zum letzten Punkt "Other []:". Dann sollte eigentlich die Abfrage kommen "Is the information correct?". Die kommt aber nicht, dafür wartet stdin jedoch schon auf "y" oder "n" als Antwort. Wenn man die Antwort "y" gibt, erscheint nachträglich dann die Frage. Da ist also stdout irgendwo zwischengepuffert und erreicht einen zu spät.
Auf einem Debian Woody funktioniert es noch schlechter. Da kommen erst gar nicht die Fragen 5 Fragen, die kommen komplett nachträglich.
Folglich: Interaktive Programme mit popen3 zum mitspielen anzuregen, funktioniert wohl nur, wenn die nicht weitere Programme aufrufen, die ihrerseits unabhängig arbeiten. Genau verstanden habe ich das Verhalten allerdings nicht. Adduser ist ein Perl-Skript, worin mit system(cmd) ein weiteres Kommando aufgerufen wird. Dessen stdout scheint irgendwie zeitversetzt weitergeleitet zu werden.
Auch mit Open4 ist das übrigens getestet, gleiche Problematik.
01.11.2006 Vorsicht bei mitgelieferten Bibliotheken
Auf einem Ubuntu Dapper System brauchte ich die http-access2 für ein bereits existierendes und funktionierendes Skript. Also eben mal mit apt-cache nachgeschaut, ob diese Bibliothek vom Ubuntu Paketmanagment her existiert. Die gab es auch. Also eben mit apt-get installiert. Mein Skript wollte aber einfach nicht laufen.
Nach 3 Stunden Fehlersuche hab ich dann endlich herausbekommen, dass eine völlig veraltete Bibliothek mitgeliefert wurde. Aus dem Jahre 2004. Weil die letzte Änderung an dieser Bibliothek 2005 war und Dapper erst 2006 rausgekommen ist, hätte ich nie damit gerechnet, dass die Bibliothek noch aus 2004 stammt.
Also Vorsicht: Am besten alle Ruby-Bibliotheken über rubygems installieren bzw. aus den Quellpaketen. Zumindest sollte man dann dran denken, sobald es Probleme gibt.
Die http-access2 scheint übrigens ziemlich Buggy zu sein. Zumindest läuft sie bei mir auf mehreren Rechnern nicht korrekt. Der Content der abgeholten Seite hat sehr oft die Länge 0, obwohl als Rückantwort 200 OK kam. Unter Ubuntu Dapper mit Ruby 1.8.4 funktioniert mitunter gar nichts richtig. Unter Windows und Ruby 1.8.2 bzw. Ruby 1.8.4 läuft alles problemlos.
01.11.2006 Minimal-Konfiguration Apache 2 und Ruby-Skripte
Was braucht man, um ein Ruby-Skript über den Apache ausführen zu lassen? Braucht man mod-ruby oder eruby? Nein, wenn man ein Ruby-Skript als ganz gewöhnliches cgi-skript laufen lassen möchte.
Zuerst einmal testet man, ob ein normales cgi-Bash Skript läuft. Das legt man z.B. unter /var/www/cgi-bin/test.cgi ab.
#!/bin/bash echo "Status: 200 OK" echo "Content-type: text/plain" echo "" echo "" echo "Hello World"
Wenn das nicht läuft, ist vermutlich der CGI-Handler nicht gesetzt. Den kann man unter Apache 2 entweder in der apache2.conf oder unter sites-available/<my-site> festlegen. In der apache2.conf sähe das so aus:
AddHandler cgi-script .cgi
Auch muss für das entsprechende Verzeichnis "Options ExecCGI" gesetzt sein.
Bei der test.cgi müssen auch die Rechte richtig gesetzt sein, z.B. 755.
Wenn das alles funktioniert, kann man auch ein Ruby-Skript mit der Endung .cgi schreiben, was dann funktionieren sollte:
#!/usr/bin/ruby1.8 out = <<EOF Status: 200 OK Content-type: text/plain Hello World! EOF print out
Besser ist es noch, wenn man statt der Endung .cgi die Endung .rbx für Ruby Skripte definiert. Auch dies geht wieder in apache2.conf oder in der entsprechenden Virtual-Host-Datei.
AddHandler cgi-script .rbx
Alles hier wurde unter Ubuntu Dapper getestet.
Wozu braucht man dann mod_ruby? Einerseits erlaubt dies einen direkten Zugriff auf die Apache-API. Andererseits laufen cgi-Skripte damit schneller, weil nicht immer bei jedem Skript ein kompletter Ruby-Interpreter neu gestartet werden muss. Allerdings läuft wohl mod_ruby je nach Konstellation nicht immer ganz problemlos.
Ruby Skripte auf einem Heim-Server mit gewöhnlichem cgi-script laufen zu lassen, birgt übrigens keinerlei Performance-Probleme. Erst wenn man aufwändige Webframeworks wie Rails benutzt, wird es unbenutzbar langsam. Das Performance Problem liegt dann nicht im Laden des Interpreters, sondern in der ganzen Initialisierung des Webframeworks.
Und wozu braucht es eruby? Das steht für embedded Ruby, womit man dann in HTML-Dateien Ruby Code mit einbinden kann, ähnlich wie man das bei php gewohnt ist. Auch wenn das für kleine Skripte recht reizvoll ist, führt das in der Regel zu Chaos in der Webprogrammierung. Besonders, wenn man versucht, die Anwendungslogik so in HTML-Dateien zu implementieren.
26.10.2006 Bildersammlung als PDF-Dokument
Wer kennt das nicht: Man hat ein paar Seiten eines Buches oder Artikels gescannt und möchte die Bilder nun zusammen in einem PDF-Dokument unterbringen. So ein PDF-Dokument ist wesentlich praktischer, als einzeln herumfliegende Bilder.
Da erinnerte ich mich an das PDF::Writer Projekt, was auf Rubyforge gehostet ist. Ob das damit geht? Leider hatte ich wenig Zeit für exzessive Abenteuer mit dieser Bibliothek. Also versuchte ich mein Glück, in einer Stunde per Trail & Error schnell was hinzubekommen. Und es kam tatsächlich was einfaches und Funktionierendes bei raus.
Das Skript nimmt sich alle Bilder eines Verzeichnisses, die 001.jpg, 002.jpg usw. heißen. Diese werden dann gemeinsam in ein PDF-Dokument geschrieben. Eine Einschränkung gibt es: Die Bilder werden anscheinend erstmal alle in den Hauptspeicher geladen. Ein Versuch, 300 Bilder mit jeweils 400 KB damit wegzuschreiben, verbrauchte gigabyteweise Hauptspeicher, um dann mit einem Fehler abzubrechen. Mit 100 Bildern klappte es aber immer noch ganz gut.
Natürlich kann man damit auch wunderbar Fotosammlungen als PDF-Dokument aufbereiten.
require 'pdf/writer' pdf = PDF::Writer.new Dir.foreach( "." ) do |file| next if file !~ /^[0-9]+\.jpg$/ puts "Add Image: #{file}" pdf.image( file ) end pdf.save_as("meinpdf.pdf")
Wirklich einfach, nicht? Vielleicht bekommt ja jemand dadurch ein wenig Lust, weiter damit zu experimentieren.
Weblinks:
21.09.2006 The Little Book of Ruby
Eine nette neue kleine Einführung in die Sprache Ruby ist kostenlos im Internet verfügbar. Allerdings in englisch.
Weblinks:
06.09.2006 Linux: Programm oder Job anstoßen
Manchmal möchte man mit einem Ruby-Programm ein anderes Skript oder ein Programm anstoßen, was dann selbstständig weiterläuft. Ein typisches Beispiel wäre eine Datensicherung, die durch ein Webinterface gestartet werden soll.
Wie macht man sowas? Ein guter erster Gedanke heißt fork. Was man aber nicht vergessen darf ist, dass man von diesem Thread STDIN, STDOUT und STDERR abtrennt. Sonst gibt es Schwierigkeiten. Ebenso muss man mittels Process.setsid den Prozess vom kontrollierenden tty abtrennen. In fast allen Implementierungen, die ich kenne, wird weiterhin mit "exit if fork" "fork and exit" ein zweiter fork gemacht. Damit wird nur der Sohn behalten, der Vater geht sofort mit exit verloren. Vermutlich braucht es das, damit man wirklich völlig abgekoppelt vom kontrollierenden TTY ist. Zum Schluß ruft man mit exec das externe Programm auf, womit dann Ruby entlassen ist und der Prozess diesem Programm gehört.
Hier ein Beispiel:
def start_my_external_job fork do Process.setsid exit if fork STDIN.reopen "/dev/null" STDOUT.reopen "/dev/null", "a" STDERR.reopen STDOUT exec( "sudo /usr/local/bin/mybackup" ) end end
Weblinks:
- http://bigbold.com/snippets/posts/show/2265
- http://redhanded.hobix.com/inspect/daemonize.html
- http://grub.ath.cx/daemonize/
- http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/201323
03.09.2006 Bücher Verkaufszahlen
O'Reilly hat eine Statistik im Internet, welche zeigt, wie viele Bücher von welcher Programmiersprache in den letzten Jahren verkauft wurden. Hier lässt sich ein gewisser Trend ablesen, wie populär eine Sprache ist. Interessant: Ruby war bis Ende 2004 noch völlig unbedeutend. Mittlerweile hat es die Verkaufszahlen von Perl-Büchern überholt. Was für mich erstaunlicher ist, dass auch die Verkaufszahlen von Python-Büchern überholt wurden.
Ein Grund dafür ist sicherlich, dass Ruby on Rails unglaublich populär ist. Webentwicklung hat einfach ein viel breiteres Publikum, als Tools-Entwicklung, wie z.B. Administrationswerkzeuge. Perl wird dagegen hauptsächlich im Administrationsbereich eingesetzt. Und auch Python hat meines Wissens im Webbereich noch kein so zündendes Framework, wie Rails, zu bieten. (Wenn auch derzeit viel entsteht, wie z.B. http://www.turbogears.org/)
Interessanter wäre für mich, wie die Zahlen aussähen, wenn man den kompletten Webentwicklungsbereich rauslassen würde. Das ist aber bei Verkaufsstatistiken kaum möglich, weil ja Ruby On Rails Entwickler auch Grundlagenbücher für Ruby kaufen. Es reicht also nicht, nur die Ruby On Rails Bücher herauszulassen.
Eine weitere Sache ist, dass die Qualität kostenloser Dokumentation entscheidend dafür ist, wie viele kommerzielle Bücher gekauft werden. PHP, Perl und Python werden da wohl viel besser abschneiden, als Ruby. Für Ruby gibt es wirklich ausgezeichnete Bücher, aber noch recht wenig gute kostenlose Online-Dokumentation. Auch wenn bestimmte Bereich schon gut abgedeckt sind, allumfassende und aktuelle kostenlose Dokumentation findet man nicht. Wer sich wirklich tiefer einarbeiten will, ist derzeit meist auf Bücher angewiesen.
Trotzdem lässt sich wohl sagen: Ruby hat in den letzten 2 Jahren stark an Popularität dazugewonnen. Mehr, als ich erwartet hätte. Ein großen Anteil daran hat der Ruby-On-Rails Hype, der die Sprache in eine breite Öffentlichkeit geholt hat. Dies war ein wirklich glücklicher Umstand.
Weblinks:
02.09.2006 Einfaches textbasiertes Menüsystem
Vor Jahren hatte ich mir schonmal ein kleines textbasiertes Menüsystem gebaut, damit unbedarfte Anwender ein paar Linux-Kommandos an der Konsole absetzen können. Ich meine diese althergebrachten Menüs, wo man durch Eingabe einer Zahl einen Menüpunkt auswählt, evtl. noch ein paar Eingaben macht, um dann irgendeine Aktion auszuführen. Ein typisches Beispiel wäre das Einstellen der Uhrzeit. Nach Auswahl des Menüpunkt "1. Set Time/Date" wird man aufgefordert, Uhrzeit und Datum einzugeben. Danach setzt das Programm die Uhrzeit.
Weil ich mir manche Linux-Befehle nicht so gut merken kann, wollte ich mir nun so ein möglichst universell benutzbares Menüsystem bauen. Hier ist ein erster Wurf, der schon ganz gut funktioniert.
Zuerst einmal der allgemeine Menüsystem-Teil (txtmenu.rb):
# txtmenu.rb # Winfried Mueller, www.reintechnisch.de, Start: 01.09.06, Stand: 05.09.06 # # Lizenz: GPL # # Einfaches textbasiertes Menuesystem. Typischer Einsatz, um Aktionen # menuebasiert auf der Konsole absetzen zu koennen. Verschachtelte # Menues mit beliebig vielen Ebenen sind moeglich. # # Die Struktur des Menues wird im Yaml-Format abgelegt. Beispiel: # # --- # Menu: # Entries: # - Text: 'Info' # Action: 'info' # # - Text: 'Set Date' # Action: 'set_date' # # - Text: 'View Untermenue' # Name: 'View' # Entries: # - Text: 'View Date/Time' # Action: 'date' # - Text: 'View passwd' # Action: 'passwd' # - Text: 'View apt sources' # Action: 'apt_sources' # # - Text: 'Network issues' # Name: 'Network' # Entries: # - Text: 'IP' # Action: 'ip' # - Text: 'Routing' # Action: 'routing' # # # Fuer jede Action muss eine Klassen-Methode implementiert werden. Fuer # jede Menue-Ebene gibt es eine separate Action-Klasse. Die Hauptebene # heißt Mn_Home, alle weiteren Mn_<Name> # Beispiel: # # class Mn_Home # def self.info # ... # end # end # # class Mn_View # def self.date # ... # end # end # # # Einige Helper-Methoden unterstützen die Interaktion, z.B. input und # wait_for_key. require "yaml" #require "wmclasslib/dbg" #---------------------------------------------------------------------- def platform case RUBY_PLATFORM when /linux/i, /freebsd/i, /bsd/i, /solaris/i, /hpux/i, /powerpc-darwin/i :unix when /mswin32/i, /mingw32/i, /cygwin/i, /bccwin32/i :windows when /java/i :java else :other end end class TXTMenu def initialize( menucfg ) @menu_config = YAML.load( menucfg ) @mn_pos = [] end def controller while 1 do view_menu() selection = prompt_selection if selection != nil && selection.to_i > 0 selection = selection.to_i - 1 if curr_entries[selection]["Entries"] @mn_pos << curr_entries[selection]["Name"] next end if (m = curr_menu) mn_class = "Mn_" + m else mn_class = "Mn_Home" end r = eval( mn_class + "." + curr_entries[selection]["Action"] ) if r == 0 # exit if !up_tree return end end else if selection =~ /^x$/i if !up_tree return end else puts "Falsche Auswahl" sleep 1 end end end end # --------------------------------------------------- private def up_tree if @mn_pos.size > 0 @mn_pos.pop return true else return false end end def curr_menu if @mn_pos.size > 0 return @mn_pos[-1] else nil end end def prompt_selection print "Auswahl: " selection = gets.chomp if selection.to_i > 0 unless selection.to_i > 0 && selection.to_i <= curr_entries.size selection = nil end end selection end def curr_entries() menu = @menu_config['Menu'] @mn_pos.each do |sub_entry| #p menu x_require unless menu menu = menu["Entries"].detect do |e| e["Name"] == sub_entry && e["Entries"] end end x_require unless menu menu["Entries"] end def view_menu() nr = 1 clear_screen puts "----------------------------------------------------------" puts view_pos puts curr_entries.each do |e| printf "%2.2d ", nr print e['Text'] if e['Entries'] print "..." end print "\n" nr +=1 end puts " X Exit" puts end def view_pos() ["Pos: Home"].concat( @mn_pos ).join( " > " ) end end # -------------------------------------------------- # Helpers # Input - Eingabe Werte # def input( prompt, rxp=nil, &block ) inp = "" while 1 do print prompt + " " inp = gets.chomp if block if block.call( inp ) == true break end else if rxp != nil && inp =~ rxp break end end puts "Fehlerhafte Eingabe." end inp end # wait_for_key - Warte auf Tastendruck # def wait_for_key(prompt="<----- Druecke Enter fuer weiter... ----->") puts print prompt gets end # clear_screen - Clear Screen def clear_screen if platform == :windows system( "cls" ) elsif platform == :unix system( "clear" ) else # clear not possible => 2 empty lines for Separation puts puts end end # pager - Text seitenweise anzeigen def pager( text, lines=23 ) clear_screen() count = 0 i = "" text.each_line do |line| print line count += 1 if count >= lines print "<------ Enter: Weiter | q: Quit ------>" i = gets.chomp if i =~ /q/ break end count = 0 i = "" clear_screen() end end if i =~ /^\s*$/ wait_for_key end end
Diesen Code binden wir nun für einen ersten kleinen Test in das eigentliche Programm ein (testmenu1.rb)
# testmenu1.rb require "txtmenu" # ================================================================= # -------------------------------------------------- # Actions class Mn_Home def self.info puts "Das ist eine Info." wait_for_key end def self.set_date t = input( "Uhrzeit(HH:MM):" ) do |inp| r = false if inp =~ /^([0-9][0-9]):([0-9][0-9])$/ h = $1.to_i m = $2.to_i if h >= 0 && h < 24 && m >= 0 && m < 60 r = true end end r end d = input( "Datum(DD.MM.YY):", /^[0-9][0-9].[0-9][0-9].[0-9][0-9]$/ ) puts "Neues Datum wird gesetzt: #{d} #{t}" wait_for_key end end # -------------------------------------------------- class Mn_View def self.date return 1 end def self.passwd return 1 end def self.apt_sources return 1 end end # -------------------------------------------------- class Mn_Network def self.ip return 1 end def self.routing return 1 end end # --------------------------------------------------- # Config cfg = <<EOF --- Menu: Entries: - Text: 'Info' Action: 'info' - Text: 'Set Date' Action: 'set_date' - Text: 'View Untermenue' Name: 'View' Entries: - Text: 'View Date/Time' Action: 'date' - Text: 'View passwd' Action: 'passwd' - Text: 'View apt sources' Action: 'apt_sources' - Text: 'Network issues' Name: 'Network' Entries: - Text: 'IP' Action: 'ip' - Text: 'Routing' Action: 'routing' EOF # --------------------------------------------------------- # Main menu = TXTMenu.new( cfg ) menu.controller
Zum einen habe ich hier mittels cfg die Konfiguration des Menüs in Yaml-Syntax festgelegt. Damit zeigt sich, wie flexibel die Sache ist. Ich kann damit beliebige Menüs schnell erzeugen. Auch Menüs, die über mehrere Ebenen gehen. Mit "Action" wird hier die Aktion bzw. der Funktionsname festgelegt, der aufgerufen werden soll, wenn dieser Menüpunkt ausgewählt wird. Aktionen sind in Klassen gekapselt, für jede Menüebene existiert eine Klasse, wobei die oberste Menüebene mit Mn_Home definiert ist. Wählt also jemand im Hauptmenü "Info" aus, so wird die Klassenmethode self.info in der Klasse Mn_Home angesprungen.
Im View-Untermenü geben wir über "Name" diesem Menü den Namen "View". Folglich sind dessen Aktionen in der Klasse Mn_View gekapselt.
Um Eingaben zu behandeln, gibt es die Helper-Methoden wait_for_key und input. Dabei kann input sowohl eine reguläre Expression übernehmen, die das Format der Eingabe verifiziert. Wie auch einen Block, über den man die Eingabe prüfen kann. Nur wenn die Eingabe dem Muster entspricht, wird diese angenommen. Wenn nicht, wird erneut aufgefordert, eine Eingabe zu tätigen.
Die Ausgabe im Hauptmenü sieht hier etwa so aus:
---------------------------------------------------------- Pos: Home 01 Info 02 Set Date 03 View Untermenue... X Exit Auswahl:
Man sieht: Mit dem Code hat man innerhalb weniger Minuten sein eigenes menübasiertes System aufgebaut.
09.08.2006 Rails: Configdatei einlesen
Jede Anwendung braucht irgendeine Konfiguration. Eine gute Idee ist es, wenn man die gesammelt in einem Yaml-File verwaltet. Hier eine Möglichkeit, dies zu realisieren:
1. Anlegen einer config.yml im Verzeichnis config der Applikation. Beispiel:
--- Config: General: EntriesPerPage: '10' Limits: MaxEntries: '1000'
2. Ans Ende der environment.rb lädt man diese in eine globale Konstante.
# environment.rb ... CFG = YAML.load_file( File.join( RAILS_ROOT, "config", "config.yml" ) )
3. Nun lässt sich in der Anwendung darauf zugreifen.
Persönlich nutze ich noch die selbst erstellte Klasse YamlCfg (siehe RubyWeblogArchiv-2005-3#A012). Dafür schreibe ich in der environment.rb folgendes:
# environment.rb ... require File.join( RAILS_ROOT, "lib", "yamlcfg" ) CFG = YamlCfg.new( File.join( RAILS_ROOT, "config", "config.yml" ) )
Damit das klappt, muss zuvor die yamlcfg.rb ins Verzeichnis lib der Applikation kopiert werden.
Vorteil ist, dass der Zugriff auf die Configvariablen nun ganz simpel per Pfadangabe ist. In der Anwendung verwende ich einfach:
maxentries = CFG['Config/Limits/MaxEntries'].to_i entries_per_day = CFG['Config/General/EntriesPerPage'].to_i
Ich habe mir angewöhnt, in Konfigurationsdateien generell nur Textstrings als Werte zu benutzen, deshalb braucht es hier ein to_i. Es spricht aber auch nichts dagegen, Integerwerte gleich als Integer in der Yaml-Datei zu halten.
28.06.2006 Rails: Logformat aufbessern
Das Logformat von Rails ist standardmäßig nicht sehr komfortabel. Besser geht es mit log4r. Wie man das einbindet, kann man hier nachlesen.
25.06.2006 Ruby Intepreter Online
Ein Ruby Intepreter, mit dem man Online im Browser spielen kann. Das gab es vor Jahren schon einmal, nun auch auf der Hobix-Homepage in einem schicken Gewand: http://tryruby.hobix.com/
02.06.2006 Schlüsselwortargumente (keyword arguments)
Rails macht exzessiv Gebrauch von Schlüsselwortargumenten in Methodenaufrufen. Die Sache ist etwas "tricky" und für Anfänger syntaktisch nicht leicht verständlich. Deshalb mal hier eine kleine Erläuterung.
Ein normaler Methodenaufruf wäre dies:
# Definition def myMethod( a, b, c) ... end # Aufruf myMethod( 10, 20, 30 )
Von welchem Typ a, b und c sind, ist nicht festgelegt, grundsätzlich kann man also alle möglichen Objekte übergeben. Die Methode kann also nicht davon ausgehen, die korrekten Datentypen bzw. Objekte übergeben zu bekommen.
Praktisch ist es, wenn man bestimmte Parameter mit einem Standardwert belegen kann, dann kann man sich manchmal eine Angabe sparen.
# Defintion def myMethod( a, b = 10, c = 20 ) ... end # Aufruf myMethod( 10 ) # -> a = 10, b = 10, c = 20 myMethod( 10, 50 ) # -> a = 10, b = 50, c = 20
Standarwerte haben eine Einschränkung. Man kann in diesem Beispiel b und c weglassen oder auch nur c. Man kann aber nicht b weglassen und c definieren.
myMethod( 10,,50 ) # -> geht nicht
Flexibler wäre folgende Idee: Es gibt ein paar benamte Parameter, die alle Defaultwerte haben und die man auch setzen kann. In etwa in der Art:
# Idee, funktioniert nicht wirklich myMethod( action => "Test", length => "10", width => "50" )
So etwas ist sehr flexibel. Eine Methode kann eine ganze Reihe von Parametern haben, wovon man nur einige in beliebiger Reihenfolge setzen kann. Der Rest, der nicht gesetzt wird, handhabt die Methode dann z.B. mit irgendwelchen Defaultwerten.
In anderen Sprachen wie Python ist das ein etabliertes Konzept. In Ruby gibt es dies als direkt unterstütztes Konzept nicht. Man kann sich jedoch über einen Hash behelfen.
# Definition def myMethod( params ) end # Aufruf, Hash mit Schlüssel als Symbol myMethod( { :action => "Test", :length => "22", :width => "50" } )
Das ist nichts außergewöhnliches, wir definieren im Methodenaufruf einfach ein Hash-Literal, wobei wir für den Schlüssel Symbole verwenden. Man kann genauso gut auch Strings verwenden. In der Methodendefinition kann man nun über den Hash params darauf zugreifen.
Weil diese Form des Aufrufs sehr häufig ist, wurde eine Syntaxvereinfachung eingeführt: Man kann die geschweiften Klammern des Hash-Literals einfach weglassen. (Die Klammern bei Methoden grundsätzlich auch, das geht immer und überall.) Und dann kommt das dabei raus, was man haufenweise in Rails wiederfindet.
# Kurzform link_to "Kommentar", :action => "show", :id => code # ist identisch zu... link_to( "Kommentar", {:action => "show", :id => code} )
Hier haben wir zuerst einen normalen Parameter und dann einen Hash. Verwendet man diese Hash-Syntax-Vereinfachung, muss man allerdings eines beachten: Immer zuerst die normalen Parameter und dann der Hash. Umgedreht funktioniert es nicht. Hinter den Hash-Werten darf kein normaler Parameter mehr folgen.
# So rum geht es nicht def myMethod( params, description ) end myMethod :action => "show", :id => 10, "Kommentar"
Die Belegung von Hash-Parametern mit Standardwerten geht nicht so einfach:
def myMethod( params = {:a=>10, :b=>20} ) ... end myMethod( :a=20 ) # > :a=20, :b=nil
Das ist im Grunde auch logisch. Als Standard wird params mit dem Hash-Literal-Objekt belegt. Sobald man aber auch nur einen Hash-Wert beim Methodenaufruf angibt, wird nicht mehr der Default-Hash verwendet. Man muss also vielmehr in der Methode prüfen, welche Werte belegt sind und den Rest mit eigenen Defaults belegen.
01.06.2006 Railsprovider: tibit
Einen Webspaceprovider zu finden, der auch Rails unterstützt, ist derzeit noch keine so leichte Übung. Da überrascht es, dass derzeit tibit.de ein extrem günstiges Angebot macht: de-Domain, Webspace, Ruby on Rails inklusive, für gerade mal 90 Cent im Monat.
Erfahrungen mit tibit.de gibt es meinerseits noch nicht. Ich bin aber am überlegen, bei dem Preis einfach mal einen Testaccount einzurichten. In der technischen FAQ steht übrigens, dass das Angebot keinen Haken hat. Dann wird ja alles gut... ;-)
Nachtrag: Einen Haken habe ich gerade doch gefunden, was Rails-Nutzer angeht. Fastcgi ist nicht möglich und normales CGI macht bei Rails nicht wirklich Freude. Zum testen und für ein paar Miniskripte ok, aber ansonsten nervig, wenn die Auslieferung jeder Seite 4-10 Sekunden dauert.
01.06.2006 Rails: Login und Zugriffskontrolle
Wer Login, Zugriffskontrolle und Benutzerrechteverwaltung für Ruby on Rails sucht, sollte sich mal ActiveRbac anschauen.
26.05.2006 Rails: Wo ist der Application error?
Jetzt hab ich mir 1 Stunde den Wolf gesucht, warum meine Rails-Anwendung nicht recht laufen wollte. Mit dem Webrick-Server lief alles bestens, wenn ich aber auf apache-cgi umstellte, kam bei der Ausführung einer Aktion "Application error" und "Rails application failed to start properly". Im Logfile von Apache und Rails konnte ich nichts finden. Also blieb nur, Stück für Stück jede Codezeile auszudokumentieren, um mich vorzutasten, wo der Fehler liegt.
Der Fehler war ganz simpel: In einer Helper-Methode, die ich mir in die application.rb gelegt hatte, habe ich versehentlich ein paar puts Fehlerausgaben noch dringehabt. Ein puts (oder sonstige Ausgabe auf den stdout) irgendwo im Code zu haben, scheint wohl tödlich zu sein, zumindest wenn man über Apache cgi oder fcgi arbeitet. Sehr unschön, dass Rails da nicht etwas gesprächiger ist.
26.05.2006 Rails fcgi Installation auf Ubuntu Breezy
Nun wollte ich auch mal fcgi mit Apache2 ausprobieren, hatte dabei aber einige Probleme. Hier mal eine Anleitung:
#Install libapache2-mod-ruby, falls noch nicht vorhanden apt-get install libapache2-mod-ruby #Install libfcgi apt-get install libfcgi-ruby1.8 #Anpassen der .htaccess im path/to/railsapp/public AddHandler fastcgi-script .fcgi #-> muss entfernt werden (Dieser Schritt ist wichtig, weil unter Ubuntu nicht mod_fastcgi (fastcgi-script) sondern mod_fcgid (fcgid-script) benutzt wird. Macht man das nicht, wird einem die dispatch.fcgi als Textfile im Browser ausgeliefert.) #virtuellen Host definieren, ich lass den mal auf Port 3001 #lauschen, damit Port 80 auf Default-WWW-Verzeichnis zeigt cd /etc/apache2/sites-available vi myrailshost #Einfügen in myrailshost <VirtualHost *:3001> ServerName * DocumentRoot /var/www/myrailstest/public/ ErrorLog /var/log/apache2/error.log <Directory /var/www/myrailstest/public/> Options ExecCGI FollowSymLinks AddHandler cgi-script .cgi AddHandler fcgid-script .fcgi AllowOverride all Order allow,deny Allow from all </Directory> </VirtualHost> #fcgi Module in Apache enablen a2enmod fcgid #virtualhost enablen a2ensite myrailshost #ports.conf -> Port 3001 nachtragen cd /etc/apache2 vi ports.conf #Add Zeile in ports.conf Listen 3001 #Restart apache /etc/init.de/apache2 restart #Test im Browser http://localhost:3001/mytestcontroller
Zu erwähnen ist noch, dass mod_fcgid eine Weiterentwicklung von mod_fastcgi ist. Anscheinend läuft das besser im Zusammenhang mit Rails. Im Netz liest man von vielen Problemen mit mod_fastcgi, besonders im Zusammenhang mit Rails. Das liegt wohl daran, dass mod_fastcgi seit Jahren nicht mehr gepflegt wird.
Weblinks:
- Eine weitere Anleitung
- http://myles.eftos.id.au/blog/archives/27
- http://www.fastcgi.com/ - Fastcgi Homepage
- http://fastcgi.coremail.cn/index.htm - mod_fcgid Homepage
- http://weblog.textdrive.com/article/187/mod_fcgid
25.05.2006 Ruby on Rails auf Ubuntu Breezy
Dumm, wenn ausgerechnet die Ruby-Version, die auf der aktuellen Ubuntu enthalten ist, nicht mit Ruby on Rails zusammen läuft. Es handelt sich dabei um die Version 1.8.3, die Probleme bereitet.
Es gibt aber eine einfache Möglichkeit, Ruby 1.8.4 zu installieren. Man benutzt einfach die Pakete der nächsten Ubuntu Version Dapper. Glücklicherweise lassen sich die Binärpakete direkt in Breezy integrieren.
1. Ruby entfernen (apt-get remove ruby1.8; apt-get remove libruby1.8)
2. Ruby Pakete von Dapper hier downloaden:
- http://packages.ubuntu.com/dapper/libs/libruby1.8
- http://packages.ubuntu.com/dapper/interpreters/ruby1.8
3. Diese Pakete mit dpkg -i in obiger Reihenfolge installieren.
4. Jetzt sollte man mit ruby1.8 --version "ruby 1.8.4" als Antwort zurückbekommen. Wenn der Link /usr/bin/ruby fehlt, dann mit "ln -s /usr/bin/ruby1.8 /usr/bin/ruby" anlegen. Gleiches gilt für den irb.
5. irb standardmäßig installieren, also "apt-get install irb1.8"
Ein erster Test mit Rails 1.1.2 funktionierte problemlos.
Um Rails über Apache ans Laufen zu bekommen, findet sich hier eine sehr gute Schritt-für-Schritt Anleitung:
Zu beachten wäre hier noch, dass das Rails-App Verzeichnis rechtemäßig so angepasst werden muss, damit es funktioniert:
chgrp www-data -R /path/to/rails/app chmod g+w -R /path/to/rails/app
03.03.2006 PDF-Dokumente generieren
Wer aus Ruby PDF-Dokumente generieren möchte, sollte sich mal Ruby FPDF anschauen. Es ist ein Port des in PHP geschriebenen FPDF.
Weblinks:
- http://brian.imxcc.com/fpdf/ - Ruby Port
- http://www.fpdf.de - Original PHP fpdf, deutsche Seite
- http://www.fpdf.org - Original PHP fpdf
- http://www.artima.com/rubycs/articles/pdf_writer.html - Eine weitere Möglichkeit: pdf-Writer
- http://rubyforge.org/projects/ruby-pdf/ - pdf-Writer auf Rubyforge
02.03.2006 Activesupport auch ohne Rails
Die Komponente activesupport gehört zwar zum Rails Projekt, kann aber auch unabhängig davon in Projekten benutzt werden. Hier findet man ganz rudimentäre Unterstützung und Ergänzungen zu bekannten Standardklassen.
Beispiel Byteumrechnungen
require "active_support" puts 20.kilobytes #=> 20480 puts 20.megabytes #=> 209771520 puts 20.gigabytes #=> 21474836480
Beispiel Zeitmanipulation und Zeitberechnung
require "active_support" puts 20.minutes #=> 1200 puts 20.hours #=> 72000 now = Time.now puts now #=> Thu Mar 02 19:03:39 CET 2006 puts 72000.ago #=> Wed Mar 01 23:03:39 CET 2006 puts 1.hours.ago #=> Thu Mar 02 18:03:39 CET 2006 puts 1.hours.from_now #=> Thu Mar 02 20:03:39 CET 2006 puts now.ago( 1.hours ) #=> Thu Mar 02 18:03:39 CET 2006 puts now.at_beginning_of_day #=> Thu Mar 02 00:00:00 CET 2006 puts now.at_beginning_of_month #=> Wed Mar 01 00:00:00 CET 2006 puts now.at_beginning_of_year #=> Sun Jan 01 00:00:00 CET 2006 puts now.at_midnight #=> Thu Mar 02 00:00:00 CET 2006 puts now.last_year #=> Wed Mar 02 19:03:39 CET 2006 puts now.monday #=> Mon Feb 27 00:00:00 CET 2006 puts now.months_ago(2) #=> Mon Jan 02 19:03:39 CET 2006 puts now.to_date #=> 2006-03-02 (Objekt der Klasse Date) #Konvertierung String => Date oder Time #nutzt parsedate aus Ruby stdlib puts "2006-03-02 19:03:39".to_date #=> 2006-03-02 (Objekt der Klasse Date) puts "2006-03-02 19:03:39".to_time #=> Thu Mar 02 19:03:39 UTC 2006 puts "2006/03/02".to_date #=> 2006-03-02 (Objekt der Klasse Date)
String Manipulation
require "active_support" puts "test_string".camelize #=> TestString puts "person".pluralize #=> people puts "car".pluralize #=> cars puts "strings".singularize #=> string
Integer
require "active_support" puts 1.even? #=> false puts 1.odd? #=> true puts 10.multiple_of?( 2 ) #=> true (10 ist vielfaches von 2)
Weblinks:
- http://api.rubyonrails.org/ - Ruby on Rails API Dokumentation
23.01.2006 Noch ein Ruby On Rails Buch in deutsch
Das ist ja ein Hammer: Ein weiteres Ruby On Rails Buch in deutsch: "Rapid Web Development mit Ruby on Rails". Auch dieses ist aus dem Hanser Verlag. Die Homepage zum Buch findet man hier: http://www.rapidwebdevelopment.de/
10.01.2006 Jetzt in deutsch: Agile Webentwicklung mit Rails
Nun ist es auch in deutsch verfügbar: Das Buch zu Ruby on Rails zu 39,90 Euro. ISBN: 3446404864
Das hat mich etwas erstaunt. So schnell geht's sonst nicht. Die englische Version ist ja erst seit ein paar Monaten raus. Das Referenzhandbuch dagegen "Programming Ruby", scheint's noch nicht in deutsch zu geben.
10.01.2006 Neues Jahr, neuer Anfang
Oh je, Ruby ist in den letzten Monaten etwas aus meinem Blickfeld verschwunden. Ein anderes Projekt hat seit September so viel Zeit beansprucht, dass für Ruby nicht mehr viel Platz war.
Und dann war da noch die schleppende Einarbeitung in Ruby on Rails. Irgendwie haben die Dinge nicht zusammengepasst: Nicht genügend Zeit und nicht genügend Ruhe. Und so hat irgendwie auch der Ruby on Rails Funke nicht recht gezündet. Sehr schade, weil ich so einige Projekte gerne damit umsetzen möchte.
Trotzdem, das Ruby On Rails Buch hab ich ca. 1/3 durch und der dort vorgestellte Webshop ist schon in einigen Teilen verstanden. Ich denke, ich werde bald einen neuen Versuch starten, dass Buch durchzuarbeiten, um dann mich an erste eigene Projekte ranzuwagen.
<< Archiv 2007 | RubyWeblog | Archiv 2005-3 >>