diff --git a/bin/md2site b/bin/md2site index cba1178..c8c729b 100755 --- a/bin/md2site +++ b/bin/md2site @@ -4,6 +4,10 @@ require 'rubygems' require 'trollop' require 'ftools' require 'rdiscount' +require 'erb' +require 'uv' +require 'hpricot' +require 'cgi' opts = Trollop::options do version "md2site (c) 2010 Dirk Breuer" @@ -18,8 +22,9 @@ EOS opt :source_path, "Where to read the Markdown files from", :default => "site" opt :destination_path, "Where to put the generated files", :default => "htdocs" - opt :config, "Path to config file with meta information", :default => "config.yml" opt :nojekyll, "Put a .nojekyll file in destination_path", :default => true + opt :template, "Which template to use", :default => "templates/base.html.erb" + opt :theme, "Which syntax color scheme to use", :default => "sunburst" end class SiteGenerator @@ -48,7 +53,7 @@ class SiteGenerator def create_directory_structure `rm -r #{@options[:destination_path]}` - + @markdown_pages.each do |md_path, html_path| File.makedirs("#{@options[:destination_path]}/#{File.dirname(html_path)}") end @@ -57,8 +62,28 @@ class SiteGenerator def create_html_files @markdown_pages.each do |md_path, html_path| markdown = RDiscount.new(File.read(md_path)) - File.open(File.join(@options[:destination_path], html_path), "w") { |f| f.write markdown.to_html } + File.open(File.join(@options[:destination_path], html_path), "w") { |f| f.write trough_template(markdown.to_html) } + end + Uv.copy_files "xhtml", @options[:destination_path] + end + + def highlight_code_in_html(html) + html = Hpricot(html) + html.search('//pre/code') do |element| + element.inner_html =~ /^\!\!\!(\w+)/ + element.inner_html = element.inner_html.gsub(/^\!\!\!(\w+)/, '') + element.inner_html = Uv.parse(element.to_plain_text, "xhtml", "#{$1 || 'ruby'}", false, @options[:theme]) end + html.to_html + end + + def template + @renderer ||= ERB.new(File.read(@options[:template])) + end + + def trough_template(content) + @content = content + highlight_code_in_html(template.result(binding)) end def create_nojekyll diff --git a/config.yml b/config.yml deleted file mode 100644 index 2d84b58..0000000 --- a/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -title: "ASS Anfänger Kurs" -url: "http://galaxycats.github.com/ASS-Beginner" -description: "Maintains the demo source code, course material, like hand outs and demo-scripts for the ASS courses at the University of Applied Science Cologne." diff --git a/site/handson/associations.md b/site/handson/associations.md index 71b9028..be33f3d 100644 --- a/site/handson/associations.md +++ b/site/handson/associations.md @@ -52,6 +52,7 @@ ableiten. 1:1-Beziehung (`has_one`) zwischen zwei Objekten: Ein `Picture` hat genau ein `Thumbnail`. + !!!ruby_on_rails class Picture < ActiveRecord::Base has_one :thumbnail end @@ -69,6 +70,7 @@ ableiten. 1:m-Beziehung (`has_many`) zwischen zwei Objekten: Eine `Company` hat viele `Client`s. + !!!ruby_on_rails class Company < ActiveRecord::Base has_many :clients end @@ -95,6 +97,7 @@ noch eine Klasse definiert werden. Beispiel für `has_many_and_belongs_to`: + !!!ruby_on_rails class Developer < ActiveRecord::Base has_and_belongs_to_many :projects end @@ -114,6 +117,7 @@ Beispiel für `has_many_and_belongs_to`: Beispiel für `has_many :through`: + !!!ruby_on_rails class Developer < ActiveRecord::Base has_many :assignments has_many :projects, :through => :assignments @@ -155,6 +159,7 @@ in den Controllern über die Methode `session` zugreifen. Das `session`-Objekt ist dabei als Hash realisiert. Die Speicherung der ID eines Benutzers könnte dementsprechend wie folgt aussehen: + !!!ruby_on_rails user = User.find_by_username(params[:username]) if user session[:user_id] = user.id diff --git a/site/handson/controller_und_views.md b/site/handson/controller_und_views.md index 23f0204..e3c108b 100644 --- a/site/handson/controller_und_views.md +++ b/site/handson/controller_und_views.md @@ -30,6 +30,7 @@ Die Aufgabe soll natürlich RESTful realisiert werden. Wir brauchen daher auf jeden Fall drei Actions in unserem Controller. Hier noch einmal beispielhaft die Implementierung der Action zu Anzeige aller Statusmitteilungen: + !!!ruby_on_rails class MessagesController < ApplicationController def index @@ -43,6 +44,7 @@ Ordner `app/views/messages` verwenden. Im Template muss nicht mehr das HTML Gerüst definiert werden, dass ist bereits über das Layout geschehen. Wir müssen also nur noch alle Statusmitteilungen ausgeben. Dazu kann man sich folgenden Code vorstellen: + !!!html_rails <% @messages.each do |message| %> <% div_for message do %>

