Ruby on Rails Screencast - Implementation einer User Anmeldung

Für eine kommende Vorlesung über Test Driven Development habe ich einen Screencast angefertigt.

Das ganze natürlich in Rails mit Test::Unit und Vim (wegen der Sparsamkeit mit dem Screenplatz).

Das Auditorium kennt Rails größtenteils nicht, weshalb es ein einfach gewähltes Beispiel ist:
Die Implementierung eines simplen Authentifizierungsmechanismus, der einen Nutzer am System anmeldet.

Screencast Rails Session Authentication from Stefan Wienert on Vimeo.

Das genutzte Programm zur Aufnahme war Xvidcap. Sehr genial.

Mal wieder ein paar Links zum Thema Rails und Vim

Vim:

Rails:

Rails/Passenger im Parallels Plesk zum Laufen bekommen

Wer auch Parallels Plesk Panel für seine Serveradministration verwendet, und sich schonmal gefragt hat, ob es was besseres als die Build-in Fast CGI Unterstützung oder Mongrel/Webrick gibt, dem will ich hier mal einen kleinen Guide geben, wie das ganze mit Passenger zu bewerkstelligen ist.
Wir verwenden für die Administration unserer Server auch Plesk, da es echt stupid simple ist.

Ich gehe davon aus, dass Ruby und Rubygems bereits installiert sind! (Sollte man z.B. Debian verwenden, auf keinen Fall die Pakete aus dem apt-get installieren, sondern manuell wie hier beschreiben.) Auch ein Root-Zugang zum Server wird benötigt.

Also:

  1. im Plesk eine neue Domain/Subdomain anlegen
  2. Unser Rails-Projekt nach /var/www/vhosts/DOMAIN/httpdocs bzw. /var/www/vhosts/DOMAIN/subdomains/SUBDOMAIN/httpdocs kopieren. Also genau dort rein, kein weiteres Unterverzeichnis (nach httpdocs kommen dann gleich app db config public …)
  3. Schauen, dass die Lese/Schreibrechte hinhauen: z.B. chgrp psaserv log db tmp -R && chmod g+w * -R. Im einzelnen mal schauen.
  4. Root Konsole: Gems installieren und Datenbank migrieren:
1
2
3
4
5
rake gems:install
rake db:migrate RAILS_ENV="production"
gem install passenger
# laengere Anleitung befolgen. Im Endeffekt muss man ein paar Zeilen in die apache2.conf kopieren. 
# Den zweiten Teil mit mit der Vserver Konfiguration interessiert uns im Moment nicht
  1. Im Ordner DOMAIN/conf also von unserem httpdocs Verzeichnis ../conf/vhost.conf nun die Datei vhost.conf anlegen und folgendes reinkopieren (jeweils das richtige Verzeichnis als Ziel nehmen, je nachdem ob wir eine Subdomain oder Domain benutzen möchten):
1
2
3
4
5
6
 DocumentRoot /var/www/vhosts/DOMAIN/httpdocs/public
# bzw.   /var/www/vhosts/DOMAIN/subdomains/SUBDOMAIN/httpdocs/public
 <Directory /var/www/vhosts/pludoni.de/subdomains/tasks/httpdocs/public >
    AllowOverride all
    Options -MultiViews
 </Directory>
  1. plesk Vhost reloaden:
1
/usr/local/psa/admin/sbin/websrvmng -u --vhost-name=DOMAIN.de

hierbei jeweils immer nur die Domain angeben, auch wenn wir nur einen Subdomain anlegen.

Nun sollte unsere Rails App unter der Domain bzw. Subdomain verfügbar sein. Neustarten können wir Passenger indem wir ein touch tmp/restart.txt oder die Datei sonst irgendwie im Timestamp verändern.

Kurzes Gem Showcase - Delicious, actsasarchive, menu borwsercms css sprite

Ich schaue gerade meine kürzlich gebookmarkten Sites an, und will hier mal ein paar coole Ruby Ressourcen zeigen.

  • Delicious-API: http://github.com/weppos/www-delicious für den beliebten Bookmarkdienst
  • Acts as archive, destroy und delete Aktionen eines Objektes führen nicht zur Löschung, sondern Archivierung http://github.com/winton/acts_as_archive
  • MMMenu http://github.com/snitko/mmmenu eine kleine Menü DSL)
  • Automatische, transparente CSS Sprite Transformation http://github.com/reevoo/spriter
  • BrowserCMS, meine Basis CMS für meine nächsten Projekte http://www.browsercms.org/
    Schaut euch das Video dazu an

