Skip to content

Instantly share code, notes, and snippets.

@oberhamsi
Created May 12, 2010 19:08
Show Gist options
  • Save oberhamsi/399012 to your computer and use it in GitHub Desktop.
Save oberhamsi/399012 to your computer and use it in GitHub Desktop.

FIXME ADD:

  • create admin interface to explain:
    • write your own macros & filterss!!
    • DONE url mapping
    • DONE method dispatching
    • middleware (auth)
    • DONE more involved actions. request object, different response
    • filestore relations for comments -> post
    • file upload. image for blog entries
    • logging, log who created / modified what post
  • unit tests
  • small ringo shell script for.. some blog cleanup task i will have to invent
  • extending java, simple package create walkthrough

WIP, http://gist.github.com/399012

Your first Ringo App

This tutorial is written for the most
recent version as available from git.

About

I will walk you through the creation of a basic blog. A assume you already have Ringo installed. See [Getting Started] if you need help with that.

The final, refined app developed in this tutorial is on github http://github.com/oberhamsi/ringo-tutorial-demoblog.

Help

If you run into trouble do not hesitate to ask for help on Ringo's Mailinglist or you can join us on IRC #ringojs on irc.freenode.net.


Content

General

Model Layer

View Layer

Other Batteries


Scaffolding

First, let Ringo's admin-create command take care of creating the directory structure and a couple of files that every webapp needs:

$ ringo-admin create demoblog

This will create the demoblog folder containing a functional - but not yet very useful - web application. This is a template for your app to get you started. But it is already a runnable web app as you will soon see.

admin-create created a couple of files in the top level directory:

  • main.js The script bootstrapping your web application.

  • config.js Settings for middleware, database, the app itself, etc.

  • actions.js Functions creating the Response. The View in MVC.

.. and several directories with rather self explanatory names:

  • skins/ Put all your skins (templates) in here.

  • static/ By default Ringo serves files from this directory under the URL /static/

  • config/ Holds jetty configuration files. Do not worry about those.

Calling ringo main.js will start a jetty webserver on your machine and you can view the demoblog app right away in your browser.

$ cd demoblog
$ ringo main.js

This will start a server on http://127.0.0.1:8080.

Instead of typing ringo main.js all the time you can start any web application with the ringo-web command. config.js must be in the directory from which you call ringo-web:

$ cd demoblog
$ ringo-web

If the server did not start that is probably because the default port used by Ringo (8080) is already in use. You can change the port in config.js by modifing the httpConfig.port property:

// config.js
exports.httpConfig = {
    staticDir: 'static',
    port: '8787',
};

This is also where you can change the directory from which Ringo serves the static files - leave it as "static" for this tutorial.

About JavaScript Modules

Ringo follows the [CommonJs] Standard. You will first notice this when dealing with modules. There are multiple JavaScript module patterns out there but in RingoJs you should only use this one:

  • Every file is a module living in its own top-level scope. No special syntax needed.
  • Attach to exports any functions or other properties a module should expose.
  • require('foobar') returns an object holding all exported properties of the module foobar.
  • include('foobar') brings all the exported properties of the module foobar into the including module (by convention: only use include in the shell)

An example should make things clearer. A simple module in the file foobar.js might look like the following. I want to exposes the function add but not the private adder:

// foobar.js
var adder = function(a, b) {
    return a + b;
};
exports.add = function(a, b) {
    return adder(a, b);
};

You can then either include() that module, which would instantly make add available:

>> include('foobar');
>> add(2,3)
5

Or - to keep your namespace clean - you can require() it and then access add as foobar.add:

>> var foobar = require('foobar');
>> foobar.add(3,4)
7

Another option is to use destructuring assignment to extract exactly the properties you want from the module you require:

>> var {add} = require('foobar');
>> add(2,3)
5

See [Modules in RingoJs] or the post RingoJS Modules and how to fix the Global Object for more details.

Store Setup

Ringo ships with multiple store backends. We will us the filestore for this tutorial because it does not need a database backend. Filestore simply puts all its data into the directory we tell it to.

We setup the Store in config.js by requiring the appropriate storage module and instantiating a Store. Other modules will import this config module to get access to the store, therefor we must export the store property by attaching it to exports.

Store only takes one argument - the filesystem path where it should save its data. You better change storePath to an absolute path like /home/yourname/db.demoblog/.

// config.js
var filestore = require('ringo/storage/filestore');
var storePath = 'db';
exports.store = new filestore.Store(storePath);

Now that we have a store setup, we can start defining the entities we want to put into the store.

