Donnerstag, 30. August 2012

Installation von CouchDB 1.2 unter Ubuntu 10.04 LTS


Leider bietet Ubuntu in der Version 10.04 in den offiziellen Paketquellen nur die Version 0.10.0 der CouchDB an.
Deshalb muss man sich die Version 1.2 der CouchDB aus den Sourcen selber kompilieren.

Der erste Schritt besteht daraus die notwendigen Abhängigkeiten zum Kompilieren der CouchDB zu installieren:

sudo apt-get build-dep couchdb

Danach entpackt man die Sourcen der CouchDB und wechselt in das extrahierte Verzeichnis per

tar -zxvf apache-couchdb-1.2.0.tar.gz && cd apache-couchdb-1.2.0

Der nächste Schritt besteht darin, dass configure Skript mit den zugehörigen Parametern ausführen.
Wichtig ist es unter Ubuntu 10.04 den LD_RUN_PATH als auch die Pfade zu den Bibliotheken und Headerfiles der Mozilla Javascript Runtime anzugeben. Ansonsten funktionieren keine Views in der CouchDB.

LD_RUN_PATH=/usr/lib/xulrunner-1.9.28 ./configure –with-js-lib=/usr/lib/xulrunner-devel-1.9.2.28/lib/ --with-js-include=/usr/lib/xulrunner-devel-1.9.1.5/include –prefix=<prefix>

Danach wird das Kompilieren der Sourcen gestartet:

LD_RUN_PATH=/usr/lib/xulrunner-1.9.28 make

Das Kompilieren bricht ab mit der Fehlermeldung:

./mochifmt.erl:none: error in parse transform 'eunit_autoexport'
In neueren Versionen der CouchDB braucht Mochiweb für die Installation erlang-eunit. Diese Abhängigkeit bestand noch nicht mit der Version des Paketes von Ubuntu. Also wird erlang-eunit über apt installiert und das Kompilieren erneut gestartet.

sudo apt-get install erlang-eunit
LD_RUN_PATH=/usr/lib/xulrunner-1.9.28 make

Danach werden die übersetzen Dateien noch in das Zielverzeichnis (prefix) kopiert:
make install

Hiernach sollte man überprüfen ob die Datei <couchdb Verzeichnis>/lib/couchdb/bin/couchjs auch gegen die Mozilla Bibliothek libmozjs.so gelinkt ist:

ldd couchdb/lib/couchdb/bin/couchjs
linux-vdso.so.1 => (0x00007fffabdff000)
libcurl.so.4 => /usr/lib/libcurl.so.4 (0x00007f4f7929f000)
libmozjs.so => /usr/lib/xulrunner-1.9.2.28/libmozjs.so (0x00007f4f78f85000)
libm.so.6 => /lib/libm.so.6 (0x00007f4f78d01000)
libcrypt.so.1 => /lib/libcrypt.so.1 (0x00007f4f78ac8000)
libc.so.6 => /lib/libc.so.6 (0x00007f4f78745000)
libidn.so.11 => /usr/lib/libidn.so.11 (0x00007f4f78511000)
liblber-2.4.so.2 => /usr/lib/liblber-2.4.so.2 (0x00007f4f78303000)
libldap_r-2.4.so.2 => /usr/lib/libldap_r-2.4.so.2 (0x00007f4f780b7000)
librt.so.1 => /lib/librt.so.1 (0x00007f4f77eae000)
libgssapi_krb5.so.2 => /usr/lib/libgssapi_krb5.so.2 (0x00007f4f77c7a000)

Nun hat man fast das Ziel erreicht und es muss nur noch die CouchDB gestartet werden.
Beim Starten der CouchDB wird nur eine Fehlermeldung angezeigt und der Prozess beendet sich wieder

{"init terminating in do_boot",{{badmatch,{error,{"no such file or directory","os_mon.app"}}},[{couch,start,0},{init,start_it,1},{init,start_em,1}]}}

Also installieren wir auch noch erlang-os-mon

sudo apt-get install erlang-os-mon

und haben nun endlich unserer Ziel erreicht.
Eine lauffähige CouchDB 1.2.0 unter Ubuntu 10.04






Montag, 27. August 2012

Administrationsoberfläche ActiveAdmin für Rails Applikationen

Abstract

Etwas größere Web-Applikationen erfordern oft einen Administrationsbereich, in welchem z.B. Texte, Produkte oder Bilder erstellt, angezeigt, bearbeitet oder gelöscht (CRUD) werden können sollen. Für Rails Applikationen, die ActiveRecord als ORM nutzen, bietet sich die Nutzung des dafür hervorragend geeigneten Tools ActiveAdmin von Greg Bell an. Im Folgenden werden ActiveAdmin und einige Anpassungs-Möglichkeiten vorgestellt.

Die Beispielapplikation kann unter https://github.com/andywenk/active_admin_example bezogen und installiert werden.

Einfache Beispiel Applikation

Als Beispiel soll hier eine ganz einfach und unvollständige Shop-Applikation dienen. Um diese zu erstellen, werden nur wenige Schritte benötigt. Für die ActiveRecord Objekte wird der komfortable Rails Generator genutzt, der auch gleichzeitig für die AR-Objekte eine Migrationsdatei anlegt:

Zusätzlich werden noch Beziehungen zwischen den Objekten product, order, order_item und user hergestellt. Dazu editieren wir jeweils die Objekt-Dateien in app/models:

Wichtig ist hier die Erstellung der Beziehung zwischen einer Order, den dazugehörigen OrderItems und einem User: ein User kann viele Orders haben (has_many :orders). Eine Order gehört hierbei exakt zu einem User (belongs_to :user). Eine Order kann wiederum viel OrderItems haben (has_many :order_items). Und schliesslich gehört ein OrderItem exakt zu einer Order (belongs_to :order).

ActiveAdmin erkennt diese Beziehungen und gibt diese entsprechend in der Administrationsoberfläche bei den einzelnen Objekten als DropDown aus. Es zeigt sich also wieder wie wichtig ein gutes Datenbankmodell und die Verknüpfungen der einzelnen Objekte in einer Applikation sind.

Einbindung in ein Applikation

Da es sich um eine Rails 3.2.x Applikation handelt, ist die Einbindung von ActiveAdmin sehr einfach. Im Gemfile der Applikation werden folgende Einträge hinzugefügt:

  gem 'activeadmin'
  gem 'meta_search'

Nachdem die Einträge vorgenommen sind, werden der Applikation mit dem Befehl bundle install die entsprechenden gems zur Verfügung gestellt.

Das gem 'meta_search' nutzt ActiveAdmin für die Suche innerhalb der Oberfläche. Im nächsten Schritt wird nun ActiveAdmin installiert, wobei hier auch Datenbank Migrationen erstell werden. Das bedeutet, dass der entsprechende rake task ebenfalls noch mal ausgeführt werden muss. Ist dies auch geschehen kann die Applikation gestartet werden und unter http://localhost:3000 aufgerufen werden:

Grundlegendes Arbeiten mit dem Administrationsbereich

Der Administrationsbereich kann nun unter http://localhost:3000/admin aufgerufen werden und mit den Zugangsdaten User: admin@example.com, Password: password betreten werden

Auf dem Bild ist das grundlegende Interface (das Dashboard) von ActiveAdmin zu sehen. Allerdings befindet sich dort noch nichts administrierbares. Das wird geändert, indem die vier ActiveRecord Objekte user, product, order und order_item als Ressource hinzugefügt werden:

Wird die Seite aktualisiert, erscheinen oben in der horizontalen Navigation die Einträge Orders, OrderItems, Products und Users. Um beispielsweise Produkte anzulegen, begibt man sich in den Bereich "Products" und klickt rechts oben den Schalter "New Product". Die folgende Seite enthält ein Formular mit den Eingabefeldern für die Attribute des Product Objektes. Für weitere Anschauungen sollten einfach ein User, ein paar Produkte und eine Order mit ein paar OrderItems erstellt werden.

Nachdem die Datensätze erstellt wurden, leitet ActiveAdmin den Anwender auf die Ansichtsseite (show). Per Klick auf den Navigationspunkt "Products" gelangt man zur Übersicht und sieht dort eine Liste der erstellten Datensätze. Ausserdem bietet ActiveAdmin natürlich "pagination" bei vielen Datensätzen und auf der rechten Seite eine Suchmaske an.

Wie hier beschrieben wurden alle Datensätze über ActiveAdmin Interface erstellt. Es versteht sich von selbst, dass die Datensätze natürlich auch über jeglichen anderen Weg eingegeben werden können und per ActiveAdmin verwaltet werden können - z.B. über Import oder Benutzereingabe.

Anpassen der unterschiedlichen Ansichten

ActiveAdmin bietet eine sehr einfache zu benutzende DSL (Domain Specific Language), um die Ansichten (Views) und Formulare (Form) anpassen zu können. Dies geschieht in einer Datei der jeweiligen Seite die unter app/admin zu finden ist:

  active_admin_example
    app
      admin
        dashboards.rb
        order_items.rb
        orders.rb
        products.rb
        users.rb

Wenn eine neue Order erstellt werden soll (was natürlich ein etwas konstruierter Fall ist, dies im Adminbereich zu tun), kann durch die Verknüpfung zwischen den Objekten user und order im Administrationsbereich ein User per dropdown ausgewählt werden. Leider wird hier aber eine Objektreferenz angeboten und nicht etwa der Vor- und Nachname des Benutzers:

Um dies zu ändern, wird das Formular unter Verwendung der DSL entsprechend in der Datei app/admin/orders.erb angepasst:

Der Aufbau des Codes in der Datei ist ein Block, welcher am ActiveAdmin Objekt zu erst einmal das Objekt Order registriert. Innerhalb des Blocks wird die Methode Form mit einem Block aufgerufen. Dieser Aufruf gehört zur DSL von ActiveAdmin. Wie später noch gezeigt wird, gibt es weitere Methoden wie show oder index.

ActiveAdmin nutzt für die Erstellung der Formulare die FormBuilder DSL formtastic.Innerhalb des form-Blocks wird nun die DSL von formtastic genutzt, wobei ein Eingabefeld für die user_id als DropDown (select) erstellt wird. Wichtig ist hier der Wert für den key :collection: mit diesem Code wird ein Array erstellt, welches key - value Paare enthält, die aus Nachname, Vorname als key und die user_id als value bestehen. Das ist exakt die Anzeige, die wir brauchen.

Wenn die Order erstellt wird, glangt man wieder zur Detailansicht dieses Datensatzes. Hier besteht für den Eintrag User nun das gleich Problem wie bei dem Formular. Dies lässt sich durch folgenden Eintrag in app/admin/orders.rb beheben:

Bei der Anzeige der Order Details wird wieder die ActiveAdmin DSL genutzt - diesmal die Methode show.

Was aber nutzt eine Order, die keine OrderItems enthält. Es sollen also OrderItems angelegt werden. Dies geschieht über den Navigationspunkt OrderItems und wiederum rechts per klick auf den Schalter New Order Item. Auch hier muss etwas angepasst werden. Die DropDowns Order und Product sollen eine OrderID und den Produktnamen anzeigen. Ausserdem soll die OrderItem Detailseite ebenfalls diese Werte wiedergeben. Der folgende Code bewerkstelligt dies in app/admin/order_items.rb.

Wenn der Link OrderItems in der horizontalen Navigation aufgerufen wird, gelangt man zur Liste aller OrderItems. Hervorragend wäre doch die Anzeige einer Spalte, zu welcher Order das Item gehört und welches Produkt es referenziert. Also wird app/admin/order_items.rb erweitert, in dem aus der ActiveAdmin DSL die Methode index genutzt wird:

Sehr gut gewählt ist hier die Bezeichnung column für die einzelnen Attribute im Gegensatz zu row bei der show Methode.

Um nun die ersten Schritte in ActiveAdmin rund zu machen, wird nochmal an den Orders gefeilt. In der Detailansicht sollte zum einen eine extra Zeile vorhanden sein, die alle OrderItems ausgibt und eine Zeile, die den Gesamtpreis der Order angibt. Dafür ist das Erweitern der Datei app/admin/orders.rb erforderlich um folgenden Code:

Für den Eintrage ORDER ITEMS ist Code erstellt worden, der in einer Variablen ordered Links zu den einzelnen OrderItems speichert, und diese dann in jeweils einer Zeile ausgibt. Im Eintrag "TOTAL" werden die Preise der einzelen OrderItemes multipliziert mit der jeweiligen Anzahl der Produkte aufsummiert. Letztich werden zu Anfang der show Methode in der Variablen items alle zu dieser Order gehörigen OrderItems gespeichert um Code Duplication zu vermeiden. Das Ergebnis ist eine sehr aussagekräftige Ansicht:

Ausblick

Die hier gegebene Einführung in ActiveAdmin streift ein paar Möglichkeiten um ActiveAdmin für die Belange einer Applikation anzupassen. Dies stellt aber bei weitem nicht das Ende der Fahnenstange dar. Unter anderem lässt sich das Design komplett per CSS anpassen. Weiterhin kann nicht nur auf eine tabellarische Darstellung der jeweiligen index Seite genutzt werden, sondern z.B. auch ein Grid. Natürlich ist ActiveAdmin auch komplett mehrsprachig einrichtbar und es gibt Möglichkeiten um die Rechte mehrere Administratoren unterschiedlich einzustellen.

Fazit

ActiveAdmin ist hervorragend geeignet, um sich das Erstellen eines Administrationsbereichs zu sparen. Das Tool bietet tiefgreifende Anpassungsmöglichkeiten und kommt mit einem schlichten, aber modernen Design daher. Der Autor des Posts nutzt das Tool in mehreren Applikationen und ist sehr zu frieden damit. Selbst das Erstellen von Seiten die nicht einem ActiveRecord Objekt zu Grunde liegen ist möglich. Es kann also nur jedem empfohlen werden, der den Bedarf eines Administrationsbereichs für seine Applikation hat

Resourcen

Donnerstag, 16. August 2012

Dynamisch StyleSheet Regeln schreiben

Um CSS Eigenschaften von DOM Elementen mit JavaScript nachträglich zu ändern, gibt es unterschiedliche Möglichkeiten. Zum einen können die Eigenschaften über das Style Attribut direkt adressiert und manipuliert werden, wodurch inline Styles geschrieben werden. Zum anderen können CSS Klassen hinzugefügt oder entfernt werden, wodurch die im StyleSheet definierten Regeln angewendet werden. Eine weitere Alternative ist das Editieren eines StyleSheet Dokuments mit JavaScript.

element.style


In vielen Fällen ist das direkte Anpassen einer CSS Eigenschaft eines Elements die offensichtlichste und einfachste Lösung. Dazu muss das Element lediglich selektiert werden und anschließend können die anzupassenden Eigenschaften an dessen Style Attribut gesetzt werden.
// native JavaScript
var elm = document.getElementById("elm");
elm.style.width = "100%";
// jQuery
var elm = $("#elm");
elm.css("width", "100%");
Die dadurch erzielten Änderungen finden sich direkt im DOM wieder.
<!-- before -->
<div id ="elm"></div>
<!-- after -->
<div id="elm" style="width:100%;"></div>
Mit jQuery können auch gleichzeitig mehre CSS Eigenschaften geändert werden.
elm.css({width: "100%", height: "auto"});
Werden immer nur einzelne Elemente auf diese Weise angepasst, ist dies eine bequeme und effektive Lösung. Sollen jedoch eine Vielzahl an Elementen angepasst werden, würden auch dementsprechend häufig DOM-Manipulationen durchgeführt, was verhältnismäßig langsam ist und sich negativ auf den Speicherbedarf auswirkt.

element.className