und sowieso:
http://www.ruby-toolbox.com/ und http://www.rubyflow.com/ zur Ideenfindung.

I love that community :)

Minibot für Erstellung eines iCals und RSS-Feeds von einer Web1.0 Site

In Dresden gibt es die Hochschule für Musik “Carl Maria von Weber”, welche auf ihrer Website auch ihr aktuelles Programm kundtun. Wer etwas Interesse an klassischer Musik hat, hat durch diese Art der Konzerte die Gelegenheit, sehr gute Pianisten sehr preiswert (umsonst…) zu hören.

Leider bieten sie weder einen Feed noch einen Kalender an, deshalb dachte ich mir, das wär wieder ein guter Einsatz für das hpricot-Gem, ich will hier mal kurz den Ablauf skizzieren.

Rien ne va plus … ohne Gems!

1
%w[rubygems hpricot curl active_support icalendar].each { |x| require x}

Im ersten Fall hatte ich Probleme mit den Umlauten, also erst einmal alles nach UTF-8 transformieren/encodieren:

1
2
3
4
5
cal = Calendar.new  # by the way: wir machen gleich mal einen ical draus, siehe icalendar gem.

curl_object = Curl::Easy.perform("http://www.hfmdd.de/veranstaltungen/")
body = Iconv.conv("UTF-8//IGNORE","ISO-8859-1", curl_object.body_str)
doc = Hpricot(body)

Im nächsten Schritt mit Selector Gadget und Firebug herausfinden, welcher CSS-Selektor uns Zeiger auf die Contentbereiche liefert, hier z.B. “#contentbereich div” mit der Eigenschaft, dass deren margin 35em ist… Leider haben die Webdesigner bei der Site der Musikhochschule wenig Gebraucht von Klassen oder IDs gemacht.

1
2
3
4
5
6
7
8
9
elements = doc.search("#contentbereich div").select{ 
  |it| it[:style].include? "35em" if it[:style].present?
}
# present? ist das Gegenteil von .blank?

# Jetzt traversieren wir die Elemente die uebrig bleiben
elements.each do |item|
  [...]
end

Jetzt zum langweiligeren […] Teil, dem lokalen Extrahieren der Daten…
Das passiert leider etwas unsauber, da, wie man auf der Seite sehen kann, recht willkürliche Formate in den Datumsangaben gemacht wurden, hier mein bester Versuch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      text = item.search("div[2]").inner_html rescue text = "???"
      date = item.search(">div>b").first.inner_text.split(".")
      time = item.search(">div[1]>*")[4].to_s[0..4].split(":") rescue time = "00:00"
      begin
        new_date = DateTime.strptime("20#{date[2]}-#{date[1]}-#{date[0]}T#{time[0]}:#{time[1]}:00+0100") 
        next if new_date < Date.today
        event = cal.event
        event.start = new_date
        event.summary = text.split("<br").first[0..150]
        event.description = text  
      rescue
        puts "INVALID: 20#{date[2]}-#{date[1]}-#{date[0]}T#{time[0]}:#{time[1]}:00+0100"
        # spaeter ein logger
      end

Jetzt koennen wir den Kalendar direkt ausgeben, oder sonstwas damit machen

1
cal.to_ical

Das Ganze war eher ein Proof-of-Concept, denn wie immer, wenn man ScreenScraping betreibt, sollte man eine Art Einverständnis des Inhabers der gescrapten Seite haben (oder sie bieten wie Google und Twitter eine API an)

Yakuake mit Startskripts versehen - Automatische Einrichtung der Arbeitsumgebung

Diejenigen von euch, die eine KDE Umgebung unter Linux verwenden, kennen vielleicht yakuake, ein Terminalprogramm mit etwas EyeCandy, welches bei Druck von F12 aus dem oberen Bildschirmrand herausfährt.
Ich selbst nutze zwar Gnome, nehme aber als priorisiertes Terminalprogramm trotzdem yakuake, da ich finde, dass das Farbschema “Dunkle Pasteltöne” einfach gut aussieht, und man die Shell, egal auf welchem Virtuellem Desktop man sich befindet, nie aus den Augen verliert (F12 und sie ist wieder da).