<%= message.content %>

@@ -69,6 +71,7 @@ verwendet. Für deren Realisierung zur Eingabe von Daten für ein `ActiveRecord`-Objekt bietet Rails eine ganze Reihe von Helper-Methoden an. An folgendem Beispiel seien einige dieser Methoden kurz beschrieben: + !!!html_rails <% form_for :message do |f| %> <%= f.text_area :content %> <%= f.submit "Post Status" %> diff --git a/site/handson/mentions_und_follower.md b/site/handson/mentions_und_follower.md index 994c8b4..45e7656 100644 --- a/site/handson/mentions_und_follower.md +++ b/site/handson/mentions_und_follower.md @@ -50,6 +50,7 @@ aufgerufen, nachdem eine bestimmte Methode ausgeführt wurde, wie z.B. die `save`-Methode eines Active Record Objekts. Hier ein kleines Beispiel, um das zu verdeutlichen: + !!!ruby_on_rails class User < ActiveRecord::Base after_save :send_welcome_email diff --git a/site/handson/partials.md b/site/handson/partials.md index da0de18..74e3034 100644 --- a/site/handson/partials.md +++ b/site/handson/partials.md @@ -26,6 +26,7 @@ Partials werden in Rails immer durch einen Underscore als Prefix im Dateinamen identifiziert und werden in der gleichen Verzeichnisstruktur abgelegt wie die eigentlichen Templates zu einem Controller: + !!!plain_text app/ +-views/ +-messages/ @@ -36,6 +37,7 @@ Templates zu einem Controller: Anhand des folgenden Beispiels wollen wir kurz erklären, wie Partials in Templates integriert werden können: + !!!html_rails

Statusmitteilungen

