Main

innoq.com Archives

30.01.08

Hell yeah! I'm done...

...with written tests for university!
Today i wrote my last and now I can fully concentrate on my diploma thesis (and innoq.com, too). By the way: innoq.com is running on Rails for almost 3 weeks now, and I think it's doing fine, isn't it, Stefan?

I think I'll have a beer right now...! Cheers!

20.12.07

AJAX calls with fallback

When you have a list of - say - articles (that’s what I have here) and you want to change the order the items appear on the fly(maybe the newest ones first, or alphabetic or maybe clockwise…you name it…), then you have two possibilities:
The old way by providing a link that sends a “normal” request to the server which returns the same site again, with the difference of an alternated order of articles.
In rails this is pretty basic stuff:

<%= link_to 'Oldest ones first', :action => "index",
:sort_string => "created_at ASC" %>

The index-action in the controller then makes nothing different than before besides doing a database-query with the altered order-statement. And returns the site. Here the excerpt from the controller:

def index
if params[:sort_string]
    sort_string = params[:sort_string]
else
    sort_string = "created_at DESC" # the default sort order
end

@articles = Article.find(:all, :order => sort_string)
...

And there is the new cool, web 2.0 way: performing an AJAX call to the controller and just update the section of the page that needs to be updated. Without having to refresh the whole site. There you go:

<%= link_to_remote 'Oldest ones first', :url => { :action => "index",
:sort_string => "created_at ASC" }, :method => :get %>

This doesn’t work without two more lines of code. But that’s done with the blink of an eye.
In the index action of the controller you need to respond to the javascript format by adding this:

... # excerpt
respond_to do |format|
    format.html # index.html.erb
    format.js # index.js.rjs THIS LINE IS ADDED
    format.xml  { render :xml => @articles }
end
... # excerpt

Further you need to create a file with name index.js.rjs* in /app/views/YOURCONTROLLER/. Add the following line of code to this file:

page.replace_html :list, :partial => "list"

…where :list is the id of your div-element that contains the list of articles.
:partial => “list” calls the _list.html.erb partial (which contains that specific div).

The list must be wrapped in a div-element and the whole thing goes into _list.html.erb!!!
So just copy the code of the list you already have to the partial file and wrap everything in a div with id “list”. Then call the partial from that point where you removed it with <%= render :partial => “list” %>.

And then you’re done. When you hit the link, the list refreshes without loading the whole site again.

But wouldn’t it be nice to make this feature always work, even for browsers that have JavaScript disabled? I think so, ‘cause it guarantees that this works without relying on an enabled JavaScript setting. And that’s super-easy. Just modify the link in the view this way:

<%= link_to_remote 'Oldest ones first', {:url => { :action => "index",
:sort_string => "created_at ASC" }, :method => :get }, :href =>
url_for(:action => "index", :sort_string => "created_at ASC") %>

The only thing I had to add was two braces and the stuff following :href =>.
If JavaScript is enabled, the AJAX call will be send and otherwise it works as a “normal” call. Nice, heh?!?

But as you can see, the parameters passed are duplicates. The :url and :href hashes are quite similar. To DRY** things up, just write a small helper-method in application_helper.rb, that does this duplication for you:

def link_to_remote_with_fallback(name, options = {}, html_options = {})
    html_options[:href] = url_for(options[:url])
    link_to_remote(name, options, html_options)
end

And BOOM! The code’s DRY again:

<%= link_to_remote_with_fallback 'Oldest ones first', :url =>
{ :action => "index", :sort_string => "created_at ASC" } ,
:method => :get %>

Credits: DRYing up linktoremote for degradable URLs

*index is the name of the action in the controller. So modify this to match your action.
**DRY - Don’t repeat yourself

15.12.07

Doing AJAX-links right in Rails 2.0

Ok, maybe this is a trivial topic, but as this was my first “real” AJAX experience in Rails 2.0, I spent some time solving this issue:

I wanted to sort a list of articles differently using AJAX. So in the appropriate action of the controller I added the format.js line:

respond_to do |format|
    format.html # index.html.erb
    format.js
    format.xml  { render :xml => @articles }
end