Die Dokumentation der Skriptfähigkeit ist etwas dürftig, außer einem Blogeintrag habe ich nicht sehr viel gefunden. Das dort abgebildete Beispielshellskript hab ich bei mir nicht zum Laufen bekommen, darum hab ich ein Ruby Programm dafür geschrieben…. (Ehrlich gesagt, find ich Shell Skript hässlich, keine Parameterliste, hässliche Conditional-Syntax… ja wann kommt denn endlich eine Ruby Shell?? :D)

kurze Einführung

Ersteinmal muss yakuake laufen, vornehmlich in einem initialen Zustand.
Das Ganze läuft über qbus und man ruft prinzipiell nur ein paar recht einfache Shellkommandos auf:

Ein Aufruf von

1
2
3
4
5
qdbus org.kde.yakuake /yakuake/sessions 

# und

qdbus org.kde.yakuake /yakuake/tabs 

Gibt uns alle möglichen Kommandos auf. Hier meine Favoriten:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
qdbus org.kde.yakuake /yakuake/sessions addSession
qdbus org.kde.yakuake /yakuake/sessions addSessionTwoHorizontal
qdbus org.kde.yakuake /yakuake/sessions addSessionTwoVertical
# Macht ein neues Tab (mit zwei, horizontal/vertikal geteilten Sessions sprich Terminals)

qdbus org.kde.yakuake /yakuake/tabs setTabTitle $tabid 'name'
# Setzt den Tab auf ein neues Label

qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal $sessionid 'command'
# Führt ein Shell-Kommando in Terminal Nr X aus

qdbus org.kde.yakuake | grep Sessions | cut --fields "3" --delim="/" | sort -n | tail -n 1
# Gibt uns die letzte vergebene SessionID aus

Wie man sieht, besteht das eigentliche Problem, den Überblick über die Sessions (Terminals) und Tabs zu behalten. Wenn man keine geteilten Fenstern nutzt, sollten die beiden identisch sein.

Ein Ruby-Wrapper

Um den IDs herr zu werden, habe ich das Ganze in ein Rubyprogramm, eine Klasse, geflochten:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Yakuake

  def initialize
    @cid=0      
    @tabid=0    
  end           

  def exec(cmd)
    puts cmd   
    system cmd 
  end          

  def add_splitted_tab(title1,command1,command2)
    exec "qdbus org.kde.yakuake /yakuake/sessions addSessionTwoHorizontal"
    @tabid+=1                                                             
    cid = last                                                            
    exec "qdbus org.kde.yakuake /yakuake/tabs setTabTitle #{@tabid} '#{title1}'"
    [command1, command2].each_with_index do |command,index|                     
      if command                                                                
        ccid = cid -1 + index                                                   
        exec "qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal #{ccid-1} '#{command}'"                                                                                        
      end                                                                                    
    end                                                                                      
  end                                                                                        

  def add_tab(name, command=false)
    exec "qdbus org.kde.yakuake /yakuake/sessions addSession"
    @tabid+=1                                                
    exec "qdbus org.kde.yakuake /yakuake/tabs setTabTitle #{@tabid} '#{name}'"
    if command and !command.empty?                                            
      exec "qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal #{last - 1} '#{command}'"                                                                                        
    end                                                                                      
  end                                                                                        

  def execute(terminal_id, command)
    exec "qdbus org.kde.yakuake /yakuake/sessions runCommandInTerminal #{terminal_id} '#{command}'"                                                                                       
  end                                                                                        

  def last
    `qdbus org.kde.yakuake | grep Sessions | cut --fields "3" --delim="/" | sort -n | tail -n 1`.strip.to_i                                                                               
  end                                                                                        
end        

# Aufruf/Einrichtung des Workspace:
y=Yakuake.new
y.add_splitted_tab("sshs","ssh username@server.de","ssh username@server2.de")
# jeweils in den Arbeitsordner navigieren und svn up
y.execute(y.last-2,"cd  public_html/modules")
y.execute(y.last-2,"svn up")
y.execute(y.last-1,"cd  ~username/public_html/modules")
y.execute(y.last-1,"svn up")
# ein paar irbs zum "rumprobieren"
y.add_splitted_tab("Irbs", "irb","irb")
# Ein Programmstarter
y.add_tab("Progs","ruby ~zealot64/rubyqt/tray.rb&")

Automatische Silbentrennung / Hyphenation ist online