Defining Models

By convention you should put all model code into model.js. Create that file now in your demoblog folder. We will put all data describing the models as well as any model behaviour into that module.

We import the config module to get access to the store instance we setup earlier. The filestore, like every store, has the function defineEntity() which allows us to, well, define entities that can be put into or retrieved from the store. How many arguments and what kind of arguments defineEntity() accepts depends on the store implementation. The filestore only requires one argument: the name of the entity.

The filestore is a schemaless store, which means we do not have to define in advance what properties the entities have; we can simply attach properties to entity instances and whatever we attach will get stored. In a traditional, schemafull store (like MySQL) you would have declare in advance - when creating tables - what properties ("fields") the entities ("tables") have.

// model.js
var config = require('./config');

exports.Post = config.store.defineEntity('Post');
exports.Comment = config.store.defineEntity('Comment');

That's it. defineEntity() returns the constructor for the entities, which we in turn export so other modules can access them. We will later use the Post and Comment constructors to create Posts and Comments.

Excursion: What about MySQL, PostgreSQL, etc.?

For a more traditional, schemafull store, like MySql, model.js would be the place to setup the whole database mapping. Typically, you would map every table in your database to a store entity, and the fields of the table would map to properties of the entities.

To give you a taste: in case your using something like MySQL your model definition will look more like the following:

// model.js (do not use this for tutorial. this is just an example)
var Post = store.defineEntity('Post', {
    table: 'persons',
    properties: {
        title: {type: 'string', nullable: false},
        text: {type: 'string', nullable: false},
        createtime: {type: 'timestamp', nullable: false},
    }
});

.. and you would have to instantiate a different store in config.js namely Ringo's Relational Store which you can install with ringo-admin. See [How to install packages][Package_Management] if you need more help with that.

$ ringo-admin install robi42/ringo-hibernate

ringo-hibernate is work in progress and does not have much documentation. You can most easily find out what is currently possible by checking out ringo-hiberante's unit tests.

There are in fact other store implementation, check out the Database section in the [list of available packages for Ringo][Packages].

Creating & Storing Model Instances

Let's do something with our models. We will create some blog posts in the Ringo shell. Change into the demoblog directory and start the shell:

$ cd demoblog
$ ringo

Import the model module. If you made any mistakes in config.js or model.js they will show up now as errors. We can now use the constructor functions we exported in the model module to create new Posts:

>> include('./model');
>> var post = new Post()
>> post.author = 'Simon'
Simon
>> post.createtime = new Date()
Tue May 11 2010 13:47:51 GMT+0200 (CEST)
>> post.text = 'My first blog Post!'
My first blog Post!

That was easy, but the Post is not yet persisted! Instances of store entities have a save() function that takes care of that:

>> post.save()

There is also a remove() function which drops the entity from the store, e.g.:

>> post.remove() // would remove that post from the store

Its usually nicer to pass the constructor an object holding all the properties you want stored:

>> var post = new Post({
..          title: 'Second Post',
..          author: 'simon',
..          text: 'Follow up post in which i explain the first Post.',
..          createtime: new Date()
.. })
>> secondPost.save()

Oh no, the first Post does not have a title. Time to query that post and give it a title!

Querying Models

When we defined the store entities in model.js, Ringo did something behind our backs: defineEntity() not only gave us back a constructor for creating the entities Post and Comment, it also added the query() function to each of those.

We can do Post.query() which does nothing, but allows us to chain real query functions. As many as we want. Currently Ringo supports the following query functions, all chainable:

  • equals (property, value)
  • greater (property, value)
  • greaterEquals (property, value)
  • less (property, value)
  • lessEquals (property, value)

To get all Posts by a certain author you would use query() and chain an equals() query and then select() to actually get the list of matching entities:

>> Post.query().equals('author', 'simon').select()
[object Storable],[object Storable]

Or to get all Posts from that author before a certain date, just add another query call:

>> Post.query().equals('author', 'simon').
..      less('createtime', new Date()).select()
[object Storable],[object Storable]

But once you call select() on a query-chain Ringo will return the list of Entities matching the Queries.

In addition to query() all store entities have get(id), so we can for example do Post.get(1) to get the post with id 1.

Excursion: Ringo comes with Batteries included

All those [object Storable] are ugly. We should add a nicer string representation for Posts. Even if this is just for our own development sanity.

We will add a toString function to the Post protototype that will return a pretty string representation. Ringo comes with many useful Modules, we are going to use ringo/utils/dates here to format the date:

// model.js
var dates = require('ringo/utils/dates');
Post.prototype.toString = function() {
    return '[Post: ' + this.title + ' (' + this.author + ', ' +
        dates.format(this.createtime, 'dd.MM.yyyy') + ')]';
};

Ringo ships with many useful modules. We have JSON support, a module for file operations, a unit testing framework and more. Most of the interesting stuff is in the ringo/ namespace.

If you still do not find what you need maybe someone else has already written a packacke that might help you: List of Ringo Packages.

... back to Querying

Now we can more easily tell which Entity needs fixing. We query the Posts again but first reload the module by include'ing it again:

>> include('model')
>> Post.query().select()
[Post: second post (simon, 11.05.2010)],[Post: undefined (simon, 11.05.2010)]

Pretty. Though now the missing title is glaring. Fixing that is easy. We set the title attribute on the second post and save() it:

>> var posts = Post.query().select()
>> posts[1].title = 'Introductory Post'
Introductory Post
>> posts[1].save()

Now both Posts show up nicely.

>> Post.query().select()
[Post: Second Post (simon, 11.05.2010)],[Post: Introductory Post (simon, 11.05.2010)]

Time to publish our thoughts!

Actions and Skins

The current index function in actions.js only returns a static skin. We will extend the function to load the last ten Posts and pass them to the skin for rendering. At the top we require the model module we wrote, so we get access to the Post prototype. As before we use query() to select all posts - we are only interested in the first ten so we slice them off.

var posts = model.Post.query().select().slice(0,10);

After that we use skinResponse() to return the rendered html to the browser. skinResponse() expects two arguments: the path to the skin file to render and a object with arbitrary properties. We call the later the "skin context". All the properties defined in the skin context are available for scripting in the skin. I'll show you in a minute. But this is how our action, returning the rendered skin, looks like:

// actions.js
var response = require('ringo/webapp/response');
var model = require('./model');

exports.index = function index(req) {
    var posts = model.Post.query().select().slice(0,10);
    return response.skinResponse('skins/index.html', {
        posts: posts,
    });
};

In the skin we will now access the "posts" array from the context and render a subskin for each post. The "posts" array is accessed as <% posts %> in the skin. You could just put something like this into the skin:

// in a skin
<% posts %>

This would output the same string representation of the list of posts as we would get on the shell:

// rendered skin output of <% posts %>
[Post: Second Post (simon, 11.05.2010)], [Post: Introductory Post (simon, 11.05.2010)]

But what we really want to do, is loop over each post in that array and render a piece of html. That is as simple as it sounds:

// in a skin
<% for post in <% posts %> render 'postOverview' %>

<% subskin 'postOverview' %>
<h2><% post.title %></h2>

This will render the subskin 'postOverview' for each post in posts. 'postOverview' is a template we re-use in each iteration to render the current post. The way we render 'postOverview' in the for-loop, it has access to the context variable 'post' and uses it to output <% post.title %>.

Subskins are an important concept in Ringo. A skin can have a lot of subskins, each of which starts with <% subskin 'foobar' %> and ends where the next subskin starts.

The other use for subskins, besides loop rendering, is to overwrite a subskin of the same name defined in the skin we extend. index.html extends from base.html. In index.html the 'content' will be the list of posts; therefor we overwrite the 'content' subskin with the loop we put together above.

This is how it looks all together:

// index.html
<% extends ./base.html %>

<!-- we overwrite the 'content' subskin which was 
     originally defined in base.html -->

<% subskin content %>
<% for post in <% posts %> render 'postOverview' %>

<!-- the 'postOverview' subskins is used by the for loop 
     to render each post -->

<% subskin 'postOverview' %>
    <h2><% post.title %></h2>
    <p>
        <% post.createtime | dateFormat "dd.MM.yyyy" %>, <% post.author %>
    </p>
    <div>
        <% post.text %>
    </div>

Note how we use a filter to format the createtime: <% post.createtime | dateFormat "dd.MM.yyyy" %>. We already have quite a lot of useful filters and macros in Ringo.

Start the app again http://127.0.0.1:8080.

$ ringo-web

The Skins Demo also shows of some skin features I did not mention. Also note that this is a very simple action. Later we will look at an action in the admin section of the Blog which creates Posts. It will have to deal with the Request object which is passed to every action as the first parameter req.

Macros & Filters

FIXME how to write macros & filter. at least 1 simple example for both.

Refining our Skins

