I’m writing this blog post in english because it seems to me that many developers still run into this issue. So now it’s up to Google to put them out of their misery…
Automatically reload Rails plugins
I just bought the PDF Plugin Patterns Enhancing Rails 2.1 by Andrew Stewart for getting in touch with best practices for writing a Rails plugin. First of all… this book is awesome. I totally agree to Chris O’Sullivan:
“Bought the [Plugin Patterns] PeepCode PDF…and I gotta say I’m loving it. It’s very well written, and you explain complicated nasty stuff with nice simple examples.”
After reading this book within an hour I started to develop my own plugin acts_as_wizard to make ActiveRecord models suitable for multi-step-forms (aka „Wizards“). I immediately noticed that I had to reload Rails manually every time I changed the plugin code because plugin paths are added to the ActiveSupport::Dependencies.load_once_paths by default. Witold Rugowski suggests adding the plugin to ActiveSupport::Dependencies.explicitly_unloadable_constants and deleting the path from ActiveSupport::Dependencies.load_once_paths or force reloading with ActiveSupport::Dependencies.load_file from a controller.
The wonderful Peepcode book offers a quite easier way…
Reloadable Plugins
If you have done any plugin development at all, you’ll know that changes don’t get picked up until you restart the server. It is tedious.
Happily for us, Rails 2.1 introduced the ability for files in plugins to be reloaded like the rest of the application. To make all plugins reload- able, add the following to your application’s configuration:
You can also switch this on and off per plugin. See the commit log (http://github.com/rails/rails/commit/cc2d6a0b93251fce06bab15c52dbe0a5d5a8342c) for details.
I gave it a try and added config.reload_plugins = true to my environments/development.rb.
It worked – but not for my „acts_as_plugin“. So I wrote an email to the author.
I just bought Plugin Patterns Enhancing Rails 2.1 by Andrew Stewart today.
I’ve a question concerning the following statement on page 95.
In fact it seems that config.reload_plugins = true does not work for acts_as-Plugins, which extend ActiveRecord because ActiveRecord won’t be reloaded anyway (because it’s core part of rails).
Maybe the author has a tip… googling around and trying out did not solve my problem. To be honest… I bought the Peepcode book to avoid this ;-)
After two days I got response from Geoffrey Grosenbach, Mr. Peepcode himself. Cool. And it did the trick. Even cooler.
Here’s another strategy that will definitely work, but requires a bit
of initial configuration.
Summary: Use either unicorn or nginx and Phusion Passenger with my
rstakeout script to automatically restart Passenger every time your
plugin changes.
Unicorn (easy, but untested):
Download my rstakeout.rb script. A copy is here:
http://github.com/EdvardM/rstakeout
Install the unicorn server and start it in your Rails app:
unicorn_rails
Note the PID (“spawned pid=15250″)
In a separate terminal window, run rstakeout.rb, telling it to restart
Unicorn by sending USR2 to unicorn whenever a file in your plugin is
saved:
Now, any time you edit a file in your plugin’s directory, Passenger
will be restarted and you’ll be working with the updated code.
Yes, a bit complicated, but I tried something similar here and it did the trick.
Let me know if you try it!
I can confirm that it works. I’m using Passenger and Apache for development on Mac OS X 10.6.2. Great. I just fetched a copy of rstakeout, put it to /usr/local/bin and ran rstakeout.rb “touch tmp/restart.txt” “vendor/plugins/acts_as_wizard/**/*.rb” from my rails application directory. For testing purposes I added a comment to the plugin’s init.rb and rstakeout immediately noticed it. => vendor/plugins/acts_as_wizard/init.rb changed, running ‘touch tmp/restart.txt’
By the way… if you install the ruby-growl gem and Growl you’ll get a Fancy-schmancy notification everytime you change something.
Trabayo bekommt im Rahmen des X-Mas-Sprints ein paar neue Features, u.a. auch die Möglichkeit zur Integration von Fotos in Service-Profilen, um einer Dienstleistung mehr Ausdruck verleihen zu können. Mittels IPTC-NAA-Annotations werden sinnvolle, maschinenlesbare Metainformationen in die Fotos integriert, sodass diese einen semantischen Kontext bekommen und von Suchmaschinen entsprechend erfasst werden können. Aus verschiedenen Gründen ist es daher erstrebenswert, die Fotos mit einem Wasserzeichen zu versehen, sodass der Bezug zu Trabayo auch optisch direkt hergestellt werden kann. Zu diesem Zwecke habe ich einen Paperclip-Prozessor geschrieben, der wie folgt eingebunden wird.
Über die optionalen Konfigurationsparameter (:watermark_[path|position|dissolve|ratio]) können Dateipfad zum Wasserzeichen, Position des Wasserzeichens im Foto, Grad der Überblendung und die maximale Größe, die das Wasserzeichen im Gesamtgrößenkontext des Fotos einnehmen darf, bestimmt werden.
# lib/paperclip_proccessors/watermark.rb
module Paperclip
# Handles watermarking images that are uploaded.
class Watermark < Processor
attr_accessor :current_geometry, :whiny, :watermark_path, :watermark_position, :watermark_ratio, :watermark_dissolve
# Adds a watermark object (+watermark_path+) to the +file+ given.
# It creates a dissolved (+watermark_dissolve+) layer at (+watermark_position+) and
# will attempt to transform the watermark to fit a given ratio (+watermark_ratio+).
# Example: A +watermark_ratio+ of 25 with +watermark_position SouthEast will place the
# watermark to the left bottom of the given file and, if needed, shrinks it to maximum 25% of either
# width or height of the file given by keeping its aspect ratio. So it neither crops the watermark
# nor scales it up. For further positioning details see ImageMagick gravity option.
# Watermark creation will raise no errors unless +whiny+ is true (which it is, by default).
def initialize file, options = {}, attachment = nil
super
@file = file
@current_geometry = Geometry.from_file @file
@whiny = options[:whiny].nil? ? true : options[:whiny]
@current_format = File.extname(@file.path)
@basename = File.basename(@file.path, @current_format)
@watermark_path = options[:watermark_path] || File.join(RAILS_ROOT, 'public', 'images', 'watermark.png')
@watermark_position = options[:watermark_position] || 'SouthEast'
@watermark_dissolve = options[:watermark_dissolve] || 30
@watermark_ratio = options[:watermark_ratio] || 25
end
# Performs the composition of the +file+ with the watermark. Returns the Tempfile that contains the new image.
def make
src = @file
dst = Tempfile.new(@basename)
dst.binmode
command = <<-end_command
#{transformation_command}
"#{File.expand_path(src.path)}[0]"
"#{File.expand_path(dst.path)}"
end_command
begin
success = Paperclip.run('composite', command.gsub(/\s+/, ' '))
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the watermark for #{@basename}" if @whiny
end
dst
end
# Returns the command ImageMagick's +composite+ needs to add the watermark to the image.
def transformation_command
watermark_max_width = (@current_geometry.width.to_i * (@watermark_ratio.to_f / 100)).to_i
watermark_max_height = (@current_geometry.height.to_i * (@watermark_ratio.to_f / 100)).to_i
trans = " -dissolve #{@watermark_dissolve} -gravity #{@watermark_position} \\( '#{@watermark_path}' -resize #{watermark_max_width}x#{watermark_max_height} \\) "
end
end
end
Seit gut 2 Wochen läuft die DT 125. Um TÜV zu bekommen, sind aber noch allerhand Kleinigkeiten notwendig, die ich nach und nach am Wochenende und nach Feierabend erledige. Nachdem ich heute die verbogene Soziusfußrastenaufnahme gerichtet und die Universalrasten mit dem Dremel passend geschliffen habe, war die Lichtschaltereinheit an der Reihe. Statt des teuren Yamaha-Originalersatzteils habe ich für ein Drittel des Preises eine gute und günstige Universalarmatur bei Götz Motorsport bekommen. Nachteil dieser Lösung war, dass die Verkabelung geändert werden musste. Also Lampemaske ab und 9 Japan-Steckverbindungen crimpen, sowie den Schaltplan studieren. 2 Stunden später war es dann soweit – Universalarmatur sitzt bombenfest und funktioniert.
Als nächstes habe ich den erst kürzlich erneuerten Benzinschlauch durch einen amtlichen Schlauch mit Gewebeumflechtung ersetzt und einen Benzinfilter in die Leitung gehängt. Die transparenten Plastikschläuche sind zwar sehr günstig – knicken jedoch ab, sitzen nicht richtig und verhärten schon nach kurzer Zeit.
Ärger mit dem TÜV
Die Plakette habe ich immer noch nicht. Hintergrund ist, dass ich zwar die Voraussetzung für die Eintragung der Leistungssteigerung auf 24 PS (Teilegutachten, Auslaßsteuerung modifiziert, Trichter zur Auslaßreduzierung entfernt) erfülle, jedoch noch ein paar Entscheidungen ausstehen.
Änderung der Übersetzung
Ich habe den neuen Kettensatz mit kürzerer Übersetzung gekauft. Original hat das Ritzel 16 und das Kettenrad 57 Zähne – ändern möchte ich das Ritzel um einen Zahn auf 15. Die Änderung des Übersetzungverhältnisses von 3,5625 auf 3,8 beträge damit 1,06%. Inwiefern die Information stimmt, dass ein bis zu 8% vom Original abweichendes Übersetzungsverhältnis eintragungsfähig ist, weiß ich nicht – aber dass mit meiner vergleichbare Änderungen defacto eingetragen werden, zeigt ein Fahrzeugschein einer Honda Transalp, den ich im Internet gefunden habe. Der TÜV Nord in Dortmund-Dorstfeld weigert sich jedoch standhaft. Eine Geräusch- und Abgasmessung würde man nicht vornehmen, ich müsse schon ein Teilegutachten vorlegen. Auf hoher See, vor Gericht und beim TÜV ist man eben in Gottes Hand. Also suche ich weiter, bis ich einen gnädigen TÜV-Prüfer gefunden habe.
Athena Auspuffanlage und offener Luftfilter
Zugegeben – die Athena Auspuffanlage verursacht ab Resonanzdrehzahl zusammen mit dem offenen Luftfilter einen infernalischen Höllenlärm (107 dB(A)). Und obwohl dieser „Lärm“ wie Musik in meinen Ohren ist, habe ich Verständnis für meine Mitmenschen. Zum Beispiel für diejenigen, die während einer Motorradtour hinter oder neben mir herfahren könnten. Und erst Recht für unbeteiligte Dritte, deren Gehör nicht durch einen Integralhelm geschützt wird. Aber – die Lautstärke hängt immer davon ab, wie man fährt. Und ich bin der Meinung, dass meine Vernunft in dieser Sache genügt, um den Spaß verantwortungsvoll zu dosieren. Da diese Vernunft bei mir aber auch erst mit der Zeit kam und ich zudem jeden Sommer die saisonalen Sonntagsfahrer unmittelbar beim Tiefflug durch dichtbesiedelte Wohngebiete erlebe, verstehe ich, dass es gesetzliche Rahmenbedingungen geben muß. Und damit meine ich nicht jene Radikalmaßnahmen wie Streckenverbote, sondern die Regelung von Leistungsdaten im Rahmen einer homologierten Betriebserlaubnis.
Als Besitzer der „Fahrerlaubnisklasse A unbeschränkt“ ist die durch die Athena-Komplettanlage bedingte Mehrleistung von rund 6 PS irrelevant, denn theoretisch dürfte ich auch Marschflugkörper mit Straßenzulassung fahren. Meiner Versicherung hingegen ist das wiederum nicht egal – denn im Falle eines Unfalls wird sie vermutlich argumentieren, dass u.a. diese (uneingetragene) Mehrleistung, die zum Erlöschen der Betriebserlaubnis führt, unfallursächlich gewesen sein könnte und ich somit in Regress genommen werden könne. Wenn man es ganz genau nimmt, ist das Fahrzeug aufgrund solcher Modifikationen ohne Versicherungsschutz. Und was das im Falle eines Personenschadens bedeutet, möchte ich mir erst gar nicht ausmalen. Ganz abgesehen von eventuellen strafrechtlichen Konsequenzen in diesem Zusammenhang.
Zusammenfassend kann man daher sagen, dass nicht nur die Lautstärke, sondern eben auch die Mehrleistung (inklusive der dadurch bedingten Auswirkungen auf den Rest des Fahrzeugs) und eventuell veränderte Abgaswerte einer Abnahme durch einen Sachverständigen bedürfen. Die Frage ist nur, ob ich einen bezahlbaren Sachverständigen finde, der sich auf eine Prüfung einlassen würde. Und ob ich die Auspuffanlage mit Dämmwolle o.ä. zumindest in den gesetzlichen Grenzbereich bringen kann, sodass ich auch bei einer Kontrolle durch die Polizei nebst amtlicher Geräuschmessung maximal 5% Abweichung von den eingetragenen Lautstärkewerten erreiche. Denn solange das nicht der Fall ist, werde ich mich wohl oder übel an die Originalauspuffanlage mit dem uncharmantem Leistungs-/Geräuschcharakter einer Nähmaschine abfinden müssen.
Die DT 125 läuft. Nachdem ich heute den gereinigten Vergaser verbaut und die letzten Teile angebracht habe, bescherte mir direkt der erste Kick die für einen 2-Takter beim Kaltstart typische, dunkelblaue Abgaswolke. Die erste (kurze) Probefahrt beschränkte sich mangels Zulassung auf die Strecke zwischen Parkplatz und Tiefgarage des Supermarktes nebenan – aber Spaß hat es trotzdem gemacht. Weniger Spaß hingegen macht der Anblick des Motorrads – wenn ich ganz ehrlich bin, dann ist das schon eine stinkende Dreckskarre. Auch vom Sound her reicht es gerade mal für das Prädikat „muffige Zwiebacksäge“. Aber so schnell gebe ich nicht auf. Als nächstes besorge ich mir von Götz Motorsport noch weitere Ersatzteile, die sich als defekt herausgestellt haben, verbaue diese und danach geht es dann erstmal zum TÜV.
Notwendige Ersatzteile
Vernünftigen Benzinschlauch und Filter
Linke Lenkerarmatur (Fernlichtschalter defekt)
Bremsscheibenabdeckung (Braucht man nicht, sagt der Götz-Kundenberater)
Kettensatz (25% Rabatt bei Götz – bei dem Preis ist „Haben besser als Brauchen“)
Neue (Carbon-)Membranblättchen (Machen mehr Probleme als Freude für die Straße, sagt der Götz-Kundenberater)
Optische Aufwertung
schwarze Verkleidungsschrauben
Sitzbankbezug (hat meine Freundin beanstandet, weil hinten aufgerissen)
Soziusfußrasten
Wie geht es weiter?
Gemischaufbereitung einstellen
Reifen aufziehen lassen
Ersatzteile verbauen
TÜV
Tank (selbst)lackieren
Probefahrt und eventuell weitere Maßnahmen planen
Nachdem ich beim TÜV war, schaue ich mir nochmal Auspuffanlage, Luftfilter und Vergaser an… irgendwie lässt mir das so keine Ruhe.
Nachtrag 25.11.2009, 18:54 Uhr:
Nach der kurzen Probefahrt und einem eingehenden Gespräch mit einem Zweiradmechaniker (der auch TÜV-Abnahmen macht) hab ich es trotz aller guten Vorsätze doch getan. Ich habe einen offenen Luftfilter und eine komplette Athena Auspuffanlage gekauft. Nach der HU/AU am Montag beantrage ich Zulassungspapiere und lasse die Leistungssteigerung (21-24 PS) eintragen.
Knapp 4 Wochen sind nun vergangen, seitdem ich das letzte Mal in der Garage an der DT 125 geschraubt habe. In der Zwischenzeit ist ein Paket mit Ersatzteilen von Polo eingetroffen, neue Continental TKC 80 Twinduro Reifen stehen zum Aufziehen bereit und der Vergaser ist auch generalüberholt. Trotz des schlechten Wetters* habe ich mich daher knapp 3 Stunden mit dem Einbau der Neuteile und dem Zusammenbau der gereinigten Einzelteile befasst.
Die alten Griffgummis konnte man prima mit einem Cutter aufschlitzen, entfernen und durch die neuen ersetzen. Die neuen Brems-/Kupplungshebel haben auch einwandfrei gepasst und machen einen soliden Eindruck. Nachdem ich Öl- und Kühlwassertank verbaut, neue Schläuche gezogen und alte Schrauben ausgetauscht habe, sieht das Motorrad schon wieder ganz passabel aus. Bei der Montage des Auspuffs ist mir aufgefallen, dass das Gewinde für die Befestigung des Krümmerendstücks kurz vor dem Endschalldämpfer von einer abgerissenen Schraube blockiert wird – genauso wie die Befestigung der Heckverkleidung auf der rechten Seite. Also Akkubohrer und einen kleinen Metallbohrer geschnappt, Schrauben ausgebohrt, Metallreste entfernt und Gewinde nachgeschnitten. Danach habe ich die Schwinge abgeschmiert, Bowdenzüge mit WD40 geölt und die neue Zündkerze eingesetzt. Letzeres natürlich mit Kupferpaste auf dem Gewinde – wenn schon penibel, dann auch richtig.
Wie geht es weiter?
Vergaser einbauen und Gemischaufbereitung einstellen
Wartungsarbeiten rund um die Bremsanlage – danach entscheiden, ob die Bremsscheiben erneuert werden müssen
Reinigen und Fetten der Antriebskomponenten – danach entscheiden, ob Kettenblatt/Ritzel/Kette erneuert werden müssen
Betriebsflüssigkeiten wechseln
Reifen aufziehen lassen
Tank (selbst) lackieren
Soziusfußrasten reparieren
Fernlichtschalter ersetzen
TÜV, Leistungssteigerung & Co.
Nachdem die erste Euphorie verflogen ist, habe ich mich nun auch emotional auf den Einsatz der DT 125 als reines Wintermotorrad eingestellt. Es war schon ein bißchen Disziplin nötig, um nicht doch die ein oder andere Investition aus Leidenschaft zu tätigen. Mittlerweile sehe ich aber auch das Thema Leistungssteigerung rational – ich werde nach Erledigung der letzten Arbeiten erstmal den Segen vom TÜV einholen und die DT 125 eine Zeit lang fahren. Danach entscheide ich, inwiefern Optimierungsbedarf besteht. Mein Kopf sagt mir, dass das jetzige Setup (110km/h und knapp 17 PS) für den Winter und Fahrzeugtyp absolut ausreichend ist und ich mir die Kosten und Nerven in Bezug auf die Eintragung einer Leistungssteigerung auf mehr als 24 PS sparen sollte. Das Argument, die dadurch gesparten Kosten in mein Café-Racer Projekt investieren zu können, hab ebenfalls dazu geführt, dass ich die Erneuerung von Bremsscheiben, Kettenblatt, Ritzel und Primärkette nochmal kritisch betrachten werde. Solange der Zustand die Betriebssicherheit und Zuverlässigkeit im Rahmen des Einsatzwecks nicht gefährdet, verzichte ich darauf.
* Leider habe ich in der Garage derzeit weder Licht noch Heizung – Katalytofen mit entsprechender Abluftvorrichtung und Anschluß an das Stromnetz stehen aber ganz oben auf der Wunschliste.
Ich habe Urlaub. Das Wetter ist schlecht, es regnet und in der Garage ist es dunkel, kalt und ungemütlich. Genau richtig, um in aller Ruhe die Einkaufsliste für das Wintermotorrad-Projekt mit gespitztem Bleistift zu kontrollieren, Preise zu vergleichen und das ein oder andere (nicht bei Polo, Louis oder Götz-Motorsport erhältliche Ersatzteil) beim Yamaha-Händler zu bestellen. Besonders hilfreich dabei ist der Original Yamaha Teilekatalog, der mir soeben von einem Forumsmitglied des DT125-Forums zur Verfügung gestellt worden ist. Den Katalog (2,3 MB) könnt ihr euch hier herunterladen.
Nach 5 Monaten Wartezeit hielt ich endlich ein Paket aus Viersen in der Hand. Nach dem Öffnen war klar: Das Warten hat sich gelohnt.
Wulf Peppmöller, für seine Handwerkskunst bekannt und längst kein Geheimtipp mehr, fertigt individuelle Einzelstücke nach Maß aus VA an. Für mein Café-Racer-Projekt sollte es ein Nachbau im Stil der Norton Commando werden. Dank eingeschlagener Typenkennzeichnung, des Kammersystems und der Verwendung mit einem Rahmen aus dem Jahre 1979 ist sogar eine TÜV-Eintragung möglich. Wulf Peppmöller gibt zudem eine Garantie auf Passgenauigkeit und Rostfreiheit. Mit 360,00 € für Endschalldämpfer, Krümmer und Flansch liegt man natürlich das ca. 7-fache über den optisch ähnlichen Krawalltüten, die es bei Louis & Co. gibt – aber ich denke nicht, dass ich den Unterschied in Bezug auf Rostanfälligkeit, Verarbeitung, Passgenauigkeit und vorallem „vernünftigem Sound“ und Leistung näher erläutern muß.
Jetzt stellt sich abschließend die Frage, ob ich den Auspuff in der Edelstahloptik belasse, oder…
(Schwarz-)Verchromen?
Binden?
In dieser Hinsicht bin ich mir noch unsicher, daher stelle ich die Entscheidung erstmal zurück. Nächster Meilenstein im Projekt ist ohnehin erstmal die Motorrevision und im Anschluß das Rolling Chassis.
Wir alle kennen das übliche Anmelde-Prozedere bei praktisch allen Web-Applikationen, die mit individuellen Benutzerdaten arbeiten. Am Anfang steht die Registrierung, bei der wir mindestens einen (hoffentlich noch verfügbaren) Benutzernamen, ein (möglichst sicheres) Passwort und unsere E-Mail-Adresse angeben müssen. An letztere wird üblicherweise im Anschluß ein Bestätigungslink versendet, um sicherzustellen, dass wir auch Inhaber des Postfaches sind. Nach erfolgreicher Bestätigung steht dann einem ersten Login-Versuch nichts mehr im Wege: anhand der von uns vergebenen Kombination aus Benutzername und Passwort können wir uns jederzeit an der Web-Applikation anmelden und auf persönliche Daten und individuelle Einstellungen zugreifen.
Das, was für den Benutzer der Web-Applikation mittlerweile obligatorisch ist, stellt den Entwickler derselben jedoch vor eine Vielzahl von zu bewältigenden Aufgaben und bringt nicht zuletzt auch eine Menge Pflichten mit sich. Denn immerhin obliegt es der Sorgfalt des Entwicklers, dass vertrauliche Daten vor unbefugtem Zugriff Dritter geschützt sind und im Falle einer Kompromittierung der Web-Applikation der Schaden so gering wie möglich gehalten wird.
Das RESTful Authentication Plugin des Ruby on Rails Core Members Rick Olson galt lange Zeit als etablierter De-facto-Standard zur Implementierung eines Authentifizierungs-Features. RESTful Authentication besteht dabei im wesentlichen aus einem Generator nebst Templates und einem Authentifizierungsmodul. Die vermeintlichen Vorteile dieses Ansatzes sind nicht nur die Erzeugung aller relevanten Komponenten innerhalb einer Rails-Applikation, sondern auch die Integration des Authentifizierungssystems in zentrale Bestandteile wie Routing, Helper & Co. Diese vermeintlichen Vorteile kehren sich jedoch schnell zu erheblichen Nachteilen um, sofern man selbst Anpassungen an der Authentifizierungsroutine vornehmen möchte. Spätestens dann ist man mit den über die Applikation verteilten Code-Fragmenten konfrontiert, sodass unterm Strich fast schon mehr Aufwand entsteht, als eine vollständige Implementierung „from scratch“ in Anspruch nähme.
Authlogic und der Grund, warum wir Rails lieben
Einer der vielen Gründe, die für die Entwicklung mit Ruby on Rails sprechen, ist die vorgegebene, klare (MVC-)Struktur einer typischen Applikation und die Integration bewährter Entwicklungsparadigmen. Besonders das „Convention over Configuration“-Paradigma schafft eine sinnvolle Grundlage, sodass sich der Entwickler auf das Wesentliche der Applikation konzentrieren kann. Authlogic folgt diesen Grundgedanken des „RailsWay“ und macht sie zu seinen Erfolgsprinzipien.
„A code example can replace a thousand words“ (Ben Johnson)
Ausgehend von einem frisch erzeugten Applikationsgrundgerüst muß zunächst das Plugin installiert werden:
Für diesen Artikel verwende ich der Einfachheit halber die Installation des Plugins. Alternativ kann aber natürlich auch das RubyGem verwendet werden:
$ gem sources -a http://gems.github.com
$ sudo gem install binarylogic-authlogic
Bei der Verwendung des RubyGems empfiehlt es sich zudem, in der config/environment.rb eine entsprechende Abhängigkeit zu definieren:
config.gem ‘binarylogic-authlogic’
Im Anschluß können wir bereits direkt ein Model erzeugen, welches von der Klasse Authlogic::Session::Base abgeleitet wird.
$ script/generate session user_session
Das besondere an diesem Model ist, dass es sich um das gesamte Session-Management kümmert und wie ein ActiveRecord-Model verwendet werden kann. Uns hindert also nichts daran, einen REST-konformen Controller (Listing 1) zu erzeugen, wie wir es auch für ein ActiveRecord-Model tun würden.
$ script/generate controller user_sessions
Listing 1
class UserSessionsController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
@user_session = UserSession.new
end
def create
@user_session = UserSession.new(params[:user_session])
if @user_session.save
flash[:notice] = 'Hi, you are logged in!'
redirect_back_or_default account_url
else
render :new
end
end
def destroy
current_user_session.destroy
flash[:notice] = 'Goodbye, you are logged out!'
redirect_back_or_default new_user_session_url
end
end
Das Routing für diesen Controller wird ebenfalls, wie gewohnt, in der config/routes.rb definiert.
map.resources :user_sessions
Die Views sind äußerlich nicht von denen zu unterscheiden, die via Controller mit ActiveRecord-Objekten interagieren (Listing 2). Im Gegensatz zu RESTful Authentication stehen daher auch die gewohnten Methoden zur Darstellung von etwaigen Fehlern zur Verfügung.
Nützliche (und für dieses Beispiel auch notwendige) Methoden, z.B. das sich selbst erklärende redirect_back_of_default, werden zentral im ApplicationController definiert (Listing 3).
Listing 3
class ApplicationController < ActionController::Base
protect_from_forgery
helper :all
helper_method :logged_in?, :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
protected
def store_location
session[:return_to] = request.request_uri
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
def current_user
@current_user ||= current_user_session and current_user_session.user
end
def current_user_session
@current_user_session ||= UserSession.find
end
def logged_in?
not current_user.nil?
end
def require_user
unless logged_in?
store_location
flash[:notice] = 'Please login in to access this page!'
redirect_to new_user_session_url
return false
end
end
def require_no_user
if logged_in?
store_location
flash[:notice] = 'Please logout to access this page!'
redirect_to account_url
return false
end
end
end
Damit ein Benutzer nach einem erfolgreichen Login auf seine persönliche Seite weitergeleitet wird, definieren wir noch einen AccountsController (Listing 4) und eine Route für die dazugehörige, singuläre Resource in der config/routes.rb:
class AccountsController < ApplicationController
before_filter :require_user
def show
@account = current_user
end
end
Der AccountsController kann später natürlich noch um weitere Methoden zum Bearbeiten des Profiles erweitert werden. Um jedoch unbefugten Zugriff auf fremde Benutzerdaten zu verhindern, sollte das Basisobjekt ausschließlich mittels der Methode current_user geladen werden. Es sei denn, man implementiert z.B. eine rollenbasierte Autorisierung, bei der ein Zugriff auf alle Benutzerobjekte durch Superuser vorgesehen ist. Wie das funktioniert, schreibe ich in einer der nächsten Ausgaben der RailsWay.
Bitte registrieren Sie sich!
Eine Benutzerauthentifizierung ist jedoch nichts ohne die zugrundeliegenden Benutzer. Und diese sollen sich selbstständig registrieren können. Dazu erzeugen wir zunächst das Model User:
$ script/generate model user
Für eine sinnvolle Minimalkonfiguration, bei der wir die E-Mail-Adresse für den Login verwenden, genügt es, in der dazugehörigen Migration die Attribute email, crypted_password und persistence_token zu definieren (Listing 5). Es sei jedoch bereits jetzt schon erwähnt, dass Authlogic eine Vielzahl von „MagicColumns“ beinhaltet, auf die wir später noch zurückkommen werden.
Listing 5
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :email, :null => false
t.string :crypted_password, :null => false
t.string :persistence_token, :null => false
t.timestamps
end
end
def self.down
drop_table :users
end
end
Wir führen nun die Migration aus:
$ rake db:migrate
…und teilen dem Model User mit, dass wir es zur Authentifizierung von Benutzerdaten verwenden möchten:
class User < ActiveRecord::Base
acts_as_authentic
end
Technisch gesehen haben wir bereits jetzt eine vollständige Benutzerauthentifizierung. Beeindruckend einfach, oder? Damit sich Benutzer nun selbstständig registrieren können, müssen wir natürlich auch ein entsprechendes Registrierungsformular bereitstellen. Wir möchten zudem, dass ein Benutzer sich erst dann einloggen kann, wenn er durch Anklicken eines Aktivierungsschlüssels bestätigt hat, dass die zur Registrierung verwendete E-Mail-Adresse auch wirklich ihm gehört. Dieses Bestätigungsprozedere bilden wir in einem endlichen Automaten ab.
Oh, Du Zustandmaschine, Du endlicher Automat!
Beim Schlagwort „Endlicher Automat“ werden bei dem ein oder anderen Leser mit akademischem Hintergrund Erinnerungen an Vorlesung zur Theoretischen Informatik und Automatentheorie wach. Ein endlicher Automat („engl. Finite State Machine“), um den es uns nun geht, beschreibt ein Verhalten bestehend aus Zuständen, Zustandsübergängen und Aktionen. Am Beispiel der Benutzerregistrierung erklärt, gibt es zwei Zustände: bestätigt (confirmed) und unbestätigt (pending). Um nun vom Zustand unbestätigt in den Zustand bestätigt zu wechseln (Zustandsübergang) ist eine Aktion notwendig – nämlich das Klicken des per E-Mail zugestellten Aktivierungsschlüssels. Aber keine Panik – so kompliziert es für manchen auch klingen mag, so einfach kann man es Dank des acts_as_state_machine-Plugins (mittlerweile umbenannt in AASM) in einer Rails-Applikation anwenden. Wir installieren das Plugin wie folgt:
Alternativ kann auch hier wieder das RubyGem verwendet werden:
$ sudo gem install rubyist-aasm
…welches dann wiederum konsequenterweise auch als Abhängigkeit in config/environment.rb definiert werden sollte:
config.gem 'rubyist-aasm'
AASM speichert den aktuellen Status eines Objektes in der Datenbank. Dazu erweitern wir das dem Automaten zugrundeliegende Model User um das Attribut status:
Bevor wir die Migration anwenden, sollten wir noch einen Standardwert für das Attribut definieren. Das ist zwar nicht zwingend notwendig – gehört aber meines Erachtens nach zu einem sauberen Stil und trägt zur Wahrung der logischen Integrität der Daten bei.
class AddStatusToUsers < ActiveRecord::Migration
def self.up
add_column :users, :status, :string, :default => 'pending'
end
def self.down
remove_column :users, :status
end
end
$ rake db:migrate
Die eigentliche Definition des Automaten erfolgt im Model User (Listing 6).
Listing 6
class User < ActiveRecord::Base
acts_as_authentic
include AASM
attr_protected :status
aasm_column :status
aasm_initial_state :pending
aasm_state :pending
aasm_state :confirmed
aasm_event :confirm do
transitions :to => :confirmed, :from => :pending
end
end
assm_state definiert die beiden Zustände, die eine Registrierung haben kann. assm_event erzeugt automatisch die Instanzmethode :confirm!, die wir später aufrufen, wenn ein Benutzer den Bestätigungslink aufruft, um den gewünschten Zustandsübergang auszulösen. An dieser Stelle möchte ich auf die vielen zusätzlichen Funktionen des AASM-Plugins hinweisen, die u.a. neben der automatischen Bereitstellungen von zustandsspezifischen named_scopes auch die Ausführung bestimmter Aktionen beim Verlassen oder Eintreten eines bestimmten Zustands ermöglichen. Eine Vielzahl von zustandsorientierten Vorgängen lässt sich so in Web-Applikation hervorragend abbilden und sorgt auf diese Weise für gut lesbaren und sicheren Code.
„MagicColumns“, „MagicStates“ – nur Harry Potter fehlt
Authlogic verfügt über sog. „MagicColumns“ und „MagicStates“ – letzere sorgen auf magische Weise dafür, dass Authlogic bereits jetzt ohne unser Zutun mit dem endlichen Automaten zusammenarbeitet. Authlogic fragt bei jedem Login die Methoden active?, approved? und confirmed? ab – sofern sie existieren. Durch die Einführung unseres endlichen Automaten mit dem Zustand confirmed haben wir automatisch auch die Instanzmethode confirmed? erhalten. Bei unbestätigten Registrierungen gibt diese den Wert false zurück und veranlasst Authlogic, eine passende Fehlermeldung anzuzeigen. So wird verhindert, dass sich ein Benutzer ohne vorherige Bestätigung mit seinen Zugangsdaten anmelden kann.
Für die Erzeugung eines Aktivierungsschlüssels verwenden wir die „MagicColumn“ perishable_token, was übersetzt sinngemäß mit „temporäres Merkmal“ übersetzt werden kann. Temporär deswegen, weil Authlogic dieses Attribut bei jeder Aktualisierung des Benutzerobjektes verändert. Die Erweiterung erfolgt wie gewohnt mit einer Migration:
Vor Anwendung dieser Migration passen wir auch hier die Migration noch einmal an, sodass das Vorhandensein eines Wertes für das neue Attribut forciert wird.
class AddPerishableTokenToUsers < ActiveRecord::Migration
def self.up
add_column :users, :perishable_token, :string, :null => :false
end
def self.down
remove_column :users, :perishable_token
end
end
$ rake db:migrate
Da es sich bei der Benutzerregistrierung zunächst um nichts anderes als die Erzeugung und Speicherung eines ActiveRecord-Objektes handelt, können wir wieder zu bekannten Mitteln greifen. Ein REST-konformer Controller (Listing 7):
$ script/generate controller users
…mit entsprechendem Eintrag in der config/routes.rb:
map.resources :users
…und einem dazugehörigen Formular (Listing 8) reichen schon aus – denn um die Validierung kümmert sich in der Standardkonfiguration bereits Authlogic.
Listing 7
class UsersController < ApplicationController
before_filter :require_no_user
def new
@user = User.new
end
def create
@user = User.new(params[:user])
if @user.save
flash[:notice] = 'Thanks for signing up. We just sent you a confirmation email!'
redirect_back_or_default root_url
else
render :new
end
end
def confirm
@user = User.pending.find_using_perishable_token(params[:confirmation_code])
unless @user
flash[:error] = 'Sorry, your activation code is invalid!'
else
@user.confirm!
@user.reset_perishable_token!
flash[:notice] = 'Great, your registration has been confirmed successfully!'
end
redirect_back_or_default root_url
end
end
Um die Zustellung der Bestätigungsmail müssen wir uns allerdings selbst kümmern. Dazu erzeugen wir zunächst die ActionMailer-Klasse UserMailer (Listing 9):
$ script/generate mailer user_mailer
Listing 9
class UserMailer < ActionMailer::Base
include ActionController::UrlWriter
def confirmation_code(user)
from 'Foo Bar '
recipients user.email
subject 'Please confirm your registration'
body :user => user
sent_on Time.now
end
end
Wichtig ist dabei, dass wir das Modul ActionController::UrlWriter inkludieren und eine Domain in config/environment.rb als Grundlage für die UrlWriter-Methoden konfigurieren, damit wir den Bestätigungslink für das Mailer-Template (Listing 10) erzeugen können:
class ActionMailer::Base
default_url_options[:host] = 'example.org'
end
Diese Notwendigkeit lässt sich mit der Tatsache erklären, dass der Prozess des E-Mail-Versands in einer MVC-Applikation keinen Zugriff auf einen Request (ActionController::AbstractRequest) hat, um Informationen über den aktuellen Hostnamen der Applikation zu bekommen. Wer möchte, kann daher für die verschiedenen Environments die passenden URLs in den dazugehörigen, separaten Konfigurationsdateien (z.B. config/environments/production.rb) konfigurieren.
Dem aufmerksamen Leser ist sicherlich die Methode confirm im UsersController (Listing 7) aufgefallen, die ein Benutzerobjekt anhand des Attributs perishable_token aus dem Pool der unbestätigten Benutzer lädt. Damit ein Benutzer also später nach Klicken des Aktivierungslinks auf diese Methode geroutet wird, benötigten wir noch einen passenden Eintrag in der config/routes.rb:
Bleibt nun noch der Versand der Bestätigungs-E-Mail nach Erzeugung eines Benutzerobjektes. Um diesen auszulösen, bedienen wir uns eines ActiveRecord-Observers (Listing 11):
$ script/generate observer user
Listing 11
class UserObserver < ActiveRecord::Observer
def after_create(user)
UserMailer.deliver_confirmation_code(user) if user.pending?
end
end
Der UserObserver muss wie üblich in der config/environment.rb aktiviert werden:
config.active_record.observers = :user_observer
Wenn wir nun unsere Applikation starten und alles richtig gemacht haben, können sich neue Benutzer unter dem Menüpunkt Register (Abb. 1) selbstständig registrieren, bekommen nach Registrierung eine Bestätigungs-E-Mail (Abb. 2) und können sich nach erfolgreicher Aktivierung (Abb. 3) einloggen (Abb. 4), um ihre persönlichen Daten einzusehen (Abb. 5).
Abb.1
Abb.2
Abb.3
Abb.4
Abb.5
Where Is My Mind?
Twitter, YouTube, Flickr & Co. – wenn es schon schwer fällt, eine vollständige Liste aller etablierten Onlinedienste wiederzugeben, dann ist es kein Wunder, dass der ein oder andere Benutzer gelegentlich Schwierigkeiten hat, sich an die richtige Kombination aus Benutzername und Passwort für die jeweilige Plattform zu erinnern. Dieses Problem wird zudem dadurch verstärkt, dass einige Applikationen bei der Anforderung eines vergessenen Passworts (phpBB & Co.) selbst ein zufälliges Passwort generieren und es per E-Mail versenden. Genauso zweifelhaft wie das Versenden jeglicher Zugangsdaten per E-Mail in Bezug auf Sicherheit ist, so wenig benutzerfreundlich sind auch die meisten Mechanismen, mit denen man diesen Vorgang zunächst auslösen muß.
Dank der Freiheit, die Authlogic bietet, kann man selbst entscheiden, wie man diesem Problem begegnen möchte. Ich habe sehr gute Erfahrung damit gemacht, einem Benutzer an seine E-Mail-Adresse einen Link zu senden, mittels dessen er sein Passwort selbst neu vergeben kann. Und genau das werden wir jetzt implementieren, indem wir zunächst einen PasswortResetRequestsController (Listing 12) generieren:
class PasswordResetRequestsController < ApplicationController
before_filter :require_no_user
def create
@user = User.find_by_email(params[:email])
if @user
@user.reset_perishable_token!
@user.deliver_password_reset_instructions
flash[:notice] = 'Please check your email to get further information on resetting your password'
redirect_to root_url
else
flash[:notice] = 'Sorry, there is no user with that email address'
render :new
end
end
end
Auch hier benötigen wir wieder ein Formular (Listing 13), damit der Benutzer seine E-Mail-Adresse angeben kann.
Zum Versand der E-Mail erweitern wir den bestehenden UserMailer um folgende Methode…
def password_reset_instructions(user)
from 'Foo Bar '
recipients user.email
subject 'How to reset your password'
body :user => user
sent_on Time.now
end
…und fügen folgenden Inhalt für das zugehörige Template in die Datei app/views/user_mailer/password_reset_instructions.erb ein:
Hi,
you may reset your password by clicking the follwing link:
< %= edit_password_reset_request_url @user.perishable_token %>
Thanks!
Nun kann der Benutzer nach Klicken des Reset-Links eines neues Passwort vergeben (Listing 14). Und damit dieser Wunsch nicht unerfüllt bleibt, müssen wir zum Abschluß im Controller natürlich noch die Methoden edit und update implementieren (Listing 15). Nach erfolgreicher Aktualisierung des Passworts ist der Benutzer automatisch eingeloggt und wird auf seine persönliche Seiten weitergeleitet. Bequemer geht es nicht.
# app/controllers/password_reset_requests.rb
def edit
@user = User.find_by_perishable_token(params[:id])
end
def update
@user = User.find_by_perishable_token(params[:id])
unless params[:user][:password].blank?
@user.password = params[:user][:password]
@user.password_confirmation = params[:user][:password_confirmation]
if @user.save
flash[:notice] = 'Your password has been updated. Your are logged in.'
redirect_to account_url
else
render :edit
end
else
flash[:error] = 'Please enter your new password!'
render :edit
end
end
OpenID, OAuth & Co – die Authlogic Add-Ons
Es gibt verschiedene Gründe, keinen eigenen Datensatz zur Authentifizierung etablieren zu wollen. Vielleicht möchte man auf eine bestehende LDAP-Infrastruktur aufsetzen, im Rahmen eines Lazy-Registration-Patterns eine Alternative in Form von OpenID zur Verfügung stellen oder aber eine der zahlreichen Authentifizierungs-APIs der Social-Networks (z.B. Facebook Connect, Twitter’s OAuth) verwenden. Authlogic ermöglicht diese und zukünftige Erweiterungen in Form von Add-Ons, die auf einer Public API aufsetzen.
Authlogic ist Kosmopolit!
Authlogic unterstützt die Rails Internationalization-API vollständig. Für diesen Artikel habe ich mich aus Gründen der Übersichtlichkeit gegen die Übersetzung ins Deutsche entschieden. Der Vollständigkeit halber möchte ich dennoch die verfügbaren Schlüssel zur Übersetzung, so wie sie im Plugin-Ordner unter lib/authlogic/i18n.rb beschrieben sind, aufzählen:
authlogic:
error_messages:
login_blank: can not be blank
login_not_found: is not valid
login_invalid: should use only letters, numbers, spaces, and .-_@ please.
consecutive_failed_logins_limit_exceeded: Consecutive failed logins limit exceeded, account is disabled.
email_invalid: should look like an email address.
password_blank: can not be blank
password_invalid: is not valid
not_active: Your account is not active
not_confirmed: Your account is not confirmed
not_approved: Your account is not approved
no_authentication_details: You did not provide any details for authentication.
models:
user_session: UserSession (or whatever name you are using)
attributes:
user_session: (or whatever name you are using)
login: login
email: email
password: password
remember_me: remember me
Whatcha Gonna Do when they come for you?
Brute-Force, Session-Fixation & Co. gehören zum Standardrepertoire der bösen Jungs. Authlogic ist sich der Gefahren bewusst und stellt dem Entwickler probate Mittel gegen diese Angriffsmuster zu Verfügung. Eine Diskussion und nähere Erläuterung würde den Rahmen des Artikels endgültig sprengen, weswegen ich an dieser Stelle auf das Studium der Sources verweisen möchte. Besonders von Interesse sind dabei z.B. lib/session/brute_force_protection.rb und die Dateien in lib/authlogic/crypto_providers. In jedemfall empfehle ich aber, die zusätzliche „MagicColumn“ password_salt für unsere Beispielapplikation zu erzeugen – diese erschwert das Entschlüsseln der Passwörter für den Fall, dass ein Angreifer Zugriff auf die Datenbanktabelle users erhält. Ebenfalls einen Blick Wert ist das SslRequirement Plugin von David Heinemeier Hansson, um sensible Benutzerinformationen ausschließlich über eine verschlüsselte und verifizierte Verbindung zu übertragen.
„Wandel und Wechsel liebt, wer lebt.“ – ob Richard Wagner auch an dieser Weisheit festgehalten hätte, wenn er Software-Entwickler geworden wäre? Ein Wechsel einer (funktionierenden) Komponente innerhalb einer Applikation bringt oftmals erheblichen Aufwand und nicht selten auch Ärger mit sich, weshalb sich in dieser Hinsicht der gutgemeinte Ratschlag „Never change a running system“ schon eher etabliert hat. Wer doch den Schritt von restful_authentication zu Authlogic in einer produktiven Applikation wagen möchte, dem sei der Artikel des Authlogic-Urhebers Ben Johnson zu diesem Thema ans Herz gelegt.
Heute waren Luftfilterkasten, Kühlmittelausgleichsbehälter, Vergaser und das Heck an der Reihe. Beim Heck fiel mir gestern auf, dass die Blinker falsch herum montiert waren und ein bequemer Schrauber einfach die Kabel gekappt, vertauscht und mit Isolierband zusammengefriemelt hatte, damit die Belegung der Seiten wieder passt. Also Heck komplett entfernen, zerlegen, reinigen – und nach Zusammenbau die Anschlüße wieder korrekt belegen und mit ordentlichen, wetterfesten Japan-Steckverbindungen versehen. Dazu einfach mal ein paar Vorher-Nachher-Fotos.
Vorher:
Nachher:
Vorher:
Nachher:
Luftfilterkasten und Kühlmittelausgleichsbehälter liegen gerade, zusammen mit den restlichen Verkleidungsteilen, in Seifenlauge in der Badewanne. Bisher ist meine Freundin auch noch recht entspannt… daher hoffe ich, dass ich den ausgewaschenen Luftfilter zum vorsichtigen Trocknen über der Heizung in einem Stoffsäckchen aufhängen kann und der (leere, ausgedunstete) Vergaser ausnahmsweise auf dem Wohnzimmerglastisch zerlegt werden darf. Die Einzelteile wollte ich dann morgen zu Wiko Motorrad zwecks Reinigung im Ultraschallbad bringen.
Warten auf die Ersatzteile
Wenn nächste Woche die Ersatzteile bestellt und geliefert werden bzw. ich mir diese bei Polo, Louis, Hellweg & Co. besorgt habe, dann kann es mit den frisch gereinigten und überholten Teilen ans Zusammenbauen gehen.
Nachdem ich gestern mein zukünftiges Wintermotorrad erstmal auseinandergebaut habe, war heute Reinigung und Elektrik dran. Die Batterie hatte ich über Nacht am Ladegerät und heute morgen mit destilliertem Wasser aufgefüllt und durchgemessen – scheint soweit in Ordnung. Auch wenn eine neue YUASA-Batterie nur rund 15 € kostet, möchte ich so wenig wie möglich Geld in die DT investieren. Entsprechend viel Selbstdisziplin hab ich daher auch gestern Abend an den Tag legen müssen, als ich bei der Erstellung einer Einkaufsliste durchgerechnet habe, was denn zusätzlich eine neue Gianelli-Anlage, neue Verkleidung, Lenker usw. kosteten. Kurzum: Investition nur in das, was zur Sicherheit beiträgt, für zuverlässigen Betrieb notwendig ist oder schlichtweg kaputt ist. Optisches Tuning daher nur mit Wurzelbürste, NeverDull und viel Handarbeit.
Elektrik
Nachdem ich den kompletten Kabelbaum durchgemessen und einige Drähte erneuert sowie ein paar marode Japan-Steckverbindung ausgetauscht hatte, funktioniert nun sämtliche Elektrik einwandfrei. Die Blinker-Relais machen zwar noch ein seltsames Geräusch, aber tun ihren Dienst. Mit ein bißchen Kupferpaste an den richtigen Stellen des YPVS war auch beim Drehen des Zündschlüssels kein allzu auffälliges Surren mehr wahrzunehmen.
Rahmen / Verkleidung
Abgesehen von Unmengen an Sand, Öl, Dreck und seltsamen Aufklebern waren Rahmen und Verkleidung noch in gutem Zustand. Die meiste Arbeit habe ich nun hinter mir. Rahmen und Motor sind vom gröbsten Dreck befreit und während ich diesen Artikel schreibe, liegt der erste Teil der Verkleidung in der Badewanne in Seifenlauge.
Abgesehen von den üblichen Plastikkratzern wird diese am Ende sogar neuwertig aussehen. Mit ein bißchen Terpentin kriegt man auch die Rückstände des stellenweise vorhandenen, blauen Farbnebels weg.
Wie geht es weiter?
Vergaser im Ultraschallbad reinigen lassen, Dichtungen erneuern sind noch intakt
Zündkerze erneuern
Kraftstofffilter einbauen sind am Tank und Vergaser innenliegend vorhanden und noch intakt
Auspuff ausbrennen oder erneuern mit Kaltreiniger von Ölkohleablagerungen befreien
Kupplungs-/Bremshebel erneuern
Lenkergriffgummis erneuern
Fernlichtschalter ersetzen
Schrauben erneuern bzw. fehlende Schrauben ergänzen
Bremsscheibenabdeckungen erneuern
Bremssscheiben und Bremsbeläge erneuern
Bremmsleitungen prüfen – bei Beschädigung durch Stahlflex tauschen