Durch Zuweisen und Entfernen von CSS Klassen an einem Element können auch mehrere vorher festgelegte Eigenschaften aus dem StyleSheet auf ein Element angewandt werden. Dies dient zum Beispiel für unterschiedlich definierte Zustände.
// native JavaScript
var elm = document.getElementById("elm");
elm.className += " active";
// jQuery
var elm = $("#elm");
elm.addClass("active");
Die dadurch erzielten Änderungen finden sich nur indirekt im DOM wieder. Der hinzugefügte Klassenname lässt eine Anpassung der CSS Eigenschaften vermuten, die konkreten Werte sind jedoch nur im StyleSheet selbst hinterlegt.
<!-- before -->
<div id="elm"></div>
<!-- after -->
<div id="elm" class=" active"></div>
Das Entfernen einer Klasse ist etwas aufwendiger, da andere bereits vorhandene Klassen nicht überschrieben werden sollen.
// native JavaScript
elm.className = elm.className.replace(/\bactive\b/, "");
// jQuery
elm.removeClass("active");
Dadurch können die DOM-Manipulationen minimiert werden, da die eine Klasse an einem Elternelement geschrieben werden kann und sich Regeln für dessen gesamte Kindelemente auswirken können. Ein Nachteil dieser Methode ist, dass alle Klassen bereits vorher im StyleSheet definiert sein müssen, wodurch damit keine dynamisch zu berechnenden Werte hinzufügen lassen. Für vordefinierte Zustände hingegen ist dies eine gute Lösung. So lassen sich zum Beispiel Elemente einer Navigation mit einer Klasse hervorheben.

stylesheet.insertRule


Eine weitere Möglichkeit ist das dynamische Schreiben von StyleSheet Regeln. Dadurch wird zum einen das DOM nicht manipuliert und zum anderen können auch mehrere Elemente gleichzeitig angepasst werden.

Um die geschriebenen StyleSheet Regeln auch wieder einfach löschen zu können, wird empfohlen eigenes leeres StyleSheet Dokument zu nutzen. Nicht mehr benötigte Regeln sollten nicht im StyleSheet Dokument bleiben und nur durch die Cascade überschrieben, sondern tatsächlich wieder entfernt werden. Ansonsten benötigt der Browser immer mehr Zeit bei jedem weiteren Rendervorgang zum Auswerten der Gewichtigkeit der erzeugten Regeln.
<!-- somewhere in the head section -->
<style type="text/css" id="sheet"></style>
In diesem Beispiel wird ein Style Tag und damit ein leeres StyleSheet Dokument direkt im Markup definiert. Theoretisch könnte dieses auch dynamisch mit JavaScript erzeugt und anschließen dem DOM hinzufügen werden, aber gerade die DOM Interaktion soll ja reduziert werden.
var sheet = document.getElementById("sheet").sheet || document.styleSheets.sheet;
var selector = "#elm";
var rulestr = "width:100%;height:auto;";
if (sheet.insertRule) {
  sheet.cssRules.length && sheet.deleteRule(0);
  sheet.insertRule(selector + "{" + rulestr + "}", 0);
} else {
  sheet.cssRules && sheet.cssRules.length && sheet.removeRule();
  sheet.addRule(selector, rulestr);
}
Auf das StyleSheet Dokument kann mithilfe dessen DOM ID zugegriffen werden. Leider ist die Syntax zum Editieren von Regeln nicht in allen Browsern einheitlich. Mit insertRule bzw. addRule können Regeln hinzufügt und mit deleteRule bzw. removeRule auch wieder entfernt werden. An welche Stelle die Regel hinzugefügt oder welche Regel gelöscht werden soll, kann mithilfe des Indexes angegeben werden. Der Selektor kann den gleichen komplexen Aufbau wie eine übliche CSS Regel haben. Mit der Ausnahme, dass im IE nur ein Selektor und nicht mehrere durch Kommas getrennte Selektoren in einer neuen Regel verwendet werden darf.

Anwendungsbeispiel


Ein Beispiel für das Schreiben dynamischer Styleregeln ist hier hinterlegt. Das Beispiel zeigt fünf nebeneinander angeordnete Bilder, welche immer auf die maximale Größe skaliert werden, ohne dabei deren Seitenverhältnis zu zerstören. Dazu müssen die Bilder entweder in der Breite oder in der Höhe aufgezogen werden. Zusätzlich werden sie zentriert, um den Bildmittelpunkt zu bewahren.

In dieser Form ist dies nicht über CSS Klassen abzubilden, da die Werte von der Browsergröße abhängig sind und somit nicht vorher definiert werden können, sondern berechnet werden müssen. Würde dieses Verhalten mit Style Attributen realisiert werden, müssten bei einer Änderung der Browsergröße die Styles jedes einzelnen Bildes immer wieder neu geschrieben werden, wodurch sehr viele DOM-Manipulationen nötig wären.

Stattdessen ist es viel performanter, die Regel für die Skalierung und Zentrierung aller Bilder dynamisch in ein StyleSheet Dokument zu schreiben. Dadurch wird das DOM nicht manipuliert und es kann sichergestellt werden, dass der Browser auch nur einen Repaint für das Skalieren aller Bilder vornehmen muss.

Die Issue-Telco

Ein Projekt kann aus vielfältigen Gründen in Schwierigkeiten geraten. Um so wichtiger ist, dass der Auftraggeber seinen Mitwirkungspflichten nachkommt und durch Beistellleistungen das Projektteam unterstützt.

Ein Mittel die Mitwirkungspflichten des Auftraggebers nachhaltig einzufordern ist die Issue Telco. Wie der Name schon sagt, geht es darum, in einer Telefonkonferenz Fragen und Probleme zum Projekt zu erörtern. Allerdings werden in dieser Telko nicht die Fragen und Probleme des Auftraggebers, sondern die des Auftragnehmers erörtert.

Themen sind also regelmäßig nicht Fehler in der vom Auftragnehmer gelieferten Software (ich gehe von Softwareprojekten aus), sondern zum Beispiel um Fehler des vom Kunden bereitgestellten Backend-Systems, oder um Unklarheiten und Widersprüche in den Anforderungen. Für die Probleme des Auftraggebers gibt es ja Ticket-Telco.

Die Issue-Telco gehört dem Auftragnehmer. Er bestimmt die Themen. Der Auftraggeber ist gehalten die an ihn gestellten Fragen zu beantworten und die Probleme auf seiner Seite zu lösen.

Damit die Issue-Telco gut funktioniert, sollten ein paar Spielregeln eingehalten werden:

  • Der Auftragnehmer läd ein und führt das Protokoll, die Issue-List.
  • Der Auftragnehmer bringt seine Issues auf die Liste.
  • Auf Auftragnehmerseite sind mindestens der Projektleiter und ggf. ein oder mehrere Experten zugegen.
  • Der Auftraggeber stellt einen festen Ansprechpartner für die Telco, der ggf. Experten und Verantwortliche auf Auftraggeberseite benennt und dazu läd.
  • Die Issue-Telco findet regelmäßig z.B. täglich Dienstags bis Freitags statt. 
  • Jedes neue Issue wird mit Datum und kurzer Problembeschreibung in der Issue-List festgehalten. Die Issue-List wird von Telco-Termin zu Telco-Termin fortgeschrieben.
  • Die Issue-Telco sollte durch ein Steering-Meeting initiiert und dabei die Verantwortlichen auf beiden Seiten benannt werden.

Der Ablauf der Issue-Telco kann wie folgt gestaltet werden. Der Ansprechpartner auf Auftraggeberseite lässt sich die Issues erläutern. Er benennt während oder nach der Issue-Telco einen Zuständigen für jedes neue Issue auf Auftraggeberseite. Auch das wird in der Issue-List dokumentiert. 

In der nächsten Issue-Telco  werden alle protokollierten Issues der Reihe nach (das älteste zuerst) durchgegangen und der aktuelle Stand der Lösung in der Issue-List protokolliert. Danach werden alle neuen Issues erörtert und in die Issue-Liste aufgenommen.


Benötigt die Lösung eines Issues absehbar mehr als ein Tag Zeit, oder ist es nicht dringend, dann kann ein Wiederaufnahme-Datum in die Issue-List eingetragen werden. Die Erörterung dieses Issues wird dann bis dahin ausgesetzt.

Motivation

Ziel der Issue-Telco ist, den Auftraggeber mit denjenigen Problemen zu konfrontieren, die in seinem Einflussbereich liegen und zeitnah Abhilfe zu schaffen.

Die Motivation speist sich auf Auftragnehmerseite daraus, dass Verantwortung zum Auftraggeber delegiert wird und Lösungen geschaffen werden. Die Motivation auf Auftraggeberseite speist sich daraus, dass er in der Regel auf die Lieferung wartet und er deshalb ein Interesse daran hat, Hindernisse zu beseitigen.

Erfahrungen

Das Durchziehen der täglichen Issue-Telco kostet Zeit und Kraft. Arbeiten Auftraggeber und -nehmer konstruktiv zusammen, dann kann die Issue-Telco schnell gute Ergebnisse liefern, neues Vertrauen schaffen und eine angespannte Projektsituation deutlich verbessern.

Kommt der Auftraggeber seiner Mitwirkung an den Issues nicht nach, oder verweigert gar die Issue-Telco, dann liefert das handfeste Argumente in der vermutlich bereits stattfindenden Auseinandersetzung. Letzteres gilt auch für den Auftragnehmer.

Issues sollten so früh wie möglich in der Telco thematisiert werden. Selbst wenn die Ursache des Problems noch nicht erwiesenermaßen beim Auftraggeber liegt.Es ist durchaus legitim, später zurückzurudern: "Wir haben das Problem selbst lösen können".

Ein Issue sollte erst dann abgeschlossen werden, wenn es definitiv gelöst ist und nicht bereits, wenn der Auftraggeber angefangen hat daran zu arbeiten. Ab besten ist, wenn der Auftraggeber selbst feststellt, dass die zuvor fehlerhafte Funktion jetzt korrekte Ergebnisse liefert. 


Samstag, 11. August 2012

git submodules

Was sind git submodules?

  • git submodules sind externe Repositorys, die über einen festgelegten Pfad in ein anderes Repository (Projekt- oder Arbeitsverzeichnis) eingebunden werden
  • ermöglicht das Einbinden und damit die gemeinsame Nutzung von Code in verschiedenen Projekten
  • externe Änderungen in den eingebundenen Submodulen können übernommen werden
  • lokale Änderungen in den eingebundenen Submodulen können committed und an die entsprechenden externen Submodule Repositorys gepushed werden

Hinzufügen eines Submoduls


Submodule werden über den folgenden Aufruf in ein anderes Repository bzw. Projektverzeichnis eingebunden:

    git submodule add <repository> <path>

<repository> bezeichnet dabei die URL zum einzubindenden Submodul (origin)
<path> bezeichnet den Pfad/Namen des lokal anzulegenden Submodul-Verzeichnisses. Die Angabe ist optional. Fehlt sie, wird der Verzeichnisname aus der URL übernommen.

    $ cd ~/tmp/developer1/myproject
    $ git submodule add ~/tmp/public/global.git
    Initialized empty Git repository in 
       /Users/simjos/tmp/developer1/myproject/global/.git/

Im lokalen Projektverzeichnis myproject befindet sich jetzt das global-Submodul im Unterverzeichnis global. In diesem Verzeichnis können wie im normalen Projektverzeichnis Änderungen gemacht werden, externe Änderungen im Original-Repository via git fetch und git merge geholt und eingebunden werden und schließlich auch gepushed werden.


Wo sehe ich, welche Submodule verwendet werden?


Mit dem Hinzufügen eines Submoduls wird die Datei .gitmodules im Projektverzeichnis angelegt:

    $ git status
    # On branch master
    #
    # Initial commit
    #
    # Changes to be committed:
    #   (use "git rm --cached <file>..." to unstage)
    #
    # new file: .gitmodules
    # new file: global

.gitmodules ist eine Konfigurationsdatei, in der die URL des externen Submodule Repository und der Pfad des lokalen Unterverzeichnisses eingetragen werden.

    $ vi .gitmodules
    [submodule "global"]
      path = global
      url = /Users/simjos/tmp/public/global.git

Werden mehrere Submodule verwendet, erscheinen hier entsprechend mehrere Einträge. Ebenso wie die .gitignore-Datei ist .gitmodules versions-kontrolliert, d. h. sie wird von git getracked und mit dem Projektverzeichnis gepushed und gepulled. Wird das Projekt an anderer Stelle ausgechecked, werden die entsprechenden Submodule automatisch mit eingebunden.


Wie sieht git Submodule?


Wichtig bei der Verwendung von Submodulen ist das Verständnis, wie git eingebundene Submodule sieht bzw. tracked.

Für den Anwender erscheinen Submodule wie normale Unterverzeichnisse im aktuellen Projektverzeichnis. git erkennt und behandelt diese Verzeichnisse aber als Submodule, deren Inhalte nicht getracked werden. Git tracked stattdessen den commit-Punkt des Submoduls.

Die besondere Behandlung als Submodule kann man anhand des Modus (mode) erkennen, der im git-Index eingetragen wird. Normale Dateien und Verzeichnisse erscheinen hier mit mode 100644, Submodule mit mode 160000.

    $ git commit -a -m 'submodule added'
    Created commit 382c3c5: submodule added
     2 files changed, 4 insertions(+), 0 deletions(-)
     create mode 100644 .gitmodules
     create mode 160000 global
   
Sieht man sich den git-Index noch einmal direkt an, ist das Submodule mit dem Modus 160000 und dem SHA-1-Hash 382c3c5 eingetragen. Dieser commit-Punkt bezieht sich auf das externe Submodule-Repository, er existiert nicht im aktuellen Projektverzeichnis. Für das Projektverzeichnis ist damit immer genau festgelegt, mit welchem commit ein Submodule eingebunden ist (ermöglicht exakte Wiederherstellung einer Umgebung z.B. beim Klonen des Projekts, eine symbolische Referenzierung wie z.B. master ist daher allerdings nicht möglich).

     $ git ls-files --stage
     100644 5a199dd569f1412adbe273a3789969fcfc04b123 0  .gitmodules
     160000 382c3c560f661df4fe9c1b31bb8d291721977949 0  global
    
Vorsicht: Aufgrund dieses besonderen Trackings werden lokal im Submodule vorgenommene Änderungen nur angezeigt, wenn man sich im Submodule-Verzeichnis befindet. Erst wenn die lokalen Änderungen im Submodule committed und gepushed werden, erkennt das Projektverzeichnis, dass sich der HEAD des Submoduls geändert hat. Die geänderte Version des Submoduls kann dann via git add und git commit in das Projektverzeichnis übernommen werden.


Klonen eines Projekts mit Submodulen


    $ cd ~/tmp/developer2/myproject
    $ git clone ~/tmp/public/myproject.git
    Initialized empty Git repository in 
       /Users/simjos/tmp/developer2/myproject/.git/

Wird ein bestehende Projekt mit git clone <repository> ausgecheckt, wird das Projektverzeichnis einschließlich der Submodul-Verzeichnisses angelegt. Diese Submodul-Verzeichnisse enthalten allerdings noch keine Dateien. Um die eigentlichen Dateien zu bekommen, müssen zwei weitere Aufrufe gemacht werden:

1. Schritt: git submodule init initialisiert das bzw. die lokalen Submodule-Verzeichnisse:

    git submodule init
    Submodule 'global' (/Users/simjos/tmp/public/global.git) 
       registered for path 'global'
   
2. Schritt: git submodule update holt bzw. checked die Daten/Dateien des Submodule-Repository zu dem im übergeordneten Projekt definierten commit-Punkts aus (aus diesem Grunde befindet man sich lokal auch nicht auf einem bestimmmten Branch des Submoduls)

    $ git submodule update
    Initialized empty Git repository in 
       /Users/simjos/tmp/developer2/myproject/global/.git/
    Submodule path 'global': checked out 
       '3e07eb13a116e712b3edfa71ebe4c1e15e79fb8e'
   
    $ cd global
    $ git branch
    * (no branch)
      master


Lokale Änderungen in eingebundenen Submodulen


Sollen in den eingebundenen Submodulen Änderungen gemacht werden, sollte zuerst ein bestimmter Branch, i.d.R. master ausgecheckt werden.

    $ git checkout master
    Switched to branch 'master'

Nach erfolgten Änderungen werden diese committed und zum Submodule-Repository gepushed:

    $ git commit -a -m "updated the submodule"
   
    $ git push
    Counting objects: 5, done.
    Writing objects: 100% (3/3), 284 bytes, done.
    Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    To /Users/simjos/tmp/public/global.git
       3e07eb1..a05c3bd  master -> master
      
Anschließend werden die Änderungen ins Projekt-Repository übernommen. Sprich, der commit-Punkt, mit dem das Submodul eingebunden ist wird akutalisiert (hier von 3e07eb1 auf a05c3bd)
      
    $ cd ..
    $ git st
    # On branch master
    # Changed but not updated:
    #   (use "git add <file>..." to update what will be committed)
    #   (use "git checkout -- <file>..." to discard changes in working directory)
    #
    # modified:   global
    #
    no changes added to commit 
       (use "git add" and/or "git commit -a")

    $ git commit -a -m 'upated submodule a'
    [master ea532ad] upated submodule a
    1 files changed, 1 insertions(+), 1 deletions(-)

    $ git show
    commit ea532addc5d24a0ea53dd762708f09f805a28757
    Author: Simone Joswig <simone.joswig@sinnerschrader.com>
    Date:   Sat Aug 11 10:29:34 2012 +0200

       upated submodule global

    diff --git a/global b/global
    index 3e07eb1..a05c3bd 160000
    --- a/global
    +++ b/global
    @@ -1 +1 @@
    -Subproject commit 3e07eb13a116e712b3edfa71ebe4c1e15e79fb8e
    +Subproject commit a05c3bda4a35aec1f3a761d1c674873369ceda80

    $ git push
    Counting objects: 3, done.
    Delta compression using up to 2 threads.
    Compressing objects: 100% (2/2), done.
    Writing objects: 100% (2/2), 337 bytes, done.
    Total 2 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (2/2), done.
    To /Users/simjos/tmp/public/myproject.git
      26312b5..ea532ad  master -> master


Externe Änderungen in eingebundenen Submodulen


Wurden von anderer Seite Änderungen in den Submodulen vorgenommen, erscheinen diese bei Aktualisierung des Projekts in dem sie eingebunden sind:

    $ cd ~/tmp/developer1/myproject
    $ git pull
    remote: Counting objects: 3, done.
    remote: Compressing objects: 100% (2/2), done.
    remote: Total 2 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (2/2), done.
    From /Users/simjos/tmp/public/myproject
       26312b5..ea532ad  master     -> origin/master
    Updating 26312b5..ea532ad
    Fast-forward
     global |    2 +-
     1 files changed, 1 insertions(+), 1 deletions(-)

     $ git st
     # On branch master
     # Changed but not updated:
     #   (use "git add <file>..." to update what will be committed)
     #   (use "git checkout -- <file>..." 
             to discard changes in working directory)
     #
     #  modified:   global
     #
     no changes added to commit 
        (use "git add" and/or "git commit -a")

     $ ~/tmp/developer1/myproject(master)$ git show
     commit ea532addc5d24a0ea53dd762708f09f805a28757
     Author: Simone Joswig <simone.joswig@sinnerschrader.com>
     Date:   Sat Aug 11 10:29:34 2012 +0200
    
         upated submodule global
    
     diff --git a/global b/global
     index 3e07eb1..a05c3bd 160000
     --- a/global
     +++ b/global
     @@ -1 +1 @@
     -Subproject commit 3e07eb13a116e712b3edfa71ebe4c1e15e79fb8e
     +Subproject commit a05c3bda4a35aec1f3a761d1c674873369ceda80

Mit git submodule update wird das Submodule jetzt auf den im Projekt definierten neuen commit-Punkt a05c3bd aktualisiert:

    $ git submodule update
    remote: Counting objects: 5, done.
    remote: Total 3 (delta 0), reused 0 (delta 0)
    Unpacking objects: 100% (3/3), done.
    From /Users/simjos/tmp/public/global
       3e07eb1..a05c3bd  master     -> origin/master
    Submodule path 'global': checked out 
       'a05c3bda4a35aec1f3a761d1c674873369ceda80'


 Und zum Schluss noch einmal aufgepasst!


Lokale Änderungen des Submoduls nicht gepushed

Nach lokaler Änderung des Submoduls wurde diese Änderung zwar committed und im übergeordneten Projekt ebenfalls committed und gepushed, jedoch nicht ins externe Submodul-Projekt gepushed. Versuchen andere anschließend das Projekt zu aktualisieren, geht es nicht:
   
    $ git pull
    $ git submodule update
    error: pathspec 
       '261dfac35cb99d380eb966e102c1197139f7fa24' 
       did not match any file(s) known to git.
    Did you forget to 'git add'?
    Unable to checkout  
       '261dfac35cb99d380eb966e102c1197139f7fa24' 
       in submodule path 'global'


git submodule update bei lokalen Änderungen im Submodul

Vorsicht beim Aufruf von git submodule update: lokale Änderungen in eingebundenen Submodulen werden dadurch ohne Warnung überschrieben.


Donnerstag, 14. Juni 2012

google-blockly


Es gibt jetzt die Möglichkeit Anwendungen mittels Drag & Drop zu programmieren. 
Unter http://code.google.com/p/google-blockly/ findet man den Editor und einige Beispiele. Die Anwendung lässt sich umwandeln in Javascript, Python, Dart und XML. 
Die Benutzeroberfläche vom Editor ist  sehr einfach aufgebaut. Auf der linken Seite hat man seine Befehle (unterteilt in Hauptgruppen) und auf der rechten Seite können diese dann angeordnet werden. 
Mittels "Run Program" kann man seine Anwendung sofort ausführen.

Ein kurzes Beispiel zeigt ein Screenshot von dem Verfahren zur statistischen Bestimmung von PI (danke Wikipedia http://de.wikipedia.org/wiki/Kreiszahl#Statistische_Bestimmung).


und hier das Ergebnis in JS und Python

######JS######

var tropfenanzahl;
var innerhalb;
var gesamt;
var dotx;
var doty;

tropfenanzahl = 100000;
innerhalb = 0;
gesamt = tropfenanzahl;
while (tropfenanzahl > 0) {
  dotx = Math.random();
  doty = Math.random();
  if (((dotx * dotx) + (doty * doty)) <= 1) {
    innerhalb = innerhalb + 1;
  }
  tropfenanzahl = tropfenanzahl - 1;
}
window.alert(4 * (innerhalb / gesamt));

######Python#####

tropfenanzahl = None
innerhalb = None
gesamt = None
dotx = None
doty = None
import random

tropfenanzahl = 100000
innerhalb = 0
gesamt = tropfenanzahl
while tropfenanzahl > 0:
  dotx = random.random()
  doty = random.random()
  if ((dotx * dotx) + (doty * doty)) <= 1:
    innerhalb = innerhalb + 1

  tropfenanzahl = tropfenanzahl - 1

print 4 * (innerhalb / gesamt)

Google-blockly steht noch am Anfang. Es fehlt noch einiges was man zum Erstellen einer Anwendung braucht, aber auf jeden Fall sehr interessant.




Sonntag, 10. Juni 2012

Image Komprimierung mit dem Titanium Framework


Mit dem Titanium Framework von Appcelerator ist es (schnell) möglich eine native App für Android und iOS mit Javascript zu entwickeln.

Durch die im Titanium Framework vorhandene JS-Api hat man Zugriff auf die Elemente/Sensoren von dem Mobil-Device. Die Verwendung ist in der Dokumentation vom Titanium Framework ausreichend beschrieben.

Auf der Website von Appcelerator gibt es die kostenlose Community-Version, die Dokumentation und viele Videos.  Die Beispiel App Kitchensink zeigt die grundlegenden Möglichkeiten des Titanium Frameworks,  was die Einarbeitung einfach macht.

Es gibt die Möglichkeit beim Titanium Framework Module zu verwenden. Dazu wird der Modul-Code vollständig  gekapselt und kann dann im eigenen Code verwendet werden. Man findet Module auf der Website von Appcelerator und bei Git.

Wenn man Bilder mit der Kamera vom Mobile-Device aufnimmt und diese versenden möchte, bietet es sich an die Bilder vorher zu skalieren bzw. zu komprimieren. Da das Titanium Framework aber keine Image Komprimierung unterstützt bleibt nur die Möglichkeit dies selber zu programmieren oder man nutzt vorhandene Module.

Es gibt für Android und iOS unterschiedliche Module, die dies bereits können.

Das folgende Beispiel zeigt wie ein aufgenommenes Bild von der Kamera des Mobile-Device mit Unterstützung der Module komprimiert wird.

Titanium.Media.showCamera({
   success : function(event) {
     var image = event.media;

     if (event.mediaType == Ti.Media.MEDIA_TYPE_PHOTO) {

       if (Ti.Platform.osname == "android") {
         var JpegEncoder = require("com.novelys.jpegencoder");
         var first = JpegEncoder.createEncoder({
           imageBlob : image,
           compressionQuality : 85,
         });
         first.scaleAndEncode();
         image = first.imageBlob;
       }else if (Ti.Platform.osname == "iphone") {
         var jpgcompressor = require("com.sideshowcoder.jpgcompressor");
         jpgcompressor.setCompressSize(102400);
         jpgcompressor.setWorstCompressQuality(0.85);
         image = jpgcompressor.compress(image)
       }
      var imageView = Titanium.UI.createImageView({
         top : 2,
         width : "100%",
         image : image,
       });
      win.add(imageView);
     }
  },
   cancel : function() {
   },
   error : function(error) {
  },
   saveToPhotoGallery : true,
   allowEditing : true,
   mediaTypes : [Ti.Media.MEDIA_TYPE_PHOTO]
 });




Das Versenden des Bildes kann direkt im Anschluss realisiert werden. Dazu gibt es eigene Funktionen vom Titanium Framework.


var xhr = Titanium.Network.createHTTPClient();
xhr.onerror = function(e) {
  Ti.API.info('error ' + e.error);
};
xhr.onload = function() {
  Ti.API.info('upload ok');
};
xhr.open('POST', "URL");

var params = {
  'image' : image
};
xhr.send(params);