Monday, May 21, 2007

Styling on the fly

If you already worked your way into Markaby as the markup of choice in Camping it might be just some obvious stuff but I think it's worth some lines to show you a shorthand to style page-elements on the fly.

You normally include your CSS in a Style-class in the controller. This allows you to keep the code in the View clean and readable. Unfortunately, I often need to diversify a style of an element, e.g. modify it's width or it's backround-color, and I don't want to create a whole new CSS-class just for only one tiny change. So I use the short :style-modifier to get the result. To provide an example:


table.bright :style => "border: solid red; width: 70%" do
tr :style => "height: 20%" do
td "Hello", :style => "background: lightgreen"
td "World", :style => "border: think dotted blue"
end
end

But please don't use these short-hands too often! It clutters your code and makes it more difficult to maintain. They come in handy to add one or two style-mods but shouldn't replace your Style-controller.

Sunday, May 13, 2007

Redhanded shut down!

Oh no! _why closed his great blog on ruby, "Redhanded". As he sais, there aren't so many ruby-blogs closing at these times. Well, let's have look on his new one at hackety.org. I hope, he doesn't stop maintaining his great collection of apps including Camping.

Friday, May 11, 2007

form {}

Forms are the interface between the user and your web-application. They allow you to receive data from your users and process it. So they really deserve a closer look to enhance your apps.

The simplest version of a form is just a button which returned data you can use in a controller.


form :action => R(SomeController), :method => 'post' do
input :type => 'submit', :value => 'Submit'
end

Nothing special here. We just add an input-'device' and specify its properties like type and value (in this case the label of the button). But a button isn't enough to create a mighty user-interface. At least we need a possibility to type in some text. So let's add a textfield:

form :action => R(SomeController), :method => 'post' do
label 'Some text', :for => 'sometext'; br
input :type => 'text', :name => 'sometext'
input :type => 'submit', :value => 'Submit'
end

Now we can process the text.input in our controller with a post-method. You can access the the given data with input.somename. In our example we would use input.sometext to get the text-form input. The labels are used to add a description to a form-element. By the way, if you deal with password-input you don't want to see the passwor in plain text. There is a solution by using :type => 'password' for the input. You get a one-line textinput but every character is represented by a '*'.

If you have worked with HTML-forms you are probably familiar with some additional form-elements.
For example the textarea which you use for inputs over more than one line.
Two other important elements are the checkboxes, radio-buttons and selection-lists (often used as drop-down-menus. In the following example we use both of them just to get a feeling:

form :action => R(SomeController), :method => 'post' do
label 'Choose one', :for => 'chooser'; br
select :name => 'chooser', :size => 1 do
option "First"
option "Second"
option "Third"
end; br
div {[input(:type => 'radio', :name => 'radios',
:value => 'one'), "One"].join(" ")}
div {[input(:type => 'radio', :name => 'radios',
:value => 'two'), "Two"].join(" ")}
input :type => 'submit', :value => 'submit'
end

The div-statements seem a bit weird. And yes, I agree but didn't find a better way to implement them. If you find a more elegant way, leave a comment.

The last important thing is just some ruby-magic to modify a partial and it's form. So if you use a partial for a form and you want to use this partial for different views you need to modify e.g. the controller which will be called by the submit-button. So what you do is something like the following:

def foo
_form(@data, :action => R(Add))
end

def _form(data, opts)
form({method => 'post'}.merge(opts)) do
#some form elements
end
end

We call the partial and provide some data as an argument. In addition we define the controller called by the submit-button in the partial. Depending of your purpose of the partial you just modify the :action-parameter. The merge-method in _form is used to merge two hashes.

Monday, April 30, 2007

back and in

Sorry for such a long pause of posting, but some minor (Final Exams [Yeah, I finished school!]) and major (I visited Zurich and its ETH [Boah, a dream city!]) things hold me off camping.



So being back on track, we will move on with a look on the ActiveRecord-statements has_many and belong_to. These modifiers are added in the Model-declaration of your app:


class Link < Base; belongs_to :user; has_many :tags; end

I added these modifiers in the Grabr-app too; but I didn't use them (actually I forgot). So what are they good for? In the Grabr-case just in one line (correct me if I'm wrong), but in general has_many and belongs_to are a convenient feature to deal with your data. The following example shows how to use them:

class Link < Base
belongs_to :user
has_many :tags
end
class Tag < Base
end
class User < Base
has_many :links
end

user = User.find @state.user_id
links = user.links
name = links[1].user.username

Btw., I changed the layout a bit to avoid nasty linebreaks and obfuscation of my code.

The example code above is a bit shortened but should demonstrate the principle effect of belongs_to and has_many. In the case of has_many you get a method to access the "children" of a model. With belongs_to, obviously, you can move a step above and gain access to the parent-object and its properties. So it's easy to get all links of one user just writing user.links if you added a has_many to your model.
If you want to get further information on ActiveRecord and its syntax I recommend a look into the documentation.

Saturday, April 7, 2007

establish_connection :adapter => 'sqlite3'

As we have finished our first serious app there is some time now to deal with some environmental issues of camping. At first we should speak about the standard database used in camping, sqlite.

sqlite is kind of a little brother of mysql. While mysql is the big swiss knife with lots and lots of tools and features, sqlite concentrates on the leightweight and fast administration of your data. You can store your whole database in a small single file which can be easily transfered between different systems.

When you install sqlite on your system you get a commandline-tool to work with your databases. Just open your terminal of choice and type


> sqlite3 ARG

Maybe there is a different link in your /usr/bin/ or /opt/local/bin/ but you will probably find the command by just typing the first letters and then hit Tab.
As argument you give the database you want to work with. For example in the camping-standard-database in your home-directory:

> sqlite3 ~/.camping.db

Now you can work with your different tables. To get a first overview of them just type

sqlite> .tables

and hit enter. sqlite understands abbrevations too, so .ta is sufficient. To get an overview over all sqlite-info-commands, you use

sqlite> .help

As you can see every sqlite-command starts with a dot. Except... Yes, except all the other ones: the SQL-statements. The SQL-commands are the meat on the sqlite-bone. They are used to modifiy your data. You find introductions all over the net (e.g. here) as well as command-references.
So just the everyday stuff in short.

  • View the content of a table: to see all rows and cols of a table you use the select-command (sqlite isn't case-sensitive, so you can also write SELECT; NOTE: concerning table- or variable-names the case isn't ignored!)

    sqlite> select * from table_name;

    Replace table_name by the name of one of your tables. Note that in contrast to the sqlite-commands, the SQL-commands require finishing by a simcolon. The * is a wildcard representing all columns in the selected table. If you just want the id- and the username-column you must replace the wildcard with id, username. You also can search for certain values with the modificator where.

    sqlite> select * from table_name where id<4;

    nstead for the id you can search say a user called "Peter" with where username = "Peter".

  • Delete datasets/rows: a dataset in SQL is represented by one row in a certain table. If you want to delete a dataset aka row you use the delete-command:

    sqlite> delete from table_name where
    username="Peter";

    You select a certain dataset with where as already seen with the select-command. Of course you can modify your deleting with </>-operators and combinations of keys, e.g.

    sqlite> delete from table_name where
    age<7 and family_name="Bauer";

  • Delete a table: You can even delete a whole table with the drop-command

    sqlite> drop table table_name;

    Deleting a whole table is a rare job as you are changing the system behind your biz-logic of your app (see below)


At the end when you finished your work you can quit sqlite with .quit

Two notes at the end:
At the beginning I spoke about the portability of sqlite-databases. Now you might argue: well, but if the data of all my camping-apps are crammed into ~/.camping.db and I want to transfer the data of one app to another place(computer, server, disk) I will have to carry a lot of senseless data from all the other apps with me. Yes, you'r right. Well, almost, because there is a fix for this problem: just put one line in the create-method at the end of your camping file. There you add the following statement:

AppName::Models::Base.establish_connection(
:adapter => 'sqlite3',
:database => 'talkr.db')

Now camping uses a special *.db-file for your app where it stores the app-data exclusively. If you want to move your app just copy it along with your app and you are done. The file will be created in the directory where your app is located.
The second one: I told you the whole stuff about sqlite for emergency-usage only. If you inserted a wrong dataset or created a user with a stupid username you can easily fix these problems. But you shouldn't use them for doing all the data-stuff for your camping app. To avoid mixing data- and business-logic up, active-record and the mvc-design has been invented.
So keep camping (with only a few lines of SQL ;-)!