Ich habe heute ein kleines Interface zu einem Silbentrenner geproggt, welches auf zwei Gems (tex/hyphen und text/hyphen) basiert.
Weiter Informationen zu den Quellen auf der Seite. Das Tool ist jetzt jederzeit im Menü verfügbar.
Solange mein Server nicht den Bach runter geht, ist die Nutzung erstmal, auf für automatisierte GET und POST Anfragen frei. Um genau zu sein ist genau diese Skriptingmöglichkeit für mich der Anlass gewesen, diese Funktion bereitzustellen.
Damit justify ausgerichtete Blocksätze auch dank automatisierter Silbentrennung wieder hübsch aussehen. :)

Aktuelle Projekte und Ideen

Wenn ich zur Zeit während meines Praktikums etwas Zeit habe, arbeite ich an ein paar Hobbyprojekten, die mir so in letzter so eingefallen sind:

  • Ein (hübsches) (openSource) DMS basierend auf Rails, welches doc, pdf, odf, text archiviert, taggt und indexiert, um die Dokumente leichter wiederzufinden
    • mit Paperclip, Ferret als Indexierungsdienst, Verwendung von pdftotext, antiword, odf2text und eventuell tesseract zum OCR (auch für Bilder und pdfs), rspec BDD Tests für die Modelle
  • Einen “Bot”, der die eigene Website abgrast um interne Linkverbindungen als Graphen darzustellen, um so lange Wege zu finden und zu eliminieren (Webdesignregel: Redundanz und kurze Wege)
    • Ruby mit mechanize oder evtl. auch einfach “wget -spider” nehmen, graphviz
  • Drupal Scaffold Ein Ruby Skript, welches mir, ähnlich dem scaffold von Rails, ein Datenmodell in ein (MySQL) Schema gießt, und auch gleich einen (einfachen) Adminsetter dazu liefert (eine Seite unter admin/settings/%name mit der Option, ein neues Objekt anzulegen und alte anzuschauen.

Umgang und Mapping einer Legacy Datenbank mit Ruby (ohne Rails) mit abweichenenden Namenskonventionen mit Active Support

Einleitung/Motivation

Zur Zeit moechte ich verschiedene XML-Dialekte aus einem vorhandenen Datenbankschema gewinnen, und brauchte dazu ein ordentliches Objektrelationales Mapping, wie man es aus Rails ja kennt. (Ausprobieren! keine Zeile SQL mehr notwendig :D).
Allerdings ohne Rails sondern in einem einfachen Rubyscript.
Was es dort alles gibt, will ich hier mal kurz exemplarisch vorfuehren.

Voraussetzung und Datenbankverbindung

Um ein Legacy relationales Datenmodell mit Ruby schoen zu mappen, ging ich letztens wie folgt vor:

Zu erst Rails (active_record ist aber auch ausreichend) installieren, falls noch nicht gemacht (fuer active_record) und composite primary keys, das uns wie der Name schon sagt, zusammengesetzte Primärschlüssel, welches unser Legacy-Schema mitunter mit sich bringt, bereitstellt.

1
sudo gem install rails composite_primary_keys 

Nun können wir uns im ersten Schritt mit unserer Datenbank verbinden:

1
2
3
4
5
6
7
8
9
10
11
require "rubygems"
require "active_record"
require "composite_primary_keys"

options = {:adapter  => 'mysql',
      :database => 'databasename',
      :username => 'username',
      :password => '******',
      :host     => 'localhost' ,
      :encoding => 'utf8'}
ActiveRecord::Base.establish_connection(options)

Definition der Modelle

Nun kommt dort drunter die Konstruktion der Modell-Klassen, die die Tabellen abbilden.

Angenommen wir haben eine Tags, Posts, Posts_Tags und Users Tabelle (in freier Ruby Namenskonvention) mit den folgenden Beziehungen:
Post : Tag = n : m (Jobs_Tags)
Post : User = n : 1

Also ein Post hat eine Anzahl Tags (über “jobs_tags”) und genau einen User.

Da das aber zu leicht wäre, gibt es folgende Handicaps :):

  • “Posts” Tabelle heißt “entries_import”, Primaerschluessel (PK) postid, Fremdschlüssel “uid” heißt “nutzer”
  • “Tags” Tabelle heißt “clouds_import”, PK tagid
  • “Posts_Tags” Tabelle heisst “clouds_import_lnk”, PK (postid,tagid) zusammengesetzter Primaerschluessel!
  • “Users”-Tabelle heißt “users”, mit PK uid

(leicht abgeändertes RealWorld Beispiel!)