It would be nicer if the startpage of our blog would only show the lead text of every entry with a link to the actual blog post. For that to work we need to set a new lead property on all our blog posts, and then define a new action to handle displaying a single post.

If you have read the part about querying above you will get this without further explanation: We just load the posts and add a property lead to each and save.

>> include('model')
>> posts = Post.query().select()
[Post: Second Post (simon, 11.05.2010)],[Post: Introductory Post (simon, 11.05.2010)]
>> posts[0].lead = 'In which we describe the introduction'
In which we describe the introduction
>> posts[0].save()
>> posts[1].lead = 'In which we introduce the blog'
In which we introduce the blog
>> posts[1].save()

Then we modify index.html to only render the lead and add a link to the full post. We use the href macro to create the links. Use the href macro to create relative links within your app.

Let's say every blog post should be reachable by a "read more" link (we will later write the action showing the full blog post):

// index.html
<% extends ./base.html %>
<% subskin content %>
<% for post in <% posts %> render 'post' %>

<% subskin 'post' %>

   <h2><% post.title %></h2>
   <p>
       <% post.createtime | dateFormat "dd.MM.yyyy" %>, <% post.author %>
   </p>
   <div>
     <p>
       <% post.lead %>
     </p>
     <a href="<% href post %>/<% post._id %>"> ...read more </a>
   </div>

Now we get links like "/post/1/", "post/2/". In the next sections we will take care of handling those so they actually output the full post.

URL Patterns: Capturing Arguments for Actions

Now the 'read more' href links to /post/id where id is the id of a post. We need a new action to handle the rendering of a single post. Remember how all the methods we export in actions.js are already mapped to Urls. This is because currently in config.js we just have that one line mapping:

// config.js
exports.urls = [
    ['/', './actions'],
];

Ringo will convert the pattern string '/' into a Regex /\/.*/, and that particular Regex will match any request path. Ringo then takes the first part of the path - everything up to the first '/' - and searches for a function with that name in the specified module actions.

That simple mechanism worked great for us so far because by default - if there is no path part, because the Url was "/" - Ringo looks for an "index" action and that is all we needed.

But how will the URL /post/id work? The first part is the action name: 'post'. Ringo will pass the rest of the URL - split by / - as parameters to the action. The action just has to accept the correct number of arguments: in this case only one argument, the id.

So our post action looks like this. It gets the post with the right id, as passed to it as the second argument, and renders that post. This time we use the get(id) function of the entity to retrieve only the one post we care about. No need for query().

// actions.js
exports.post = function post(req, id) {
   var post = model.Post.get(id);
   return response.skinResponse('skins/post.html', {
      post: post,
   });
 };

.. and the accompanying skins/post.html. This skin is even simpler, as it doesn't do any looping. It only overwrites the 'content' subskin to output the post:

// skins/post.html
<% extends ./base.html %>

<% subskin content %>
<h1><% post.title %></h1>
<p>
  <% post.createtime | dateFormat "dd.MM.yyyy" %>, <% post.author %>
</p>
<div>
   <p><% post.lead %></p>
   <p><% post.text %></p>
</div>
<a href="<% href / %>"> back to front </a>

Our app should be fully functional again, let's try it: http://127.0.0.1:8080.

$ ringo-web

Roadmap for the Rest of the Tutorial

Creating Posts in the Ringo shell was a great way to show you how our Storage API works. But in the real world you will have a backend to edit and create posts. That is what we will build now. Keeping it simple, we just add two actions: one to edit posts and another one to create them.

We want the URLs to look like this:

  • /admin/edit/id show the form for editing Post with id
  • /admin/create/ show an empty post form for creating new Posts

Some kind of authentication would be nice. Ringo ships with an authentication middleware which allows us to define protected URLs and users who can access. That will do for now. We will take a closer look at this middleware in a later section.

To show of Ringo's logging facility we will also log everything that goes on in the backend.

URL Mapping for the admin section

Our super simple URL mapping brought as far - with all the automatic parameter capturing & passing that is going on. Though for the admin backend we have to extend it. As I layed out above, all the backend actions will have the common prefix /admin/. The easiest way to setup a mapping for this is to put all the admin actions in a separate file - adminactions.js for example - and map every Url that starts with /admin/ to that file. Easy:

// config.js
exports.urls = [
    ['/', './actions'],
    ['/admin/', './adminactions']
];

That's it already. For the Url /admin/create to work we only need to add a create action in adminactions.js - will write that in the next section.

RESTful Dispatching on Method

The edit action has two tasks:

  • it must output a form with the Blogpost's data for editing
  • when the user clicks 'save' it must accept the incoming POST request and modify the Blogpost's data accordingly.