Monday, April 2, 2007

Summin' up

As we have finished our Grabr-app I'll give you the whole code in one listing. As in earlier posts the design of the code isn't optimal. But due to the layout of the whole blog I have to add line-breaks in the code to avoid ugly code-snippets.


require 'camping'
require 'camping/session'

Camping.goes :Grabr

module Grabr
include Camping::Session
end

module Grabr::Models
class Link < Base
belongs_to :user
has_many :tags
end
class Tag < Base; end
class User < Base; end

class CreateTheBasics < V 1.0
def self.up
create_table :grabr_links,
:force => true do |t|
t.column :id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :title, :string, :limit => 255
t.column :address, :string, :limit => 255
t.column :date, :datetime
t.column :description, :text
end
create_table :grabr_users,
:force => true do |t|
t.column :id, :integer, :null => false
t.column :username, :string
t.column :password, :string
end
User.create :username => 'admin',
:password => 'camping'
end
def self.down
drop_table :grabr_links
drop_table :grabr_users
end
end
end

module Grabr::Controllers
class Index < R '/'
def get
unless @state.user_id.blank?
@user = User.find @state.user_id
@links = Link.find(:all,
:conditions => {:user_id => @state.user_id})
end
render :index
end
end

class Add
def get
unless @state.user_id.blank?
@user = User.find @state.user_id
@link = Link.new :title => input.link_title,
:address => input.address,
:description => input.description,
:user_id => @state.user_id,
:date => Time.now
end
render :add
end
def post
unless @state.user_id.blank?
link = Link.create :title => input.link_title,
:address => input.address,
:description => input.description,
:user_id => @state.user_id,
:date => Time.now
redirect Index
end
end
end

class View < R '/view/(\d+)', '/view'
def get link_id
unless @state.user_id.blank?
@user = User.find @state.user_id
end
@link = Link.find link_id
render :view
end
def post
unless @state.user_id.blank?
@link = Link.find input.link_id
@link.update_attributes :title =>
input.link_title,
:address => input.address,
:description => input.description
redirect Index
end
end
end

class Delete < R '/delete/(\d+)', '/delete'
def get link_id
unless @state.user_id.blank?
@user = User.find @state.user_id
@link = Link.find link_id
render :delete
end
end
def post
unless @state.user_id.blank?
Link.delete(input.link_id)
redirect Index
else
redirect Index
end
end
end

class Login
def post
@user = User.find :first,
:conditions => ['username = ? AND
password = ?
',
input.username, input.password]

if @user
@login = 'login success !'
@state.user_id = @user.id
else
if input.user_add and not
input.password.empty? and not
input.username.empty?
@user = User.create :username =>
input.username,
:password => input.password
@login = 'Account successfully created!'
else
@login = 'Wrong user name or password!'
end
end
render :login
end
end

class Logout
def get
@state.user_id = nil
render :logout
end
end

class Style < R '/styles.css'
def get
@headers["Content-Type"] =
"text/css; charset=utf-8"
@body = %{
body {
background-color: #FFFFFF;
}
h1 {
background-color: #FFBDB0;
border-bottom: thin dotted darkgrey;
margin-bottom: 10px;
margin-top: 5px;
font-size: 20px;
}
h1.h {
background-color: #E9E8FF;
font-size: 25px;
}
h2 {
background-color: #FFFCBA;
font-size: 14px;
border-bottom: thin dotted darkgrey;
margin-bottom: 0px;
}
h2:hover {
background: #E9E8FF;
}
hr {
border: 1px dotted darkgrey;
}
a {
text-decoration:none;
background: #FFFCBA;
margin: 1px;
padding: 2px;
color: black;
}
a.h {
background: none;
}
a.nav {
font-size: 12px;
}
a.h:hover {
background: none;
}
a:hover {
background: #E9E8FF;
}
a:visited {
color: black;
}
p {
margin: 2px;
}
p.date {
font-size: 13px;
color: darkgrey;
}
textarea {
width: 500px;
}
input {
background: #FFFCBA;
margin: 5px;
}
table {
border-spacing: 10px;
}
td {
border-right: thin dotted darkgrey;
padding: 20px;
}
ul {
list-style-type: square;
}
li {
margin: 8px;
}
}
end
end
end