And then I created a new view template in the view folder belonging to that controller: index.js.erb.
This was my first fault! The correct file extension is .js.rjs! First this didn’t make sense to me, but I got some explanation here: http://www.railsforum.com/viewtopic.php?pid=47149

And then - in my index.js.rjs template - I wrote the following line:

page.replace_html :list, :partial => "list"

This replaces the HTML of the div list with a rendering of the partial _list.html.erb. I never had any doubt that this line was correct. ;-)
So far so good, but I got really ugly results clicking a link_to_remote link:

<%= link_to_remote 'Oldest Articles first', :update => 'list',
:url => { :action => "index", :sort_string => "created_at ASC" }, :method => :get %>

The result was something like:

try { Element.update ...

…followed by more strange ruby code, mixed with some of my list data in between, replacing the list-div. Here I expected the newly rendered, resorted list.

The problem was the :update => ‘list’ statement. That way, the code was not interpreted as JavaScript and got directly inserted into the div-tag. And as you can see, I specified to div to be updated in the page.replace_html call in index.js.rjs.

So, the correct call of link_to_remote would be without the :update parameter:

<%= link_to_remote 'Oldest Articles first', :url => { :action => "index",
:sort_string => "created_at ASC" }, :method => :get %>

Problem solved!

In short:

  1. the right file extension for responding to JavaScript is .js.rjs
  2. when using link_to_remote with page.replace_html don’t use the :update parameter

As I said above, this is not such a big problem when done correctly, but sometimes it’s an accumulation of 2 or 3 little issues that turn into a big one. At least for me.

22.11.07

The preview feature and more updates on re-coding innoq.com

So now finally another post concerning the “real” work - meaning the progress on re-coding innoq.com.

I’m still working on the part of maintaining all the articles on the site. To be precise I did a lot of thinking about and coding the articles preview-feature, i.e. when you write a new article or edit an existing one, you can see a preview when clicking the appropriate button.

What took a quite huge amount of time was gathering all the possible alternatives on how to implement that particular functionality. Will the preview be displayed on the same page? Will this be done with or without AJAX? Or should the preview open in a new window or in the same window on a new page? Or is a separate page, embedded in an iframe-element, the best way to do it?
I decided to go with the “same-window-new-page”-variant, and here’s why:
The reason for displaying the preview on a separate page was, that I wanted the article to appear in the way it will appear on the actual webpage-frontend. Furthermore I really wanted to use the original stylesheet, and not a faked 2nd version just for the preview. I think that is easier to maintain and more consistent in case the original stylesheet will be altered. And: why not use the original show-method from the articles controller to display an articles preview? (actually I did implement an extra preview-method and -view-template, which are slightly modified copies of the show-stuff)
The reason for the “same-window”-part is that I don’t like new windows: so (even if they open in new tabs) when you do not close them, after a while you’ll have plenty of them and your screen gets cluttered*. And it is the same amount of work to close the window or hit a button or link taking you back to where you came from. Further this is more linear and user-friendly IMHO.

So this is what it looks like when editing an article:

Editing an article. Click to enlarge.

And this is how the preview looks like when you hit “Vorschau” (i.e. preview in german):

Previewing an article. Click to enlarge.

The giant link at the bottom will take you back to the edit-screen with javascript:history.back();.
I should mention that the preview does not save anything to the database. It is generated on-the-fly and so it takes all the fields of the form as parameters. It’s a GET-Request, so these parameters appear in the adressbar in your browser:

Adressbar

I’m not sure if I think that’s too ugly…maybe this deserves some improvement.

Another thing I implemented is a button for buffering (“Zwischenspeichern” in german). So you can save the article without being sent back to the index action. So far this is just an ordinary POST-Request, with reloading the whole page. Maybe it would be sexier to use AJAX, but it’s more difficult to implement and during a save you won’t be editing the article anyway, do you?

So that was my - a bit extensive - update on innoQonRails…comments are always welcome!

*Yes, I now you can give the window a name and each time you hit the button for the preview it will be loaded in that window. I don’t like that, either.

05.11.07

Rails Sammelsorium

Hier kleinere Rails-Codeschnipsel, die ich in letzter Zeit benutzt habe.

Form-Partial in Rails 2.0

Als ich mir den Scaffold für die einzelnen Artikel erstellt habe, wurde mir kein Form-Partial (_form.html.erb) erstellt, wie ich es aus Rails 1.2.3 kannte (dort hieß die Datei noch _form.rhtml). So stand der Code der Eingabe- und Editierform 2x im Code: einmal in new.html.erb und einmal in edit.html.erb.
Vorher fand' ich das irgendwie besser, da die Form für's Editieren und neu Hinzufügen die gleiche ist, und somit habe ich den alten Zustand wieder hergestellt.

Zuerst musste ich die Datei _form.html.erb erstellen. Dort kam dann der ganze Code der Form rein, bis auf den Button zum abschicken der Form.
Da in Rails 2.0 der form_for tag etwas anders aussieht...

<% form_for(@article) do |f| %>

...und die Abkürzung über die Variable f gegangen wird...

<%= f.text_field :title, :class => "text_field" %>

musste eben diese noch übergeben werden. Dies geschieht mit folgendem Zusatz im render-Aufruf des Partials:

<%= render :partial => "form", :locals => {:f => f} %>

Und so ist dann auch wieder alles ordentlich DRY.

Tabindex

Um einen Tabindex für Eingabefelder festzulegen, gibt es einfach die Option :tabindex

<%= f.text_field :title, :class => "text_field", :tabindex => "1" %>

Focus auf ein bestimmtes Feld setzen

Dazu legt man sich lediglich eine kleine Helper-Funktion z.B. in application_helper.rb an...

def set_focus_to_id(id)
    javascript_tag("$('#{id}').focus()")
end

...und ruft diese - nach dem end-tag der Form - wie folgt auf:

<%= set_focus_to_id "article_title" %>

Das ganze setzt voraus, dass die Prototype/Scriptaculous-Skripte eingebunden sind über

<%= javascript_include_tag :defaults %>

(via Wolfmans Howlings)

validates_presence_of mit habtm korrekt in update-Action

Hat man eine many-to-many Beziehung zwischen zwei Entitäten, wie sie z.B. habe mir Artikel und deren Kategorien...

has_and_belongs_to_many :categories # (in article.rb)
has_and_belongs_to_many :articles # (in category.rb)

...so gab es bei der Update-Aktion im articles-controller das Problem, dass die Zeile

 validates_presence_of :categories # (in article.rb)

keine Wirkung zeigte und Änderungen an den Kategorien (in meinem Fall mit Checkboxen dargestellt) nicht übernommen wurden. So musste noch folgende Zeile in der Aktion update in articles_controller.rb hinzugefügt werden:

@article.categories.clear if !params['article']['category_ids']

In der Form sieht das Ganze wie folgt aus:

<% for category in @categories %>
    <%= check_box_tag "article[category_ids][]", category.id,
    @article.categories.include?(category), :class => "checkbox" %>
    <%= " " + category.long_name %><br/>
 <% count = count + 1 %>
<% end %>

(via diverser Quellen, die ich nicht mehr zusammen bekomme ;-))

(Randnotiz: wenn ich mir so besonders meine letzte Überschrift in diesem Post so durchlese, frage ich mich manchmal, warum man sich es antut Posts auf deutsch zu schreiben!? Da ich grundsätzlich in Englisch programmiere, und mir eigentlich auch englische Code-Kommentare leichter von der Hand gehen, muss ich manchmal echt überlegen, wie ich das, was ich schreiben möchte, jetzt auf deutsch ausdrücke.)

27.10.07

Dynamic Stylesheets in Rails

Nach einem ersten Test-Deployment kam es noch zu einigen Fehlern, was relative Linkangaben anging, wenn man die Seite z.B. in einem anderen Verzeichnis ablegt.
Besonders Pfadangaben zu Bildern im Stylesheet machten Probleme. Da aber CSS-Stylesheets nicht dynamisch sind, habe ich erstmal die entsprechenden Angaben herausgenommen und inline in einem Template (application.html.erb) abgelegt. Dort konnte ich dann die entsprechende Dynamic reinbringen.
Das hat mir aber natürlich nicht gefallen, so viele Style-Angaben im Template zu haben, und so habe ich mich auf die Suche nach einer Lösung gemacht und bin auf folgenden Blogeintrag von Josh Susser gestoßen: http://blog.hasmanythrough.com/2007/10/18/simpler-than-dirt-restful-dynamic-css

Nach diesem Rezept bin ich dann quasi vorgegangen, um das Problem zu lösen. Ich musste es nur etwas anpassen, da meine Angaben überall verfügbar sein sollten - also application-wide.
Also habe ich erstmal ein view-template im Verzeichnis /app/views/application/ erstellt mit dem Namen "dynamic_stylesheet.css.erb".
Danach habe ich im Application-Controller (application.rb) folgende Methode implementiert:

def dynamic_stylesheet
    respond_to do |format|
      format.css
    end
end

Einfach eine Methode, die bei Anforderung eines Stylesheet entsprechend reagiert.

Des weiteren war es noch notwendig eine Route in routes.rb hinzuzufügen:

map.connect '/dynamic_stylesheet.:format',
:controller => "application", :action => "dynamic_stylesheet"

So war es dann schon möglich, das Stylesheet mit folgender URL direkt aufzurufen: http://localhost:3000/dynamic_stylesheet.css

Um das Stylesheet in einem view-template noch einzubinden, brauchte ich nur noch folgende Zeile hinzufügen:

<%= stylesheet_link_tag '/dynamic_stylesheet' %>

So sieht dann alles auch wieder schick aus und der Style-Kram ist wieder da, wo er hingehört. War in der Tat "Simpler than dirt"! Thanks to Rails 2.0!

17.10.07

Backend Layout

Ich bin dann jetzt mal wirklich beim Backend gelandet.
Dort angekommen, musste ich mich erstmal darum kümmern, dass die entsprechenden Templates (Edit, Index, New...) erstmal ein anderes Layout bekommen als die Webseite selber.

Zuerst dachte ich, ich könnte einfach Folgendes in den ArticleController schreiben und gut is...:

layout "backend", :except => [:show, :search]

Dem war aber leider nicht so. Denn anstelle, dass show und search weiterhin das Standardlayout verwenden, benutzten sie dann gar keins mehr. So musste ich noch folgende Zeile in den entprechenden Methoden hinzufügen/modifizieren:

format.html { render :layout  => "application" } # show.html.erb

So habe ich explizit festgelegt, welches Layout sie nutzen sollen. Das entspricht zwar nicht Convention over Configuration aber mir is bis jetzt nix besseres eingefallen.

Da ich momentan "Getting Real" lese, dachte ich, mach' ich es wie die Jungs und fange beim UI an. Der folgende Screenshot zeigt einen ersten Entwurf:

screenshot_backend1.png

Ich habe mit dem Layout für das Hinzufügen neuer Artikel angefangen, da dies erstmal die wichtigste Komponente des ganzen CRUD ist. Außerdem ist das edit-Template dann quasi auch direkt fertig.

Ok, es sieht im Moment sehr schlicht aus. Ich habe grundsätzlich erstmal das Stylesheet, welches mir das scaffolding generiert hat genommen. Ich habe dann nur den Hintergrund der Webseite eingefügt.
Ferner habe ich das Markdown Cheat Sheet von Mr. Markdown himself eingefügt. Ich glaube zwar, dass sich die bisherigen Autoren der Webseite gut mit der Markdownformatierung auskennen, aber wer weiss, wer da sonst noch mit arbeiten wird...Außerdem: sollte man eine Formatierung vergessen haben, kann man eben schnell nachschauen. Genug Platz ist auf jeden Fall da.

Die Eingabeform besteht nur aus den Feldern für Titel und Fliesstext sowie einer Auswahl in welche Kategorien man den Artikel stecken möchte. Das ist erstmal alles was man braucht. Ich habe mich dazu entschieden die optionalen Felder (Kurztitel und Zusammenfassung) per default ausgeblendet zu lassen, um 1. den Blick auf das Wesentliche zu richten und 2. die momentane Situation so ist, dass die wenigsten Artikel einen Kurztitel und/oder Zusammenfassung besitzen.
Alle anderen Felder für einen Artikel werden automatisch generiert. Diese müssen also (erstmal) nicht auf der Maske erscheinen.

Ich denke so kann man erstmal damit arbeiten. Was ich allerdings vielleicht noch einfüge ist ein Header mit dem innoQ-Logo, damit man auch weiss, wo man sich hier befindet.

14.10.07

innoq.com: Suche mit GET

Ok, ich bin dann doch erstmal wieder beim Frontend gelandet. Irgendwie mag ich es nicht, wenn Dinge so halbfertig sind, besonders wenn es nur "Kleinigkeiten" sind. Und so habe mich dran gemacht, die bisher schon implementierte Suche (mittels acts_as_ferret plugin), anstatt mit POST, mit GET und somit REST-konform umzusetzen.

Für die Sache musste ich dann ein extra Route in routes.rb eintragen und schon funktionierte es! :-) Naja, ok...ein bisschen nachdenken musste ich schon vorher. Aber ich denke das Ergebnis, sprich der in der Adresszeile übergebene String, macht Sinn und man kann anhand der URL verstehen, was abgeht:

suche_screenshot.png

Ich habe mich bei der Benennung des Suchparameters für die lange Version ("query=") entschieden, da man so besser nachvollziehen kann, was gemeint ist. Die Kurzfassung ("q=" z.B. bei Google) ist einfach nicht so sprechend. Und zusätzlich ist der Quellcode auch verständlicher. Ferner habe ich den title-tag der Ergebnisseite angepasst, so dass der Titel nun auch besagt, dass man gesucht hat und wonach man gesucht hat. Somit lässt sich die Seite dann auch bequem bookmarken.

12.10.07

innoq.com Frontend fertig

Das Frontend für die innoQ-Webseite ist nun fertig (kleinere Änderungen vorbehalten). Es waren noch ein paar Detailsverbesserungen notwendig, so. z.B. das richtige Highlighten (uuuuhhhh) der Unternavigation. Des weiteren wurde die Datenbankstruktur nochmal komplett neu aufgesetzt und so ist z.B. auch ein Feld "short_title" - für einen optionalen Kurztitel eines Artikels - hinzugekommen. Ferner wurde der Missstand entfernt, dass bei einigen Artikeln der Titel nochmal im Text selber stand und bei manchen nicht. Jetzt steht nirgendwo mehr der Titel im eigentlichen Fließtext und somit sind die beiden Dinge sauber getrennt.

Für das ganze Daten-hin-und-her-Geschiebe in der Datenbank habe ich wieder rake-Tasks benutzt, was auch wieder hervoragend funktioniert hat. Dort habe ich wieder ordentlich mit Regulären Ausdrücken gearbeitet und muss sagen, dass ich die Dinger mitlerweile doch sehr lieb gewonnen habe.

Als nächses werde ich mich dann dem Backend widmen und wohl eine GUI zum erstellen und bearbeiten von Artikeln coden.

01.10.07

Suche mit acts_as_ferret

Da auch die Suche der innoq-Webseite neu gecoded werden muss, habe ich mich da auch nach einer Lösung umgesehen und bin auf das Plugin acts_as_ferret gestoßen.

Auch zu diesem Plugin gab's ein Tutorial von railsenvy.com. Und zwar hier: http://www.railsenvy.com/2007/2/19/acts-as-ferret-tutorial

Und es ist in der Tat so einfach, wie es in diesem Tutorial beschrieben ist. Coole Sache.

30.09.07

Rake me slugs!

Da die URLs nachwievor "schön" sein sollen, hatte ich das Problem, dass für die einzelnen Artikel einer Kategorie (Leistungen, Referenzen etc.) noch keine slugs (Kurznamen) hinterlegt waren. Und diese slugs tauchen eben in der Adresszeile auf und sie sollen als Navigationsgrundlage dienen.
Wie generiere ich mir also am besten eben solche?
Da der slug in Zukunft direkt beim Erstellen eines neuen Artikels generiert wird, hat im Prinzip die Generierung für bereits vorhandene slugs nix mit meiner Anwendung zu tun.
Dazu wollte ich dann rake benutzen. Da ich Befehle wie rake db:migrate o.Ä. bereits kannte, dachte ich mir wird das damit bestimmt schön zu lösen sein...
Und so war's dann auch. Zu meinem Glück hatten die Jungs von railsenvy.com gerade ein rake-Tutorial veröffentlicht. Dieses kann ich nur empfehlen, wenn man sich noch nicht mit rake auseinandergesetzt hat: http://www.railsenvy.com/2007/6/11/ruby-on-rails-rake-tutorial

