Main

Ruby on Rails Archives

23.04.08

ActiveResource and Associations

Ok, it’s been quite a while since the last post popped up here; that’s because I struggled with the innoQ internal timereporting- and invoicing-application I’m working on, which is my diploma thesis, too.
And here’s one of my problems I had…

Preface

Concerning users that are going to use my application, there has been a change made, so my app is no longer responsible for user management. This is now all done by a separate application. And this applications has a REST interface which I have access to and should obtain my user data from, as well as handle authentication. I now look at the “obtain data” part, because the authentication part is definitely worth another blog post.

ActiveResource basics

Now: receiving data from another Rails app with a REST interface is quite simple. In the model (in my case User) you inherit from ActiveResource::Base instead of ActiveRecord::Base and specify a site url to the remote Rails application. Like this:

class User < ActiveResource::Base
    self.site = "http://the.url.to/yourapp/"
end

Now you can obtain user data almost the same way you do with ActiveRecord classes:

User.find(2) # finds the user with the id of '2'

or even

User.find(:all) # finds all users in the remote app

if you like.

My problem

But what if your resource is somehow associated with your local model and database?
In my case an user has many projects and a project has many users. So here you have a many-to-many relationship.

That really is more difficult for problems like “I’d like to obtain all users for a specific project” and vice versa, because you can’t just make up a many-to-many relationship as you would with two ActiveRecord objects. Like this:

has_and_belongs_to_many :projects # or
has_many :projects, :through => 'projects_users'

In a class which inherits from ActiveResource this is not supported/allowed.
Ok, in my project model these statements would work, because it is an ActiveRecord object. But all the methods you gain through this won’t work either, because they don’t know the you are referencing a model of type ActiveResource and all finds will fail.

So what to do?
Here’s what works for me (so far).
But I’m sure there are many more ways out there to solve this, and I’d really appriciate every comment on this topic, ‘cause I am curious if there’s a more elegant solution. So, there you go:

My solution

Remember the initial situation:

Project (ActiveRecord - in my app) (M)—————-(has)—————(N) User (ActiveResource - remote app)

So first of all I created a join table and the corresponding join model by typing (in the console, of course):

script/generate model projects_users

This creates a model for me with along with a migration file. Inside this file I write these lines:

class CreateProjectsUsers < ActiveRecord::Migration
  def self.up
    create_table :projects_users do |t|
      t.integer :project_id, :user_id, :null => false
      t.string :color, :null => false
    end
  end

  def self.down
    drop_table :projects_users
  end
end

An advantage of join models is that you can store additional attributes in the join table. The only thing I store along the project_id and the user_id is a color. That’s just relevant for the front-end to display the project in the correct color, so don’t get irritated. ;-)

Then, I ran the migration:

rake db:migrate

Now, let’s head over to the association part.
As you can’t put any associations in a ActiveResource model, there’s nothing to do in the User model.

In the Project model I put a has_many statement:

has_many :projects_users, :class_name => "ProjectsUsers"

(Note, that in my case I had to explicitly tell the class_name of the join model class, because Rails assumes this to be singular, i.e. ProjectUser.)

In the ProjectsUsers model I wrote the following two lines:

belongs_to :project
belongs_to :user # so far this isn't used by me, but it may be useful some day

Now the interesting part: How can you get all users for a given project in a way like project.users.find(:all)?
In project.rb, define a method called users:

def users
    user_ids = self.projects_users.find(:all, :select => "user_id")
    users = user_ids.collect { |projects_users| User.find(projects_users.user_id) }
end

Now via project.users you’ll get all the users you need.
(I’m pretty sure you can rewrite this, so that it works the railsway and you can call project.users.find(:all) instead, but I haven’t tried it, yet)

Ok, the other way round, meaning obtaining all projects for a specific user, I faked it a bit, and I’m sure you can do better here, too. But it works!
Instead of creating a projects method in the User model, I coded a method called self.find_by_user_id in the Projects model:

def self.find_by_user_id(userid)
    project_ids = ProjectsUsers.find_all_by_user_id(userid, :select => "project_id")
    projects = project_ids.collect { |projects_users| projects_users.project }
end

You can call it this way:

user_projects = Project.find_by_user_id(2)

And BOOM!, you have all projects for the user no. 2.

That’s all folks! This was my solution/workaround for integrating a REST-Resource via a many-to-many association in Rails. Hope you liked it. :-)

05.04.08

Rails and in_place_editor issues

Ok, so the last few days I had a bit of a struggle concerning the script.aculo.us in place editor and the corresponding plugin/helper in rails.
So here are my links that really helped me solving these issues:

If you have more interesting links regarding the in_place_editor and rails, feel free to leave a comment right here. Thanks.

26.02.08

Altered ER-Model [UPDATE]

I altered a part of my ER-Model concerning the implementation of the time-reporting:

There’s no longer an entity called “Arbeitszeit” (working time). All the worked hours and days should now get stored in the extra many-to-many-table that’ll be created in between “Stundenzettel” (timesheet) and “Projekt” (project). That table might have the following attributes: StundenzettelID, ProjektID, workingTimeTotal, dailyTimes, notes.
So far I’m not sure how to store all the hours to their corresponding days in dailyTimes (because that’s at least about 30 values every month and user), but I might use a plain text field which contains a somehow-delimited list of working-hours (ascending from the 1st to the last day of the month).

The “Mitarbeiter” (co-worker) entity is also the applications entity for storing login- and user-information.

What you see on the latest mockup, is in Rails the update- or new-action/view for the “Stundenzettel”-controller. I think there won’t be any implementation of a read- or delete-action for “Stundenzettel” (timesheets).

So, I hope this will work for me…

And yes, next time the ER-Model will be in english, too, that I don’t have to bother you (and me) with this language mix. I also code in english, so this makes sense anyway.

[UPDATE]

After talking to Tim and Phillip and considering Stefans Comment, I think this is the right way of modelling the Entity Relationship:

So, in the DailyReport entity there is the attribute Date which is an additional primary key to the ProjectID and the UserID. This way the WorkedHours are stored for every day. So you have a table for every day, employee and project.
The same with notes. This may be useful to describe your tasks on that day, for example. This way I think I’ll have to reengineer the UI, ‘cause notes do not get stored on a monthly basis. I already have something in mind for that.

[/UPDATE]

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.

08.12.07

Rails 2.0 released

"This is a fantastic release that’s absolutely stuffed with great new features, loads of fixes, and an incredible amount of polish."

Check out more in DDHs Post:

Riding Rails: Rails 2.0: It's done!

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.

20.11.07

Make TextMate cope with Rails 2.0 filename convention

In Rails 2.0 view templates have an ending like .html.erb, .css.erb or .xml.erb (instead of f.i. .rhtml in Rails 1.2.x). So if you've selected a syntax highlighting in TextMate for - say a .html.erb template, this would apply to all templates with ending .erb (i.e. .css.erb etc.) and that's bad, because you want the .css.erb file to have another syntax highlighting (CSS!).

To fix this, you simply have to do the following:

  1. In the TextMate menu select Bundles -> Bundle Editor -> Edit Languages
  2. Expand the "Ruby On Rails" item
  3. Select "HTML (Rails)"
  4. Replace "fileTypes = ( 'rhtml' );" with "fileTypes = ( 'rhtml', 'html.erb' );"
  5. Redo this for every language you use (i.e. CSS -> css.erb, XML -> xml.erb...) in its corresponding menu
  6. Now you can select a separate highlighting for every .erb extension

(via Google Groups)

10.11.07

Rails 2.0: Release Candidate 1

Rails 2.0 RC1 is here:
http://weblog.rubyonrails.com/2007/11/9/rails-2-0-release-candidate-1

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!

26.10.07

Ruby und Rails in Leopard

Eine übersichtliche Liste, was Leopard so alles mit sich bringt:

http://trac.macosforge.org/projects/ruby/wiki/WhatsNewInLeopard

(via RubyInside)

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.

04.10.07

Lesbarer Quellcode

"IMHO ist lesbarer Code wesentlich besser, vor allem, was die Wartbarkeit angeht. Und wenn man dafür die doppelte Anzahl Codezeilen benötigt..."

Das habe ich in einem Post vor einiger Zeit geschrieben, und bekomme nun eine kleine Bestätigung von jemandem, der mehr Erfahrung hat. Koz vom Rails Core Team schreibt auf therailsway.com:

"While the refactored version may have more lines of code, but don’t let that scare you. It’s far more important for code to be human readable than incredibly concise."

Genau meine Meinung! Ob ich mich selber immer daran halte steht auf einem anderen Blatt... ;-) Ich finde in dem kurzen Post ist ein sehr schönes Beispiel angegeben. Solchen Code mag ich.

Der Link: http://www.therailsway.com/2007/10/4/many-skinny-methods

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.

Rails 2.0 ist fast da

Es gibt ein erstes Preview-Release von Rails 2.0!

Um dieses zu Installieren gibt es folgende Möglichkeiten:

gem install rails --source http://gems.rubyonrails.org

Oder, um es nur in einem Rails-Projekt auszuprobieren:

rake rails:freeze:edge TAG=rel_2-0-0_PR

In dem Post erläutert DHH einige neue Features von Rails 2.0. Sicherlich lesenswert: http://weblog.rubyonrails.com/2007/9/30/rails-2-0-0-preview-release

Grab it:

Grabbing Rails 2.0

;-)

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.

27.09.07

HTML-Tags aus einem String entfernen mittels regulärem Ausdruck [UPDATE]

Mein erster Kontakt mit Regulären Ausrücken: ich wollte - im Rahmen der neuen innoq-Webseite - aus einem String sämtliche HTML-Tags entfernen.

Bevor ich direkt nach einer fertigen Lösung suche, dachte ich mir probierst du das mal selbst aus, und dabei bin ich auf 2 ganz nützliche Online-Tools gestoßen, die einem helfen Regular Expressions zu überprüfen:

  1. reWork: Mit diesem Tool kann man einen regulären Ausdruck gegen einen bestimmten Text Prüfen. Sehr nützlich, vor allem, weil er auch für gewisse Aufgaben direkt den Code ausspuckt. So gibt er Ruby-, Javascript-, Python- und PHP-Code aus und liefert auf Wunsch auch direkt die Syntax für eine spezielle Aufgabe aus, wie z.B. das ersetzen eines Teilstrings.

  2. txt2re: Hiermit kann man quasi umgekehrt vorgehen und sich seinen regulären Ausdruck für einen bestimmten Textpattern zusammenstellen. Nicht so schick, und auch nicht wirklich vollständig, aber es hilft einem einen Ansatz zu finden, wenn man eigentlich keine Ahnung hat.

Naja, nach einiger Rumprobiererei hab ich's leider nicht ganz hinbekommen einen passenden regulären Ausdruck zu finden, obwohl die Lösung, die ich dann genommen habe sehr einleuchtend ist:

<[^>]*>

Also mein Code-Schnipsel sieht dann so aus:

item.markdown_content.gsub(/<[^>]*>/, '').to(250)

[UPDATE]: Ich kann auch wieder ein Cheat Sheet von Dave Child empfehlen: http://www.ilovejackdaniels.com/cheat-sheets/regular-expressions-cheat-sheet/

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?

19.09.07

Das "Wie erstelle ich ein neues EdgeRails Projekt"-Tutorial

Die innoq-Webseite soll auf Ruby On Rails portiert werden. Zu diesem Zweck nutze ich EdgeRails. EdgeRails ist die aktuellste Entwickler-Revision von Rails, die Features enthält, die wahrscheinlich in einer der nächsten Rails-Versionen implementiert werden. Mehr dazu gibt's auch hier: http://wiki.rubyonrails.org/rails/pages/EdgeRails

Unter dieser Adresse findet man zwar auch eine Anleitung dafür, wie man eine bestehende Rails-Anwendung unter EdgeRails laufen lassen kann, aber eben nicht, wie man eine komplett neue EdgeRails-Anwendung erstellt. Und genau das möchte ich hier erläutern. Zumdindest für Unix-Systeme (Mac OSX, Linux etc.). Windows-User müssen die Befehle entsprechend anpassen. Subversion muss installiert sein für dieses Tutorial. Subversion gibt's hier: http://subversion.tigris.org/

Deine aktuelle, systemweite Rails-Installation ist nicht betroffen und wird nicht überschrieben o.Ä. Du kannst weiterhin "normale" Rails-Anwedungen erstellen, wie bisher.

Auf ins Terminal und los geht's:

1. Wechsel in das Verzeichnis in welchem du das Projekt anlegen möchtest. Z.B. so:

cd RubyOnRailsProjects/

2. Die neuste EdgeRails-Version mittels Subversion auschecken und im Ordner rails speichern:

svn co http://dev.rubyonrails.com/svn/rails/trunk rails

Jetzt musst du einen Moment warten, bis der ganze Kram runtergeladen ist.

3. Lege ein neues EdgeRails Projekt an:

ruby rails/railties/bin/rails dein_edge_projekt

Der Projektordner (hier: dein_edge_projekt) wird angelegt inkl. der entsprechenden Struktur und den entsprechenden Dateien.

4. Verschiebe den rails-Ordner nach ./dein_edge_projekt/vendor/

mv rails ./dein_edge_projekt/vendor/

Für rails-Befehle wie z.B. script/generate scaffold article wird dann immer die Dateien unter vendor/rails genommen. Es kommt quasi einem rake:freeze:edge gleich, nur das die Dateien nicht erneut runtergeladen werden.

Das war's!

Viel Spass mit deiner EdgeRails Anwendung.

Für Verbesserungsvorschläge, Lob, Kritik etc. könnt ihr gerne die Kommentarfunktion nutzen. ;-)

17.08.07

The secret santas on rails