Ringo has a builtin mechanism for dispatching on the request method, which we will use here.

So far we have only seen actions that are plain functions. Those plain functions will be triggered for all HTTP methods (POST, GET, etc.). Instead of the action being a function, it can also be an object literal with properties matching the HTTP method names. For our edit action we need one for GET and another for POST:

// adminactions.js
exports.edit = {}
exports.edit.GET = function edit(req, id) {
    // output the model data for displaying
    var post = model.Post.get(id);
    return response.skinResponse('skins/edit.html', {
        post: post,
    });
};
exports.edit.POST = function edit(req, id) {
    // TODO handle post data
    return response.redirectResponse(req.path);
};

The POST action so far only redirects back to the GET action. Let's first deal with the edit.html skin. It displays the form for the passed post object:

// edit.html
<% extends ./base.html %>
<% subskin content %>

<h1> Edit Post '<% post.title %>' </h1>
<form name="blogpost" action="<% href %>" method="POST">
    <h3>Title<h3>
    <input type="text" name="title" size="30" value="<% post.title %>"><br/>
    <h3>Lead<h3>
    <textarea name="lead" cols="50" rows="5"><% post.lead %></textarea>
    <h3>Text</h3>
    <textarea name="text" cols="50" rows="20"><% post.text %></textarea>
<br/>
<input type="submit" name="Save" value="save"/>
</form>

This should already work! Try accessing http://127.0.0.1:8080/admin/edit/1. The form displays and you can press Save and that will redirect you back to the GET.edit action.

The Request Object

Ringo passes the Request object as the first argument req to every action. We have seen this before, but in the edit action we will finally do something with it. We loop over the list of editable properties, read them from req.params and set them on the Blogpost.

// in an action
var post = model.Post.getById(id);
for each (var key in ['text', 'lead', 'title']) {
   post[key] = req.params[key];
}

req.params holds all GET as well as POST parameters. See Request for more info on the Request class.

Finally we save() the modified post and redirect back to the GET.edit action. This works because req.path holds the current request path (/admin/edit/2 for example). The whole action is still quiet simple:

// adminactions.js
exports.edit.POST = function edit(req, id) {
    var post = model.Post.getById(id);
    for each (var key in ['text', 'lead', 'title']) {
        post[key] = req.params[key];
    }
    post.save();
    return response.redirectResponse(req.path);
};

The create actions are even simpler. The GET.create function renders the edit.html skin like GET.edit does but without passing a post, thereby creating an empty form. And that's it:

// adminactions.js
exports.create = {};
exports.create.GET = function create(req) {
    return response.skinResponse('skins/edit.html');
};

The POST.create stores what properties it gets via req.params in a new Post object but also attaches the automatically created author and createtime properties. And finally, it redirects to the GET.edit action of the newly saved Post:

// adminactions.js
exports.create.POST = function create(req) {
    var post = new model.Post();
    for each (var key in ['text', 'lead', 'title']) {
        post[key] = req.params[key];
    }
    post.author = 'unknown author';
    post.createtime = new Date();
    post.save();
    // once the Post is stored, redirect to it's edit page
    return response.redirectResponse('./admin/edit/' + post._id);
};

Not bad. Startup your blog and try creating a new Post by opening http://127.0.0.1:8080/admin/create

Skins: if macro (Excursion)

Two things are annoying: both, the create and edit page, say 'Edit Post' at the top. A quick fix for this is that we extract the header as a subskin. We should only render the header subskin if the object post is set in the skin context. A good opportunity to introduce the if macro:

// edit.skin
<% extends ./base.html %>

<% subskin content %>
<% if <% post %> render editHeader %>

<form name="blogpost" action="<% href %>" method="POST">
    <h3>Title<h3>
    <input type="text" name="title" size="30" value="<% post.title %>"><br/>
    <h3>Lead<h3>
    <textarea name="lead" cols="50" rows="5"><% post.lead %></textarea>
    <h3>Text</h3>
    <textarea name="text" cols="50" rows="20"><% post.text %></textarea>
<br/>
<input type="submit" name="Save" value="save"/>
</form>

<% subskin editHeader %>
<h1> Edit Post '<% post.title %>' </h1>

This will do for now.

Sessions (Excursion)

Also it would be nice to have messages like "Post saved successfully" after saving. One way to do this is use the req.session (see ringo/webapp/request.Session). We can set a message property on req.session in POST.edit and read that property from the session in GET.edit.