module Grabr::Views
def layout
html do
head do
title 'Grabr'
link :rel => 'stylesheet',
:type => 'text/css',
:href => '/styles.css', :media => 'screen'
end
body do
if @user
p do
[('You are logged in as \''+
@user.username+'\''),
a('Logout',
:href => R(Logout))].join(" | ")
end
end
h1.h { a.h 'Grabr Web-Aggregator',
:href => R(Index)}
self << yield
end
end
end

def index
if @user
h1 "Your Grabbed Links"
if @links.empty?
p 'You haven\'t grabbed anything yet!'; br
p {a 'Add a link now!', :href => R(Add)}
else
p {a 'Add a link now!', :href => R(Add)}
for link in @links.reverse
_entry(link)
end
end
else
h1 "Please login or create an account"
_login
end
end

def login
p {b @login}
p {a 'Continue', :href => R(Index)}
end

def logout
p 'You have been logged out'
p {a 'Continue', :href => R(Index)}
end

def add
if @user
_form(@link, :action => R(Add))
else
_login
end
end

def delete
p "Are you shure that you want"
" to delete the bookmark \'"+@link.title+"\'?"
br
form :action => R(Delete), :method => 'post' do
input :type => 'hidden',
:name => 'link_id', :value => @link.id
input :type => 'submit',
:name => 'delete', :value => 'Delete'
end
end

def view
if @user
_form(@link, :action => R(View))
else
_login
end
end
def _login
form :action => R(Login), :method => 'post' do
label 'Username', :for => 'username'
br
input :name => 'username', :type => 'text'
br

label 'Password', :for => 'password'
br
input :name => 'password', :type => 'text'
br

input :type => 'submit', :name => 'login',
:value => 'Login'
input :type => 'submit', :name => 'user_add',
:value => 'Create Account'
end
end

def _entry(link)
h2 {a.h link.title, :href => link.address}
p.date 'Added at '+
link.date.strftime("%d. %B %Y")
p link.description
p do
[a.nav('View', :href => R(View, link.id)),
a.nav('Delete', :href => R(Delete,
link.id))].join(" | ")
end
hr
end

def _form(link, opts)
form({:method => 'post'}.merge(opts)) do
label 'Title', :for => 'link_title'
br
input :name => 'link_title', :type => 'text',
:value => link.title
br

label 'Link-Address', :for => 'link_address'
br
textarea link.address, :name => 'address'
br

label 'Description', :for => 'description'
br
textarea link.description,
:name => 'description'
br

input :type => 'hidden',
:name => 'link_id', :value => link.id
input :type => 'submit', :value => 'Done!'
end
end
end

def Grabr.create
Camping::Models::Session.create_schema
Grabr::Models.create_schema :assume =>
(Grabr::Models::Link.table_exists? ? 1.0 : 0.0)
end

So have a lot of fun! Leave a comment if you want any special topic I should deal with in some later posts.

'post to grabr'

If you followed all the steps to build Grabr and played around a bit with adding, editing and deleting bookmarks you were probably annoyed by opening a new tab, opening localhost and then add a new bookmark manually. There is a remedy you come across by using e.g. delicious: a bookmark in your browserbar

This bookmark uses a bit of JavaScript-magic to transfer the current URL to Grabr.
The only thing you need to do is to drag the following link to your browsers quick-link-bar: post to grabr. Then try it out with this page and enjoy the ease of Grabr.

Let's have a view

So let's finish our bookmarking-app! Only the views are still missing.
Beginning with our layout:


def layout
html do
head do
title 'Grabr'
link :rel => 'stylesheet',
:type => 'text/css',
:href => '/styles.css', :media => 'screen'
end
body do
if @user
p do
[('You are logged in as \''+
@user.username+'\''),
a('Logout',
:href => R(Logout))].join(" | ")
end
end
h1.h { a.h 'Grabr Web-Aggregator',
:href => R(Index)}
self << yield
end
end
end

The if @user-statement adds a status-bar ontop of every page which indicates the user-status. If there is user logged in, its name appears and there is a link to log out quickly.
The next methods are just the standard stuff, nothing new nor spectacular.

def index
if @user
h1 "Your Grabbed Links"
if @links.empty?
p 'You haven\'t grabbed anything yet!'; br
p {a 'Add a link now!', :href => R(Add)}
else
p {a 'Add a link now!', :href => R(Add)}
for link in @links.reverse
_entry(link)
end
end
else
h1 "Please login or create an account"
_login
end
end

def login
p {b @login}
p {a 'Continue', :href => R(Index)}
end

def logout
p 'You have been logged out'
p {a 'Continue', :href => R(Index)}
end

def add
if @user
_form(@link, :action => R(Add))
else
_login
end
end

def delete
p "Are you shure that you want"
" to delete the bookmark \'"+@link.title+"\'?"
br
form :action => R(Delete), :method => 'post' do
input :type => 'hidden',
:name => 'link_id', :value => @link.id
input :type => 'submit',
:name => 'delete', :value => 'Delete'
end
end

def view
if @user
_form(@link, :action => R(View))
else
_login
end
end

Just standard stuff as I promised.
So lets add the partials:

def _login
form :action => R(Login), :method => 'post' do
label 'Username', :for => 'username'
br
input :name => 'username', :type => 'text'
br

label 'Password', :for => 'password'
br
input :name => 'password', :type => 'text'
br

input :type => 'submit', :name => 'login',
:value => 'Login'
input :type => 'submit', :name => 'user_add',
:value => 'Create Account'
end
end

def _entry(link)
h2 {a.h link.title, :href => link.address}
p.date 'Added at '+
link.date.strftime("%d. %B %Y")
p link.description
p do
[a.nav('View', :href => R(View, link.id)),
a.nav('Delete', :href => R(Delete,
link.id))].join(" | ")
end
hr
end

def _form(link, opts)
form({:method => 'post'}.merge(opts)) do
label 'Title', :for => 'link_title'
br
input :name => 'link_title', :type => 'text',
:value => link.title
br

label 'Link-Address', :for => 'link_address'
br
textarea link.address, :name => 'address'
br

label 'Description', :for => 'description'
br
textarea link.description,
:name => 'description'
br

input :type => 'hidden',
:name => 'link_id', :value => link.id
input :type => 'submit', :value => 'Done!'
end
end

The _entry-partial contains the informations of a stored bookmark, the _form is used for editing a posted link.
Last thing missing is the initialization of the database:

def Grabr.create
Camping::Models::Session.create_schema
Grabr::Models.create_schema :assume =>
(Grabr::Models::Link.table_exists? ? 1.0 : 0.0)
end

Yeah! We are finished! Well, almost, there are still some features to add (e.g. tags). But for now we have a working bookmarking-engine. Next post will add post-with-one-click. Then its time to sum up!

Monday, March 26, 2007

Bookmarking with Style

So the last section of the controller is still missing: the styles. Lets change it by giving you the whole block. It's quite a lot and there might be a potential for some tuning:


class Style < R '/styles.css'
def get
@headers["Content-Type"] =
"text/css; charset=utf-8"
@body = %{
body {
background-color: #FFFFFF;
}
h1 {
background-color: #FFBDB0;
border-bottom: thin dotted darkgrey;
margin-bottom: 10px;
margin-top: 5px;
font-size: 20px;
}
h1.h {
background-color: #E9E8FF;
font-size: 25px;
}
h2 {
background-color: #FFFCBA;
font-size: 14px;
border-bottom: thin dotted darkgrey;
margin-bottom: 0px;
}
h2:hover {
background: #E9E8FF;
}
hr {
border: 1px dotted darkgrey;
}
a {
text-decoration:none;
background: #FFFCBA;
margin: 1px;
padding: 2px;
color: black;
}
a.h {
background: none;
}
a.nav {
font-size: 12px;
}
a.h:hover {
background: none;
}
a:hover {
background: #E9E8FF;
}
a:visited {
color: black;
}
p {
margin: 2px;
}
p.date {
font-size: 13px;
color: darkgrey;
}
textarea {
width: 500px;
}
input {
background: #FFFCBA;
margin: 5px;
}
table {
border-spacing: 10px;
}
td {
border-right: thin dotted darkgrey;
padding: 20px;
}
ul {
list-style-type: square;
}
li {
margin: 8px;
}
}
end
end

The Grabr-app will look that way in the near future:



So keep camping!

Sunday, March 25, 2007

Everything under Camping::Control

For our bookmarking-app we will need in the first place the bookmarks itself stored in our database. They should belong to a certain user and have a title, the address of the bookmark and a short description.

In Camping-code in the Module-Section that looks as following:


module Grabr::Models
class Link < Base
belongs_to :user
has_many :tags
end
class Tag < Base; end
class User < Base; end

class CreateTheBasics < V 1.0
def self.up
create_table :grabr_links,
:force => true do |t|
t.column :id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :title, :string, :limit => 255
t.column :address, :string, :limit => 255
t.column :date, :datetime
t.column :description, :text
end
create_table :grabr_users,
:force => true do |t|
t.column :id, :integer, :null => false
t.column :username, :string
t.column :password, :string
end
User.create :username => 'admin',
:password => 'camping'
end
def self.down
drop_table :grabr_links
drop_table :grabr_users
end
end
end

Nothing special, I just left the tags out as we want to get running quickly and will add some additional features later.
The :user_id connects every posted link to the user who posted it.

The next step will be the Controller. As it is quite long I'll cut it in several slices. Lets get started with the Index-controller:

class Index < R '/'
def get
unless @state.user_id.blank?
@user = User.find @state.user_id
@links = Link.find(:all,
:conditions => {:user_id => @state.user_id})
end
render :index
end
end

When the index page is called by the url loalhost:anyport(probably 3301)/ the Index-controller looks for a logged-in user. This is done by checking the cooky set by the Camping-server. If there is a cooky containing a valid user-id then the instance-variable @user is created. We define another instance-variable called @links which contain all the users bookmarked links. At the render the index page, which will use both variables which is shown in the next post.

Next block are the Add- and View-Controller:

class Add
def get
unless @state.user_id.blank?
@user = User.find @state.user_id
@link = Link.new :title => input.link_title,
:address => input.address,
:description => input.description,
:user_id => @state.user_id,
:date => Time.now
end
render :add
end
def post
unless @state.user_id.blank?
link = Link.create :title => input.link_title,
:address => input.address,
:description => input.description,
:user_id => @state.user_id,
:date => Time.now
redirect Index
end
end
end

class View < R '/view/(\d+)', '/view'
def get link_id
unless @state.user_id.blank?
@user = User.find @state.user_id
end
@link = Link.find link_id
render :view
end
def post
unless @state.user_id.blank?
@link = Link.find input.link_id
@link.update_attributes :title =>
input.link_title,
:address => input.address,
:description => input.description
redirect Index
end
end
end

The get-methods of both classes does the same user-checking as almost every other Controllers (checking user, defining some variables, rendering a page). The post-methods only differ in the way they handle the input. While the Add#post-method creates a new link in the database the View#post-method just updates the atributes. The View gets the functionality you normally put in an Edit-controller but I thought it doesn't hurt if we merge them. Something special is the fact that the Add#post-method already accepts data-input. This is used for the convenient feature of a one-click-adding feature (I'll deal with in a later post).

Next one is the Delete-method (sorry, it's kind of a marathon but 300 lines are still 300 lines):

class Delete < R '/delete/(\d+)', '/delete'
def get link_id
unless @state.user_id.blank?
@user = User.find @state.user_id
@link = Link.find link_id
render :delete
end
end
def post
unless @state.user_id.blank?
Link.delete(input.link_id)
redirect Index
else
redirect Index
end
end
end

Quite self-descriptive: the get-method does the standard-job as seen in Add/View; the post-method deletes the data-base-entry.

So just login/logout-Controllers are still missing and then we are finally finished with the controller (almost, the Styles come in the next post):


class Login
def post
@user = User.find :first,
:conditions => ['username = ? AND
password = ?
',
input.username, input.password]

if @user
@login = 'login success !'
@state.user_id = @user.id
else
if input.user_add and not
input.password.empty? and not
input.username.empty?
@user = User.create :username =>
input.username,
:password => input.password
@login = 'Account successfully created!'
else
@login = 'Wrong user name or password!'
end
end
render :login
end
end

class Logout
def get
@state.user_id = nil
render :logout
end
end

The Login#get method does all the magic of logging a user in. Incase there is a new user to be created it adds an entry to the grabr_users-table. The Login-class just sets the cooky-user-information to nil to tell Grabr the user is not longer logged in.

Puh, thats was quite a rush through the whole Grabr-controller. But I think we should quickly come to terms now, as we are all excited what Grabr looks like in action. If I'm leaving some things out or miss any point leave a hint.