Als erstes - zugegebenerweise recht kleines - Rails Projekt bin ich grade dabei die Secret Santa Anwendung mit Rails umzusetzen. Hauptsächlich um mal zu lernen, wie der ganze Krempel in Rails so zusammenhängt und um erste Erfahrungen an einem simplen Beispiel zu sammeln.

Und ich muss zugeben, da war ich bis jetzt schon gut beschäftigt. Eigentlich ist das ja eine kleine Anwendung, aber irgendwie hab ich schon für's Grundgerüst rel. lange gebraucht (wesentlich länger als für die reine Ruby Anwendung).

Meine Hauptprobleme waren erstmal weiter mit der Syntax klar zukommen (wann nehme ich person.id, wann :id zur Übergabe von Parametern etc. - ich weiss nicht, ob ich das schon 100%ig gerafft habe), wie ich richtig auf Zeilen oder Felder in meiner Datenbank zugreife (Stichwort .find) und wie er einen Datenbankabfrage aller Datensätze in einen Array packt (nämlich als Hash - also hat man einen Hash in einem Array - im Nachhinein schon logisch, aber man muss sich erstmal dran gewöhnen) und wie ich dann auf diesen zugreife.

Naja, die signifikanten Codeauschnitte sehen dann so aus:

Die Klasse einer Person:

class Person < ActiveRecord::Base  
  has_one :person

  #do you have a correct santa assigned?
  def has_correct_santa?
    lastname != Person.find(person_id).lastname
  end

  #can one person be the santa of other?
  def can_be_santa_of?(other)
    lastname != other.lastname
  end

  #return the object behind the person_id
  def personal_santa
    Person.find(person_id)
  end
end

Die Methode swap_santas:

#switches the two personal_santas of the passed people
  def swap_santas (person1, person2)
    person1.update_attribute("person_id", person2.person_id)
    person2.update_attribute("person_id", person1.person_id)
  end

Die Methode assign_santas:

#assigns each person his secret santa
  def assign_santas
    @people = Array.new
    @people = Person.find(:all)
    @santas = Array.new(@people)
    @people.each { |person| 
            person.person_id = 
@santas.delete_at(rand(@santas.length)).id #assigns santas randomly
            person.update_attribute("person_id", person.person_id)
          }

    @people.each { |person| 
      unless person.has_correct_santa?
        candidates = @people.select { |p| 
p.personal_santa.can_be_santa_of?(person)
 && person.personal_santa.can_be_santa_of?(p) } #finds possible candidates for a swap
        swap_santas(person, candidates[rand(candidates.length)])
      end
      }
    flash[:notice] = 'The secret santas have been assigned!'
    redirect_to :action => 'list'
  end

So funktioniert es im Moment auf jeden Fall, aber das ist keine gute Lösung. In dieser Version habe ich definitiv mehr Datenbankzugriffe als nötig sind. Ich sollte besser nur einmal die Daten aus der Datenbank holen und dann mit diesen Werten arbeiten bis die Partner korrekt zugewiesen sind und dann den Kram in die Datenbank zurückschreiben (so werden die Daten 2x geschrieben: einmal beim zufälligen zuweisen und dann nochmal bei der ggf. notwendigen Korrektur). Oder nicht?

Naja, ich denke das werde ich noch verbessern und mir dann auch mal angucken, wie das mit Stylesheets und Javascript in Rails gemacht wird.

Ruby und Rails Dokumentation

Über den rubyonrails.org-Blog habe ich diesen recht schicken API-Browser gefunden: http://www.noobkit.com/
Sieht auf jedenfall schonmal besser aus, als die jeweilige API selbst.

Ferner finde ich das Widget RubySearch von Brian Guthrie ganz hilfreich.

Es gibt auch noch ein weiteres Ruby-Widget, dieses benötigt allerdings ne Internet-Verbindung: Ruby RDoc Widget

Und hier noch ein nützliches Cheat-Sheet: Ruby On Rails Cheatsheet

24.07.07

Learning Ruby and Rails

Nachdem ich letzte Woche das Tutorial von Apple zu Ruby on Rails durchgearbeitet habe - welches doch so einige als "deprecated" gekennzeichnete Funktionen beinhaltete - fange ich jetzt damit an folgendes Tutorial von O'Reilly durchzuarbeiten: Cookin with Ruby on Rails - Designing for Testability.
Das ist bereits der 4. Teil dieses Tutorials und etwas aktueller als das von Apple...und meiner Meinung nach auch einiges informativer und ansprechender geschrieben.

Nebenher werde ich noch einen Blick in das Online-Buch "Programming Ruby: The Pragmatic Programmer's Guide" werfen, um mit Ruby weiterzukommen bzw. es überhaupt mal richtig zu lernen.

Ferner teste ich grade Textmate als Editor und muss sagen, der macht einen sehr guten Eindruck!

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