Note how we also unset the req.session.message in Get.edit - we do no want it lurking in the session forever:

// actions.js
exports.edit = {}
exports.edit.GET = function edit(req, id) {
    // ...
    var message = req.session.data.message;
    req.session.data.message = "";
    return response.skinResponse('skins/edit.html', {
        post: post,
        message: message,
    });
};
exports.edit.POST = function edit(req, id) {
    // ...
    post.save();
    req.session.data.message = "Successfully saved Post " + id;
    return response.redirectResponse(req.path);
};

Adding <% message %> to the edit.html skin is left as an exercise.

Admin Dashboard

Manually hacking the Url to get into the admin interface is cumbersome. Let's build a simple admin dashboard, reachable under /admin/ that lists all stories with an edit link. This is pretty straight forward so I won't explain much. First the index action, which just renders skins/adminindex.html with all posts in the context:

// adminactions.js
exports.index = function index(req) {
    var posts = model.Post.query().select();
    return response.skinResponse('skins/adminindex.html', {
        posts: posts,
    });
};

.. and the accompanying skin adminindex.html, which renders all posts, links to their edit page and has a "create new post" link on top:

// adminindex.html
<% extends ./base.html %>

<% subskin content %>
<h1> Demoblog Admin Interface </h1>
<a href="./create"> new post </a>
<ul>
<% for post in <% posts %> render 'post' %>
</ul>

<% subskin 'post' %>
<li>
    <a href="./edit/<%post._id%>"><% post.title %></a>
    <% post.createtime | dateFormat "dd.MM.yyyy" %>, <% post.author %>
</li>

Tada: http://127.0.0.1:8080/admin/

Middleware

For authentication we use Ringo's basic auth middlware. Like any middleware it is activated by adding it to the array middleware in config.js:

// config.js
exports.middleware = [
    'ringo/middleware/responselog',
    'ringo/middleware/error',
    'ringo/middleware/notfound',
    'ringo/middleware/basicauth'
];

But that has no visible effect unless we also define a protected realm. In our case all backend Urls start with '/admin/' so that will be the realm, which only the user 'demoblog' with the password 'secret' can access. The passwords is given as a SHA1 hash. The final auth config looks like this:

// config.js
exports.auth = {
    '/admin/': {
        blogadmin: "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4" // "secret"
    }
};

You can create the SHA1 for a string with Ringo's ringo/utils/strings digest() method:

>> var strings = require('ringo/utils/string')
>> strings.digest('secret', 'sha1')
e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4

When you access any of the /admin/ Urls you should now get a basicauth prompt, try http://127.0.0.1:8080/admin/.

FIXME: explain middleware in general and auth in particular

Logging

Ringo come with a logging Module. Instantiating a logger is easy, put this somewhere at the top of adminactions.js:

// adminactions.js
var log = require('ringo/logging').getLogger(module.id);

Then in the edit.POST and create.POST actions we log which user updated what post. With authbasic the user is sent as in the header of every request, we will need a utility function in adminactions.js to extract the current user from the request. I copied this from ringo/middleware/auth:

// adminactions.js
function authUser(req) {
    var credentials = base64.decode(req.headers.authorization
                        .replace(/Basic /, '')).split(':');
    return credentials.length && credentials[0];
}

Now we can start logging. And while we are at it we can finally fix create.POST to actually set the correct author. Note how we use {} (curly bracket pairs) for string replacement. The logger will replace the bracket pairs with the following arguments.

// adminactions.js
exports.create.POST = function create(req) {
    var post = new model.Post();
    for each (var key in ['text', 'lead', 'title']) {
        post[key] = req.params[key];
    }
    var user = getAuthUser(req);
    post.author = user;
    post.createtime = new Date();
    post.save();
    log.info('{} created by {}', post, user);
    return response.redirectResponse('./edit/' + post._id);
};

This will yield a log line like this:

8068446 [qtp1868018799-13] INFO  adminactions  - [Post: Second Post (simon, 04.08.2010)] updated by blogadmin

... to be continued http://gist.github.com/gists/399012

@jirpok
Copy link

jirpok commented Aug 4, 2010

Perhaps you're missing require('ringo/webapp/util');? Otherwise get() does not work. But I may have misunderstood something.

Jirka

@oberhamsi
Copy link
Author

I'm not sure what you mean.. what's not working for you?

@jirpok
Copy link

jirpok commented Aug 5, 2010

hmm.. never mind. Suddenly it works now. I still need to dig deeper to get the full picture of ringo mechanisms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment