Skip to content
Techwraith edited this page Feb 9, 2012 · 7 revisions

##Welcome to the Geddy Tuorial In this tutorial we'll go over how to get Geddy installed, and how to create an example to do list manager app.

We'll cover:

  • How to generate an app
  • Setting up a resource for us to use
  • Review how to use Bootstrap to automatically make a mobile version of your app
  • Using Geddy Models
  • Using a model adapter to interact with your data
  • How to use init.js in your app
  • How views in Geddy work
  • How to use controllers to tie everything together
  • How to set up custom routes

##Installing Geddy:

First, make sure that you have node installed on your machine.

Next, install Jake and Geddy. Jake is a task system written in Javascript similar to Ruby's Rake.

$> npm install -g jake geddy

##Create Your first app Now lets use the geddy executable to generate a basic app structure.

$> geddy app todo_app

The geddy executable can do many things, to generate an app just type geddy app {{app_name}} where {{app_name}} is the name of the directory you want to put your app in. Geddy will create the directory and create a basic app structure for you.

##Start Your App To start your app, all you need to do is cd into your app's directory and run geddy.

$> cd todo_app
$> geddy

Geddy will default to running in development mode, which means your server will output all unhandled errors output debug logs.

##Check out your app After running the geddy command, your app should be running on port 4000. Visit http://localhost:4000 in your browser to see your app.

###optional: check out your app on a mobile phone

  • point your mobile phone's browser to your computer's port 4000
  • OR open up your favorite phone simulator and go to http://localhost:4000
  • OR resize your browser to at least 480px wide

##Generate a resource Now, lets actually get started building our To Do list manager. First, we'll need to generat the todo resource. We do this using the geddy executable as well:

geddy resource todo

What did that do?

  • It generated a todo model
  • It generated a todos controller
  • It generated views for:
  • an index of todo's
  • a single todo
  • creating a todo
  • editing a todo
  • It generated these routes:
  • /todos (GET)
  • /todos (POST)
  • /todos/add (GET)
  • /todos/:id/edit (GET)
  • /todos/:id (GET)
  • /todos/:id (PUT)
  • /todos/:id (DELETE)

Now, lets run the app again:

geddy

You should be able to see your new resource's index veiw at http://localhost:4000/todos

##Creating the Todo model We're ready to start in on modeling our data. Geddy provides us with some pretty cool tools to do this:

  • Validation
  • Typed Data
  • Instance Methods
  • Static Methods