<%= render "messages/form" %> diff --git a/site/handson/rails.md b/site/handson/rails.md index 895ddf4..3de6652 100644 --- a/site/handson/rails.md +++ b/site/handson/rails.md @@ -57,6 +57,7 @@ Datenbank-Tabellen angelegt werden. ### Rails installieren und Projektstruktur initialisieren + !!!plain_text $> gem update --system $> gem --version 1.3.6 @@ -64,6 +65,7 @@ Datenbank-Tabellen angelegt werden. Rails-Projekt generieren: + !!!plain_text $> rails ### Generatoren und rake-Tasks @@ -73,6 +75,7 @@ Zunächst muss eine Resource mit Hilfe des folgenden Befehls generiert werden: Dabei werden alle relevanten Dateien erzeugt, von der Migration über das Modell bis hin zum Controller. Hier ein Beispiel: + !!!plain_text rails generate resource message content:string ($> ~/projects/ass/twitter-clone) invoke active_record @@ -101,6 +104,7 @@ Jetzt kann man direkt die erzeugte Migration ausführen und den Server starten: **Aber** wir wollen uns das ganze erstmal ohne Web Server auf der *Console* ansehen: + !!!plain_text $> rails console irb> Message => Message(id: integer, content: string, created_at: datetime, updated_at: datetime) @@ -110,6 +114,44 @@ ansehen: => true irb> message => # + +#### Einrichten der Rails Console + +Die Console lässt sich um einige Gimmicks erweitern, wie etwa +Syntax-Highlighting. Eine weitere sinnvolle Anpassung ist die Umleitung der +Logausgaben auf STDOUT. So kann man direkt in der Console sehen wie etwa die +SQL-Query aussieht, die von Rails generiert wird. Um dass zu erreichen, müssen +zwei kleine Gems (Ruby Bibliotheken) installiert werden: + + !!!plain_text + $> gem install wirble + $> gem install utility_belt + +Danach muss noch eine `.irbrc`-Datei im Home-Verzeihnis angelegt mit folgendem +Inhalt angelegt werden: + + # load libraries + require 'rubygems' + require 'wirble' + require 'utility_belt' + + # start wirble (with color) + Wirble.init + Wirble.colorize + + IRB.conf[:SAVE_HISTORY] = 100 + IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history" + + if Object.const_defined?('Rails') + Rails.logger.instance_variable_set("@log", STDOUT) + end + +Anschließend müssen nun noch die zuvor installierten Gems im Rails-Projekt +hinzugefügt werden. Dazu einfach folgende Zeilen im `Gemfile` des +Rails-Projektes hinzufügen: + + gem "wirble" + gem "utility_belt" ### Konventionen @@ -131,6 +173,7 @@ In der Routing-Datei (`config/routes.rb`) muss zu diesem Zeitpunkt nichts weiter `root`-Route anzulegen. Diese beschreibt welche Action mit dazugehörigem Controller beim Aufruf der Haupt-URL (`http://localhost:3000`) aufgerufen werden soll. Dazu fügt man folgenden Code am Ende der Routing-Definition ein: + !!!ruby_on_rails route :to => "#" ### Migrationen diff --git a/site/handson/ruby.md b/site/handson/ruby.md index fa06f1e..93b082f 100644 --- a/site/handson/ruby.md +++ b/site/handson/ruby.md @@ -31,11 +31,13 @@ Program"](http://pine.fm/LearnToProgram/ "Learn to Program, by Chris Pine") von * [Rails Searchable API Doc](http://railsapi.com/ "Rails Searchable API Doc") * [Ruby Quick Reference Card](http://www.scribd.com/doc/7991776/Refcard-30-Essential-Ruby "Refcard #30: Essential Ruby") +* [Ruby Essentials](ruby_essentials.html "Ruby Essentials") ## Shortcuts ### Environment Setup + !!!plain_text export PATH=/usr/local/bin:$PATH ### Strings @@ -64,7 +66,7 @@ Program"](http://pine.fm/LearnToProgram/ "Learn to Program, by Chris Pine") von 4.times { |n| puts n } - while i < 10 + while i < 10 # do something end diff --git a/site/handson/ruby_essentials.md b/site/handson/ruby_essentials.md new file mode 100644 index 0000000..5f33fa9 --- /dev/null +++ b/site/handson/ruby_essentials.md @@ -0,0 +1,380 @@ +# Tag 1, Hands-On 1.1: Ruby Essentials + +## Ziel + +Hier zeigen wir ein paar ausführlichere Ruby Beispiel und gehen noch mal auf +die zentralen Konzepte der Objekt-Orientierung ein. + +## Konzepte der OOP + +### Abstraktion + +Jedes Objekt im System kann als ein abstraktes Modell eines Akteurs +betrachtet werden, der Aufträge erledigen, seinen Zustand berichten und +ändern und mit den anderen Objekten im System kommunizieren kann, ohne +offenlegen zu müssen, wie diese Fähigkeiten implementiert sind (vgl. +abstrakter Datentyp (ADT)). Solche Abstraktionen sind entweder Klassen (in +der klassenbasierten Objektorientierung) oder Prototypen (in der +prototypbasierten Programmierung). + +* *Klasse* Die Datenstruktur eines Objekts wird durch die Attribute (auch + Eigenschaften) seiner Klassendefinition festgelegt. Das Verhalten des + Objekts wird von den Methodern der Klasse bestimmt. Klassen können von + anderen Klassen abgeleitet werden (Vererbung). Dabei erbt die Klasse die + Datenstruktur (Attribute) und die Methoden von der vererbenden Klasse + (Basisklasse). +* *Prototype* Objekte werden durch das Clonen bereits existierender Objekte + erzeugt und können anderen Objekten als Prototypen dienen und damit ihre + eigenen Methoden zur Wiederverwendung zur Verfügung stellen, wobei die + neuen Objekte nur die Unterschiede zu ihrem Prototypen-Objekt definieren + müssen.. + +### Datenkapselung + +Als Datenkapselung bezeichnet man in der Programmierung das Verbergen von +Implementierungsdetails. Der direkte Zugriff auf die interne Datenstruktur +wird unterbunden und erfolgt statt dessen über definierte Schnittstellen. +Objekte können den internen Zustand anderer Objekte nicht in unerwarteter +Weise lesen oder ändern. Ein Objekt hat eine Schnittstelle, die darüber +bestimmt, auf welche Weise mit dem Objekt interagiert werden kann. Dies +verhindert das Umgehen von Invarianten des Programms. + +### Polymorphie + +Verschiedene Objekte können auf die gleiche Nachricht unterschiedlich +reagieren. Wird die Zuordnung einer Nachricht zur Reaktion auf die Nachricht +erst zur Laufzeit aufgelöst, dann wird dies auch späte Bindung genannt. + +### Feedback + +Verschiedene Objekte kommunizieren über einen +Nachricht-Antwort-Mechanismus, der zu Veränderungen in den Objekten führt +und neue Nachrichtenaufrufe erzeugt. Dafür steht die Kopplung als Index +für den Grad des Feedback. + +### Vererbung + +Vererbung heißt vereinfacht, dass eine abgeleitete Klasse die Methoden und +Attribute der Basisklasse ebenfalls besitzt, also „erbt“. Somit kann die +abgeleitete Klasse auch darauf zugreifen. Neue Arten von Objekten können +auf der Basis bereits vorhandener Objekt-Definitionen festgelegt werden. Es +können neue Bestandteile hinzugenommen werden oder vorhandene überlagert +werden. Wird keine Vererbung zugelassen, so spricht man zur Unterscheidung +oft auch von objektbasierter Programmierung. + +## Everything is an Object! + +### Die Basics + + "Alles sind Objekte".class # String + 6.class # Fixnum + (5.0).class # Float + + # achja ... + "test".class.class # Class + + # Alles sind Objekte?! Ja alles! + 7.+(7) + m = 7.method('+') + m.class + m.call(7) + # ... Wirklich alles ist ein Objekt! + + # Wissenwertes: Call-by-Reference + def modify_arg(arg) + arg.clone << rand(40) + end + arg << rand(40) + # Kann mit der +clone+ Methode verhindert werden. + array = [12,34,54] + modify_arg(array) + array + +### Dynamische Typisierung und starke Bindung + + my_string = "7" + my_string.class # String + + number = 7 + number.class # Fixnum + + number + my_string # TypeError + number.to_s + my_string # '7hello World' -> Explizites Casten nötig + + # in der irb ... + self.class + # -> Es gibt immer ein 'Root'-Objekt + +### Strings + + # vielleicht so ... + anzahl_schafe = 6 + new_string = 'Ich habe ' + anzahl_schafe.to_s + ' Schafe.' + # ... oder besser so! + new_string = "Ich habe #{anzahl_schafe} Schafe." + +### Symbole + + # Symbole als andere Repräsentation von Strings + jedi1 = {"firstname" => "Anikin", "lastname" => "Skywalker", "rank" => "Jedi Padawan"} + jedi2 = {"firstname" => "Obi-Wan", "lastname" => "Kenobi", "rank" => "Jedi High General"} + jedi3 = {:firstname => "Qui-Gon", :lastname => "Jinn", :rank => "Jedi Master"} + jedi4 = {:firstname => "Mace", :lastname => "Windu", :rank => "Jedi Master"} + + object_ids_of_keys = [] + + jedi1.keys.each { |key| object_ids_of_keys << key.object_id } + jedi2.keys.each { |key| object_ids_of_keys << key.object_id } + + puts object_ids_of_keys.uniq # => 85690, 85750, 85810, 85470, 85530, 85590 + object_ids_of_keys = [] + + jedi3.keys.each { |key| object_ids_of_keys << key.object_id } + jedi4.keys.each { |key| object_ids_of_keys << key.object_id } + + puts object_ids_of_keys.uniq # => 109858, 109938, 110018 + +### Datenstrukturen + + # In Arrays liegen die Daten sortiert und lassen sich über + # ihren Index abrufen + an_array = [1, 2, 3, 4] + an_array[1] + an_array_of_words = %w(Geige Gitarre Violine Bass) # so ... + an_array_of_words = ["Geige", "Gitarre", "Violine", "Bass"] # ... oder so + an_array_of_words[2] + + # In einem Hash liegen die Daten unsortiert und lassen sich + # über ihren +key+ abrufen + an_hash = { :author => "Dave Thomas", "title" => "Programming Ruby" } # Keys können sowohl Symbol als auch String sein + an_hash[:author] + an_hash['title'] + +#### Arbeiten mit Datenstrukturen + + # Iteratoren über Datenstrukturen (oder einfach alles was +Enumerable+ implementiert) + an_hash.each { |k,v| puts "Der Key ist: #{k} und der Value ist #{v}" } + an_array_of_words.each { |w| puts w } + +### Klassen + + class Rectangle + + attr_accessor :width, :height + + # attr_reader :width + # attr_writer :height + + # Konstruktor mit Parametern + def initialize(width, height) + # Zwei Instanzvariablen + @width, @height = width, height # Mehrfachzuweisung ;-) + end + + # # Getter/Setter - non-Ruby Style + # def width + # @width # Es wird IMMER der letzte Wert zurückgegeben, daher oft kein +return+ noetig! + # end + # + # def width=(width) + # @width = width + # end + # + # def height + # @height + # end + # + # def height=(height) + # @height = height + # end + + def area + height * width + end + + def to_s + "This is a #{self.class.name} with the dimension: #{width}x#{height}" + end + + protected + + def meine_protected_methode + puts "protected" + end + + private + + def meine_private_methode + puts "private" + end + + end + + # - Es gibt keine abstrakten Klassen! + # - Es gibt aber Vererbung ... + class Square < Rectangle + def initialize(width) + super(width, width) + end + + def width=(width) + self.height = width + self.width = width + end + + private + attr_writer :height + end + +### Variablen + + class String + def play + puts "playing song: #{self} ..." + end + end + + # Verwendung von Klassen-, Instanzvariablen und Konstanten + class MusicPlayer + + VENDOR_ID = "00:23:FE:89:EC:42" + + def initialize(playlist) + @playlist = playlist + end + + def self.overall_played_tracks + @@overall_played_tracks ||= 0 + end + + def play_track(track_number) + @playlist[track_number].play + @@overall_played_tracks += 1 + end + + end + + puts MusicPlayer::VENDOR_ID # => 00:23:FE:89:EC:42 + + mp = MusicPlayer.new(["Duality", "B.Y.O.B"]) + + puts MusicPlayer.overall_played_tracks # => 0 + mp.play_track(0) # => playing song: Duality ... + puts MusicPlayer.overall_played_tracks # => 1 + mp.play_track(1) # => playing song: B.Y.O.B ... + puts MusicPlayer.overall_played_tracks # => 2 + +### Mixins + + # Über Modules können zusätzliche Funktionalitäten + # in eine Klasse "gemixt" werden. + module MultiplierMixin + def multiply(times) + data * times + end + end + + class MyString + include MultiplierMixin + + def data + "foo" + end + end + + class MyInteger + include MultiplierMixin + + def data + 23 + end + end + + str = MyString.new + puts str.multiply(3) # => foofoofoo + + int = MyInteger.new + puts int.multiply(3) # => 69 + +### + +### Schleifen und Bedingungen + + for i in 1...10 + puts i + end + + 10.times {|i| puts i} + + a = rand(2) + if a == 0 + puts "Das war eine Null" + else + puts "Das war keine Null" + end + + puts a == 0 ? "Das war eine Null" : "Das war keine Null" + + while line = gets + puts "Ihre Eingabe: #{line}" + end + +### Blöcke und Procs + +Einfach: + + my_proc = Proc.new { puts 'hello world' } # Ohne Uebergabewert + my_proc.class + my_proc.call + + my_proc = Proc.new { |to| puts "hello #{to}" } # Mit Uebergabewert + my_proc.call 'ASS Kurs' + + # Sicherstellen, dass etwas vorher und hinterher gemacht wird. + + do_something = Proc.new { puts "Irgendwas was gemacht wird." } + + def ensure_important_stuff(any_proc) + puts "Sehr wichtige Sache, die vorher gemacht werden muss!" + any_proc.call + puts "Sehr wichtige Sache, die nachher gemacht werden muss!" + end + +Ein bisschen komplexer: + + # Proc Objekt + class Array + def my_find(find_proc) + for i in 0...size + return self[i] if find_proc.call(self[i]) + end + return nil + end + end + + puts [1,3,5,6,7].my_find Proc.new { |e| e.modulo(2) == 0 } + + # Block to Proc + class Array + def my_find(&find_block) + for i in 0...size + return self[i] if find_block.call(self[i]) + end + return nil + end + end + + puts [1,3,5,6,7].my_find { |e| e.modulo(2) == 0 } + + # Block via yield + class Array + def my_find + for i in 0...size + return self[i] if yield self[i] + end + return nil + end + end + + puts [1,3,5,6,7].my_find { |e| e.modulo(2) == 0 } + \ No newline at end of file diff --git a/site/handson/testing.md b/site/handson/testing.md index 5af9076..5408b69 100644 --- a/site/handson/testing.md +++ b/site/handson/testing.md @@ -115,6 +115,7 @@ specific Assertions") finden lassen. Beispiel: + !!!yaml # Dieser Datensatz muss in der test/fixtures/articles.yml Datei stehen # Auch gelten ganz normale Kommentare article_for_test: # Name des Datensatzes @@ -141,6 +142,7 @@ erwähnt sein, sich mit der Thematik näher zu beschäftigen. Beispiel: + !!!ruby_on_rails class ArticleTest < ActiveSupport::TestCase test "should have a valid title" do article = Article.new(:title => '', :author => users(:jessie)) diff --git a/site/handson/validations.md b/site/handson/validations.md index 3527339..fdc4fbf 100644 --- a/site/handson/validations.md +++ b/site/handson/validations.md @@ -35,6 +35,7 @@ dann sollen ihn entsprechende Fehler darauf aufmerksam machen. ### Einige Standardvalidierungen: + !!!ruby_on_rails validates_presence_of :first_name, :last_name # Attribute 'first_name' und 'last_name' dürfen nicht leer sein validates_uniqueness_of :username # Attribut 'username' muss einzigartig sein validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i # Attribut 'email' muss dem angegebenen Format entsprechen @@ -47,6 +48,7 @@ dann sollen ihn entsprechende Fehler darauf aufmerksam machen. ### Eigene Validierung: + !!!ruby_on_rails def validate errors.add(birthday, "Who are you? Methusalem?") unless birthday < Time.parse("01-01-1900") end \ No newline at end of file diff --git a/site/index.md b/site/index.md index 042be4b..e403a0e 100644 --- a/site/index.md +++ b/site/index.md @@ -1,6 +1,8 @@ # ASS Anfängerkurs - +Hier stellen wir alle relevanten Kursmaterialien wie Hands-On Aufgaben und +Musterlösungen für die Teilnehmer des ASS Anfängerkurses "Ruby on Rails" +bereit. Der Kurs findet statt vom *07.04. – 09.04.2010*. ## Hands-On @@ -23,4 +25,16 @@ ## Musterlösungen -*Not yet ... ;-)* \ No newline at end of file +*Not yet ... ;-)* + +## Authors + + * Andreas Bade ([andi@galaxycats.com](mailto:Andreas Bade (andi@galaxycats.com))) + * Dirk Breuer ([dirk@galaxycats.com](mailto:Andreas Bade (andi@galaxycats.com))) + +## Download + +Alle Handouts und Musterlösungen können als +[ZIP](http://github.com/galaxycats/ASS-Beginner/zipball/master "zip") oder +[tar](http://github.com/galaxycats/ASS-Beginner/tarball/master "tar") Archive +runtergeladen werden. diff --git a/templates/base.html b/templates/base.html.erb similarity index 65% rename from templates/base.html rename to templates/base.html.erb index 16f1872..06835ad 100644 --- a/templates/base.html +++ b/templates/base.html.erb @@ -2,7 +2,9 @@ - <%= @options[:config]["title"] %> + ASS Anfänger Kurs – Kursmaterial + +