-
Notifications
You must be signed in to change notification settings - Fork 240
Geddy Tutorial
##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;
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.
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 theTodos#index
action (hint, check outconfig/router.js
) - Add some logging with
geddy.log
- Set up metrics by running
mkdir node_modules && npm install metrics
, and setmetrics: { port: 4001 }
in your 'config/environment.js` file