Go ahead and open up app/models/todo.js. Read through the commented out code there if you'd like to learn a little more. We'll be writing our model from scratch for this tutorial, so lets leave that commented out (it's been deleted in the example app.)

So, minus the commented out code, you should have a file that looks like this:

var Todo = function () {

};

Todo = geddy.model.register('Todo', Todo);

Let's add three properties onto the todo model:

  • Title
  • Status
  • id

To do this all we have to do is add some this.property's onto the model function:

this.property('title', 'string', {required: true});
this.property('status', 'string', {required: true});
this.property('id', 'string', {required: true})

The first argument of the property method is the name of the property you want to define, the second is the data type, and the third is an options object. We want all of ours to be required, so we made sure that our options object has required set to true.

While we're here, lets set up some validations:

this.validatesPresent('title');
this.validatesLength('title', {min: 5});

this.validatesWithFunction('status', function (status) {
  return status == 'open' || status == 'done';
});

For the title property, we made sure that the property is always present and we made sure that the title property is a minimum of 5 characters long.

For the 'status' property, we used a function to validate that the property is always set to either open or done.

##Creating a Todo model adapter

Now that we've set up our todo model, we can create somewhere to store our models. For the purposes of this tutorial, we're just going to hang a todos array off of our global geddy object. If you want to make this app more robust after the tutorial, persistence might be a good place to start.

###Editing your init.js file Open up your config/init.js file. All that should be in there now is a global uncaught exception handler.

if (geddy.config.environment != 'development') {
  process.addListener('uncaughtException', function (err) {
    geddy.log.error(JSON.stringify(err));
  });
}

Right after that block of code, lets hang our array off the geddy global:

geddy.todos = [];

There, now we've got a place to store our todo's (at least until the server restarts).

###Creating the model adapter A model adapter is used as a go-between for models and data sources. Our data source is pretty simple (just and array!), so writing our model adapter should be pretty simple too.

Create a directory in lib called model_adapters

$> mkdir lib/model_adapters

And create a file in lib/model_adapters called todo.js

$> touch lib/model_adapters/todo.js

Lets open up that file and add in some boilerplate code:

var Todo = new (function () {

})();
exports.Todo = Todo;

Require the model adapter in init.js

So we set up a new Todo model adapter object. It's pretty barren right now, but we'll get to that soon. For now, we'll have to go back to init.js to add this model adapter into our app. After the geddy.todos = []; in config/init.js add these two lines:

geddy.model.adapter = {};
geddy.model.adapter.Todo = require(process.cwd() + '/lib/model_adapters/todo').Todo;
We're working on making this nicer.

We created a blank model adapter object, and add the Todo model adapter onto it.

##Saving todos Now that we have our model and model adapter in place, we can start in on the app logic. Lets start with adding to do items to our to do list.

###Edit the save method on the adapter to save a todo instance When working with data, the first place you should go is the model adapter. We need to be able to save an instance of our Todo model to our geddy.todos array. So open up lib/model_adapters/todo.js and add in a save method:

  this.save = function (todo) {
    todo.saved = true;
    geddy.todos.push(todo);
  }

All we have to do is set the instance's saved property to true and push the item into the geddy.todos array. Now lets move on to the controller create action.

###Edit the create action to save a todo instance Go ahead and take a look at the create action in app/controllers/todos.js

this.create = function (req, resp, params) {
  // Save the resource, then display index page
  this.redirect({controller: this.name});
};

Pretty simple right? We stubbed it out for you. So lets modify it a little bit:

this.create = function (req, resp, params) {
  var todo = geddy.model.Todo.create({title: params.title, id: geddy.string.uuid(10), status: 'open'});
  if (todo.isValid()) {
    todo.save();
    this.redirect({controller: this.name});
  } else {
    this.redirect({controller: this.name, action: 'add?error=true'});
  }
};

First, we create a new instance of the Todo model with geddy.model.Todo.create, passing in the title that our form will post up to us, and setting up the defaults for the id and status. Then we check to see if the model passed validation, if it did, we call the save method that we created on the model adapter and redirect the user back to the /todos route. If it didn't pass validation, we redirect the user back to the /todos/add route and pass an error as a query parameter. Geddy has sessions too, so this might be another good spot to improve the codebase after the tutorial.

###Edit add.html.ejs

Now it's time for us to set up the add template. Take a look at app/views/todos/add.html.ejs, it should look like this:

<div class="hero-unit">
  <h3>Params</h3>
  <ul>
  <% for (var p in params) { %>
    <li><%= p + ': ' + params[p]; %></li>
  <% } %>
  </ul>
</div>

We won't be needing that ul for our use case, so lets get rid of it for now. Make your add.html.ejs look like this:

<div class="hero-unit">
  <h2>Add a ToDo:</h2>
  <form action="/todos" method="POST">
    <% if (params.error) {
        var title = 'title not long enough, must be 5 characters or more.'
      } else {
        var title = 'enter title'
      }
    %>
    <input type="text" class="span6" placeholder="<%= title %>" name="title"/>
    <input type="submit" class="btn btn-primary">
  </form>
</div>

All we're doing here is adding in a heading and a form. We set the form's action to /todos and it's method to POST. When the server receives a POST to /todos, it routes the request over to the Todos controller's create action.

Sidenote:

If you want to make this a little prettier, you can copy the [example app's style.css file](https://github.com/mde/geddy/blob/master/examples/todo_app/public/css/style.css) into your app.

The other stuff is pretty self explanatory - we check to see if the error param is there, and if it is, display an error message. If not, we show some placeholder text.

Go ahead and visit http://localhost:4000/todos/add to see your template in action. Create a to do item while you're at it.

##Listing all todos

Now that we have user input to do items being added into our geddy.todos array, we should probably list them somewhere. Lets start in on the index action in the todos controller.

###Edit the index action to show all todos Open up /app/controllers/todos.js again and take a look at the index action. It should look something like this:

this.index = function (req, resp, params) {
  this.respond({params: params});
};

This part is really simple, just replace the params property in the template variable object with a todos property and set it to geddy.todos.

this.index = function (req, resp, params) {
  this.respond({todos: geddy.todos});
};

Thats it for the controller, now onto the view.

###edit index.html.ejs Take a look at /app/views/todos/index.html.ejs, it should look like this:

<div class="hero-unit">
  <h3>Params</h3>
  <ul>
  <% for (var p in params) { %>
    <li><%= p + ': ' + params[p]; %></li>
  <% } %>
  </ul>
</div>

Looks a lot like the add.html.ejs template doesn't it. Again, we won't need the params stuff here, so take that out, and make your index.html.ejs template look like this:

<div class="hero-unit">
  <h2>To Do List</h2>
  <a href="/todos/add" class="btn pull-right">Create a new To Do</a></p>
</div>
<% if (todos.length) { %>
  <% for (var i in todos) { %>
  <div class="row todo-item">
    <div class="span8"><h3><a href="/todos/<%= todos[i].id; %>"><%= todos[i].title; %></a></h3></div>
    <div class="span4"><h3><i class="icon-list-alt"></i><%= todos[i].status; %></h3></div>
  </div>
  <% } %>
<% } %>

This one is also pretty simple, but this time we've got a loop in our template. In the header there we've added a button to add new todo's. Inside the loop we're generating a row for each todo, displaying it's title (as a link to it's show page), and it's status.

To check it out, go to http://localhost:4000/todos.

##Showing a todo Now that we have a link to the show page, we should probably work on the show page.

###Create a load method in the model adapter Open up your model adapter again (/lib/model_adapters/todo.js) - by now, it should look something like this:

var Todo = new (function () {

  this.save = function (todo) {
    todo.saved = true;
    geddy.todos.push(todo);
  }

})();

exports.Todo = Todo;

Lets add a load method to this adapter:

var Todo = new (function () {

  this.load = function (id, callback) {
    for (var i in geddy.todos) {
      if (geddy.todos[i].id == id) {
        return callback(geddy.todos[i]);
      }
    }
    callback({});
  };

  this.save = function (todo) {
    todo.saved = true;
    geddy.todos.push(todo);
  }

})();

exports.Todo = Todo;

This load method takes an id and a callback. It loops through the items in geddy.todos and checks to see if the current item's id matches the passed in id. If it does, it calls the callback, passing the todo item back. If it doesn't find a match, it called the callback with a blank object. Now we need to use this method in the todos controller's show action.

###Edit the show action to find a todo Open up your todos controller again and take a look at it's show action. It should look something like this:

this.show = function (req, resp, params) {
  this.respond({params: params});
};

Lets use the load method that we just created:

this.show = function (req, resp, params) {
  var self = this;
  geddy.model.adapter.Todo.load(params.id, function(todo){
    self.respond({todo: todo});
  });
};

All we're doing here is loading the todo and sending it down to the template to be rendered. So let's take a look at the template.

###Edit show.html.ejs Open up /app/views/todos/show.html.ejs, it should look like this:

<div class="hero-unit">
  <h3>Params</h3>
  <ul>
  <% for (var p in params) { %>
    <li><%= p + ': ' + params[p]; %></li>
  <% } %>
  </ul>
</div>

Once again we're not going to need the params block, so lets remove it. Make your show.html.ejs look like this:

<div class="hero-unit">
  <h3><%= todo.title; %></h3>
  <div class="pull-right">
  <% if (todo.status == 'open') { %>
  <form id="finish-todo" class="hidden" action="/todos/<%= todo.id;%>">
    <input type="hidden" name="status" value="done"/>
    <input type="hidden" name="id" value="<%= todo.id; %>"/>
    <input type="hidden" name="title" value="<%= todo.title; %>">
  </form>
  <span><a href="#" class="btn btn-primary btn-large" id="finish-btn">Finish To Do</a></span>
  <script type="text/javascript">
    var form = $('#finish-todo');
    $('#finish-btn').click(function(e){
      e.preventDefault();
      $.ajax({
        type: "PUT",
        url: form.attr('action'),
        data: form.serialize()
      }).done(function( msg ) {
        $(e.target).replaceWith('<p>This todo is finished!</p>');
      });
    })
  </script>
  <% } else { %>
    <p>This to do is finished!</p>
  <% } %>
  </div>
</div>

We're doing a few more complicated things now. First off, you'll notice that we've got a script block in there. We thought we'd show you that you can do all this with ajax as well.

This template is basically a big if statement. If the status is open, display the title of the todo and a button to finish it. It's got a hidden form in there as well, this is where the PUT request to /todos/:id get's it's data.

##Updating a todo Now that we have a button to update our todo, lets make the feature work.

###Edit the save method in the model adapter to save over existing todos Open up your model adapter again. We're going to want to change the save method to allow for saving over existing model instances. You're save method should look something like this:

  this.save = function (todo, callback) {
    todo.saved = true;
    geddy.todos.push(todo);
  }

Lets edit it to look like this:

  this.save = function (todo, callback) {
    for (var i in geddy.todos) {

      // if it's already there, save it
      if (geddy.todos[i].id == todo.id) {
        geddy.todos[i] = todo;
        return
      }

    }
    todo.saved = true;
    geddy.todos.push(todo);
  }

We'll loop over all the todo's in geddy.todos and if the id is already there, replace that todo with the new todo instance. If you were hooked up to a real DB here, you'd want to use it's update functionality instead.

###Edit the update action to find a todo, change the status, and save it Alright, now that we have our save method in order, lets edit the update action in the todos controller. It should look something like this right now:

this.update = function (req, resp, params) {
  // Save the resource, then display the item page
  this.redirect({controller: this.name, id: params.id});
};

You'll want to edit it to make it look like this:

this.update = function (req, resp, params) {
  var self = this;
  geddy.model.adapter.Todo.load(params.id, function(todo){
    todo.status = params.status;
    todo.save();
    self.redirect({controller: this.name, id: params.id});
  });
};

We're taking the id that we sent up via the ajax PUT request on the show page and using the load method that we created earlier to find a todo item. Then we're setting it's status to be what we sent up in the params ('done'). Then we use the save method that we just updated to save over the existing todo item. Then, in case this isn't coming from an ajax request, we're redirecting the request over to the show action.

##Conclusion At this point you should have a working To Do List app!

If you want to explore a little more, here are some other things you could do:

  • Change the Main#index route to point to the Todos#index action (hint, check out config/router.js)
  • Add some logging with geddy.log
  • Set up metrics by running mkdir node_modules && npm install metrics, and set metrics: { port: 4001 } in your 'config/environment.js` file