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

Permalink

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

Permalink

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

Permalink

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

Permalink

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

Permalink

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

Permalink

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

Permalink

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

Permalink

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:

03.09.2006 Bücher Verkaufszahlen

Permalink

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

Permalink

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

Permalink

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

Permalink

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

Permalink

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)

Permalink

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

Permalink

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

Permalink

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?

Permalink

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

Permalink

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:

25.05.2006 Ruby on Rails auf Ubuntu Breezy

Permalink

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:

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

Permalink

Wer aus Ruby PDF-Dokumente generieren möchte, sollte sich mal Ruby FPDF anschauen. Es ist ein Port des in PHP geschriebenen FPDF.

Weblinks:

02.03.2006 Activesupport auch ohne Rails

Permalink

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:

23.01.2006 Noch ein Ruby On Rails Buch in deutsch

Permalink

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

Permalink

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

Permalink

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 >>