Und ich muss sagen, es ist wirklich ziemlich einfach gewesen ein kurzes Skript zu schreiben, um mir sämtliche slugs generieren zu lassen:

namespace :db do
  desc "Creates a slug for each article in the database, based on
           its title"
  task(:create_slugs => :environment) do
    articles = Article.find(:all, :conditions => { :slug => nil })
    articles.each do |article|
        article.update_attribute("slug", article.generate_slug
                                            (article.title))
    end
  end # of task do
end # of namespace do

Und schon hatte ich alles zusammen und konnte weiterarbeiten.
Zur Erläuterung: die Methode generate_slug() entfernt alle Sonderzeichen etc.

20.09.07

PrettyURLs

Die "neue" innoQ-Homepage soll auch nachwievor schöne URLs haben, sog. PrettyURLs. Die Hauptnavigation der Seite besteht aus verschiedenen Kategorien, wie z.B. "Leistungen", "Referenzen" usw. Siehe auch folgenden Ausschnitt: Bild%202.png

In der Anwendung ist es so modelliert, dass jeder Link eine Kategorie repräsentiert. Somit hat man also einen Controller categories. Gemäß dem Standard in Rails werden diese Kategorien also über Links also z.B. über /categories/3 aufgerufen, wobei categories der Controller ist und 3 die entsprechende ID. Wie bekommt man es aber hin, dass man das Selbe mit einer PrettyURL hinbekommt, also z.B. /leistungen/?

Zunächst muss ich noch erzählen, dass in der Datenbank für jede Kategorie neben dem "schönen" Namen (long_name), welcher auch Sonderzeichen enthalten kann, auch einen "slug", einen Kurznamen, für jede Kategorie gibt. Dieser beinhaltet keine Sonderzeichen und ist quasi URL-freundlich.

Jetzt sollte man zunächst eine entsprechende Route anlegen, die dafür zuständig ist, die URL korrekt umzusetzen. Dies geschieht in routes.rb im config-Verzeichnis:

map.connect ':slug', :controller => "categories", :action => "show"
:slug speichert eben solchen. Also z.B. "leistungen" oder "Referenzen". Über :controller und :action wird dann gesagt, was damit passieren soll. In der Action show im categories controller passiert dann folgendes:
if params[:id] 
  @category = Category.find(params[:id])
else
  @category = Category.find(:first,
                      :conditions => { :slug => params[:slug] })
end

@path = "../" 

Der erste Teil der Anweisung ist dafür da, dass Standard-Rails-Weg immer noch funktioniert. Im else-Zweig wird dann einfach statt nach der ID nach dem entsprechenden slug in der Datenbank gesucht. @path wird den Links vorrangestellt, da man ja, wenn man in eine der Kategorien navigiert, eine Hierarchiestufe tiefer ist als bisher und man somit erst wieder eine Stufe nach oben navigieren muss, damit der Link stimmt.

Das war es dann auch fast schon. Jetzt muss man nur noch die Links in der View anpassen:

    <= link_to category.long_name, @path + category.slug + "/" >
< end >

(die %-Zeichen müsst ihr euch denken. Hab's nicht hinbekommen, die drinzulassen, ohne das der Code verschwindet). Also im Prinzip eigentlich ganz einfach, nur finde ich sind die Routes, also map.connect etc., relativ wenig dokumentiert.

Was ich wahrscheinlich als nächstes implementiere ist der Fall, dass eine ungültige URL angegeben wird.

Wie findet ihr die Lösung? Ist die so in Ordnung, oder bekommt man das Ganze noch eleganter hin?

About

DanielHi. I'm Daniel Pietzsch and this is my innoQ-Blog. I'm a 26y old student at FH Bochum and working student at innoQ.
In this blog I mainly write about the progress concerning my diploma thesis which will be an in-house application for innoQ based on Ruby on Rails, but some other (geek) stuff might appear here, too.

daniel [dot] pietzsch [alt-L] innoq [dot] com

I recommend

Categories

Recent Comments

License

Creative Commons License This weblog is licensed under a Creative Commons License.
Powered by
Movable Type 3.31