Tja sieht schon recht messy aus. Aber alles machbar:

Der Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Post < ActiveRecord::Base
  set_table_name "entries_import"  # posts Tabelle
  set_primary_key "postid"
  has_many :tags, :through => :taggings
  has_many :taggings, :primary_key => "postid", :foreign_key => "postid"
  belongs_to :user,  :foreign_key => "nutzer"

# Extra Points! Unser 'Post' hat ein "visible" Attribut, welches 
# geradezu nach einem named scope schreit! :)
   named_scope :visible, :conditions => {:visible => 1}, :order => "pubDate desc"


#Extra Points! Tags direkt als (Komma)getrennte Liste zurueckgeben lassen, und 
# noch den Tag "Blog" auf jeden Fall ans Ende der Tags haengen
  def tag_list(sep = ",")
    (tags.map(&:name) << "Blog").uniq.join(sep)
  end
end

class Tag < ActiveRecord::Base
  set_table_name "clouds_import"
  set_primary_key "tagid"
  has_many :jobs, :through => :taggings
  has_many :taggings, :primary_key => "tagid", :foreign_key => "tagid"
end

class Tagging < ActiveRecord::Base
  set_table_name "clouds_import_lnk"
  set_primary_keys :jobid, :tagid  # Zusammengesetzter Primaerschluessel
  belongs_to :job, :primary_key => "postid", :foreign_key => "postid"
  belongs_to :tag, :primary_key => "tagid", :foreign_key => "tagid"
end

class User < ActiveRecord::Base
  set_primary_key "uid"
  set_table_name 'users'
  has_many :jobs, :foreign_key => "nutzer", :primary_key => "uid"
end

Fertig!
Nun können wir in althergebrachter Rails Manier extrem bequem auf unsere Modelle wie folgt zugreifen:

1
2
3
4
5
6
7
8

posts = Post.all
posts.first.tags  #  gibt uns die tags zurück
post = Post.find(:first, :order => "...")
post.user.name

# oder auch named scopes (siehe oben)
Post.visible.first.tag_list

usw. Wirklich eine Wohltat ;)

Natürlich kann man in seinen Modellen noch Methoden definieren, die uns den Zugriff noch erleichtern und unser Modell um Funktionalität erweitert und damit unsere Controller und Views nicht zumuellt.

Bonus: XML Builder

Wie man jetzt damit einen XML-Dialekt (z.B. auch RSS) baut, ist recht einfach, dank des XML Builder Gems (Das bei Rails eigentlich auch schon dabei sein sollte).
Ich will hier nur ganz kurz teasern, ansonsten.. Google ist dein Freund:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "rubygems"
require "builder"

xml = Builder::XmlMarkup.new( :target => $stdout , :indent => 1 )
xml.instruct!
xml.rss do  
# oder:  xml.tag!("rss") do   sinnvoll falls unser Tag nicht nur aus Kleinbuchstaben besteht
  xml.channel do
    for item in Post.visible
       ...
       ... item.title  ...
      
    end
  end
end

Tool fuer externe/interne Link-Analyse mit rel=nofollow

Fuer ein aktuelles Projekt wurde eine Linkanalyse, insbesondere der ausgehenden Links gewuenscht. Insbesondere die Verwendung des SEO Buzzwords “rel=nofollow” sollte dabei aufgezeigt werden. Was lag naeher als schnell in Ruby mit Zuhilfenahme des hpricot Gems ein kleines Skript dafuer zu bauen?

Das Skript gruppiert die Links in die drei Kategorien “Internal, External with nofollow, External without nofollow”. Hintergrund ist hierbei, dass das nofollow Attribut Suchmaschinen anweist, den verlinkten Seiten bei der Berechnung ihres Pagerankes diesen Link zu ignorieren. Mehr Infos in der Wikipedia. Anmerken moechte ich noch, dass die Verwendung des Attributes umstritten ist, ich persoenlich finde es relativ ueberfluessig; Warum verlinkt man eine Seite, nur um den Suchmaschinen zu sagen, “hey, ich hab jetzt aber nicht auf die Seite verlinkt”.

Dank XML Builder ist dann auch ein ordentlicher XML Export kein Problem, und laesst sich so mit z.B. Firefox bequem navigieren.

Unter linkcheck/link_check_form findet ihr ein minimalistisches Eingabeformular. Das Ganze laesst sich natuerlich auch bequem mit GET ansteuern :).