diff --git a/Makefile b/Makefile index 89a1cdff..9318ef7d 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,17 @@ build: install: @mkdir -p $(DESTDIR)$(PREFIX)/bin && \ - mkdir -p $(DESTDIR)$(PREFIX)/lib/node_modules/geddy && \ - mkdir -p ./node_modules && \ - npm install jake utilities model barista && \ + mkdir -p $(DESTDIR)$(PREFIX)/lib/node_modules/geddy && \ + mkdir -p ./node_modules && \ + npm install jake utilities model barista && \ + cp -R ./* $(DESTDIR)$(PREFIX)/lib/node_modules/geddy/ && \ + ln -snf ../lib/node_modules/geddy/bin/cli.js $(DESTDIR)$(PREFIX)/bin/geddy && \ + chmod 755 $(DESTDIR)$(PREFIX)/lib/node_modules/geddy/bin/cli.js && \ + echo 'Geddy installed.' + +quickinstall: + @mkdir -p $(DESTDIR)$(PREFIX)/bin && \ + mkdir -p $(DESTDIR)$(PREFIX)/lib/node_modules/geddy && \ cp -R ./* $(DESTDIR)$(PREFIX)/lib/node_modules/geddy/ && \ ln -snf ../lib/node_modules/geddy/bin/cli.js $(DESTDIR)$(PREFIX)/bin/geddy && \ chmod 755 $(DESTDIR)$(PREFIX)/lib/node_modules/geddy/bin/cli.js && \ diff --git a/examples/related_models/app/models/box.js b/examples/related_models/app/models/box.js index d3255018..c59c6b1e 100644 --- a/examples/related_models/app/models/box.js +++ b/examples/related_models/app/models/box.js @@ -6,7 +6,7 @@ var Box = function () { this.hasMany('Things'); - this.adapter = 'Mongo'; + this.adapter = 'mongo'; }; Box = geddy.model.register('Box', Box); diff --git a/examples/related_models/app/models/thing.js b/examples/related_models/app/models/thing.js index d0a2ae79..cbceb936 100644 --- a/examples/related_models/app/models/thing.js +++ b/examples/related_models/app/models/thing.js @@ -7,7 +7,7 @@ var Thing = function () { this.belongsTo('Box'); - this.adapter = 'Mongo'; + this.adapter = 'mongo'; }; Thing = geddy.model.register('Thing', Thing); diff --git a/examples/related_models/app/models/widget.js b/examples/related_models/app/models/widget.js index 96d7bba2..56d3ce18 100644 --- a/examples/related_models/app/models/widget.js +++ b/examples/related_models/app/models/widget.js @@ -6,7 +6,7 @@ var Widget = function () { this.hasOne('Thing'); - this.adapter = 'Mongo'; + this.adapter = 'mongo'; }; Widget = geddy.model.register('Widget', Widget); diff --git a/examples/todo_app_coffee/app/models/todo.coffee b/examples/todo_app_coffee/app/models/todo.coffee index 3f05a122..1a781d57 100644 --- a/examples/todo_app_coffee/app/models/todo.coffee +++ b/examples/todo_app_coffee/app/models/todo.coffee @@ -16,6 +16,6 @@ Todo = -> @validatesWithFunction 'status', (status) -> status == 'open' || status == 'done' - @adapter = 'Mongo' + @adapter = 'mongo' Todo = geddy.model.register 'Todo', Todo diff --git a/site/tutorial.html b/site/tutorial.html index 494604da..81ea5343 100644 --- a/site/tutorial.html +++ b/site/tutorial.html @@ -13,7 +13,8 @@ - + + @@ -97,7 +98,8 @@ -
+ +In this tutorial we'll learn how to use Geddy by creating a simple todo manager applciation. There will be two created applications one created from scaffolding(Finished version) and one created from using resources(Finished version).
+ +In this tutorial we'll learn how to use Geddy by creating a simple todo manager applciation. We will create two applications one using scaffolding and one using resources. See the finished version.
Next, install Geddy from NPM, this will also install Jake:
$ [sudo] npm install -g geddy-
(Note installing packages globally may require super-user access)+
We need to install it globally (-g) so we can use geddy generators or start the server. More on this later. + Note: installing packages globally may require super-user access.
Now that we have Geddy installed we need to learn how to use it's command from the CLI. There are a few commands and options that help with the development process of creating applications using Geddy. Here we will go over what each of them do. Note If no arguments are given Geddy will start up the server if it's a Geddy application, otherwise it will show the help dialog.
+Now that we have Geddy installed we need to learn how to use its command from the CLI. There are a few commands and options that help with the development process of creating applications using Geddy. Here we will go over what each of them do. Note if no arguments are given Geddy will start up the server if it's a Geddy application, otherwise it will show the help dialog.
app <name>
: Create a new Geddy applicationresource <name> [model attributes]
: Create a new Geddy resource. Resources include a model, controller and a routescaffold <name> [model attributes]
: Creates a new Geddy scaffolding. Scaffolding includes everything Resources have as well as views and a default model adapterscaffold <name> [model attributes]
: Creates a new Geddy scaffolding. Scaffolding includes everything Resources have as well as viewssecret
: Generate a new application secret in `config/environment`controller <name>
: Generate a new controller including an index view and a routemodel <name> [model attributes]
: Generate a new modelconsole
: opens a console in the context of geddyEach of Geddy's commands(app
, resource
, controller
, etc.) take a command or set of commands(excluding secret
). Here we'll learn how to use those commands.
Each of Geddy's commands(app
, resource
, controller
, etc.) take a command or set of commands(excluding secret
and console
). Here we'll learn how to use those commands.
app
takes a single argument being the name you'd like, then it will generate a base application. If no name is given the command will fail.secret
doesn't take any arguments, it will find your config/environment
file and create a new secret in it deleting any other secret.controller
takes a single argument being a name. It will create a new controller, a route and a index view. If you also include the options --jade
, --handle
or --mustache
you can substitute the template language to your liking.controller
takes a single argument being a name. It will create a new controller, a route and an index view. If you also include the options --jade
, --handle
or --mustache
you can substitute the template language to your liking.model
takes one or more arguments, the first being a name and the others being a set of model properties. We won't go over model properties right now but you can learn about them here. This will create a new model including the model properties given.model
takes one or more arguments, the first being a name and the others being a set of model properties. We won't go over model properties right now but you can learn about them in the next section. This will create a new model including the model properties given.resource
takes one or more arguments, the first being a name and the others being a set of model properties. We won't go over model properties right now but you can learn about them here. This will create a controller, a model including the given model properties and a resource route.resource
takes one or more arguments, the first being a name and the others being a set of model properties. This will create a controller, a model including the given model properties and a resource route.scaffold
takes one or more arguments, the first being a name and the others being a set of model properties. We won't go over model properties right now but you can learn about them here. Scaffolding includes a controller, a model including the given model properties as well as a default model adapter a resource route and will create all views. If you also include the options --jade
, --handle
or --mustache
you can substitute the template language to your liking.scaffold
takes one or more arguments, the first being a name and the others being a set of model properties. Scaffolding includes a controller, a model including the given model properties as well as a default model adapter a resource route and will create all views. If you also include the options --jade
, --handle
or --mustache
you can substitute the template language to your liking.console
doesn't take any arguments, it will start a geddy console.
There are a couple commands(resource
, model
and scaffold
) that also include a model properties argument. This is a list seperated by spaces that include the property, it's type and an optional default setting. Below are some examples of how they are used in the commands.
There are a three commands(resource
, model
and scaffold
) that also include model property arguments. This is a list seperated by spaces that include the property, its type and an optional default setting. Below are some examples of how they are used in the commands.
$ geddy scaffold user name:string-
The example above example will create our normal scaffolding and include a name
option that's a string
type. If no type is given it will default to string
.
The example above will create our normal scaffolding and include a name
property of type string
. If no type is given it will default to string
.
$ geddy scaffold user name:default-
This example creates scaffolding but includes name
as the default property that will be used when displaying the content in the views. In this example the property name
is given the type string
because no type was given, you could of also written it name:string:default
, or you could've used a different type of course. the default
setting also includes a alias called def
, if no default property is given it will use id
as the default.
This example creates scaffolding but includes name
as the default property that will be used when displaying the content in the views. In this example the property name
is given the type string
because no type was given, you could of also writte name:string:default
, or you could've used a different type of course. The default
setting also includes an alias called def
. If no default property is given Geddy will use id
as the display property.
-
$ geddy scaffold user name:default id:number-
As the above examples this creates are scaffold, and this time we use name
type string
as the default property we also overwrite the included id
property. If no id property is given Geddy will use include id
type string
$ geddy scaffold user name:default id:int+
This time we used name
type string
as the default property. We also overwrote the included id
property with a different type (by default it's a string).
-
(Note a ID property will always be created)+
Note: an ID property will always be created.
This will be a short tutorial as scaffolding will do almost everything for us, I won't go into detail on what it does as it will be covered in exstensive detail in the resources tutorial. The source for this tutorial will be here
+This will be a short tutorial as scaffolding will do almost everything for us, I won't go into detail on what it does as it will be covered in exstensive detail in the resources tutorial. The source for this tutorial will be here.
First we'll create our application, this will create a base so we can start on.
$ geddy app todo_app-
Now you can start the application up by simply doing $ cd todo_app && geddy
, Then open your browser to localhost:4000, and you'll find the hello world page.
Let's spend some time reviewing what geddy did. The previous command created a lot. During the tutorial we will edit and review some of this files, but we'll briefly explain what they are now so you get familiar with the base application.
-So now we want to create a scaffold that will be used to create our todo items. We will create a title and status property so that we have some attributes to use.
-$ geddy scaffold todo title status+
app/controllers
: contains the base controller and the main controller. All controllers will go in this folderapp/views/layouts/application.html.ejs
: layout used by default by all the viewsapp/views/main/index.html.ejs
: main view displayed when you visit the root of your web applicationconfig/development.js
: configuration for the development environmentconfig/environment.js
: configuration for all your environmentsconfig/init.js
: this is where you write code that will be run only once your app starts.config/production.js
: configuration for the production environmentconfig/router.js
: contains route settings. It has some examples and you can learn more about routes from the Wiki.public/
: contains static assets that will be served directly by geddy's server public/css/
: Geddy uses twitter bootstrap. These are referenced by the layout file (application.html.ejs
)public/img/
: contains a few images used by twitter bootstrap. Your images will usually go here as wellpublic/js/
: bootstrap and jquery scriptsNow from your app's root simply start geddy
-Now that we've created a scaffolding we should install the mondodb-wrapper package and install a mongodb server if you haven't already. This is because scaffolding will create a default model adapater for us that uses mongodb.
-$ [sudo] npm install -g mongodb-wrapper-
Once that gets installed and we start our mongodb server up we can start up Geddy to test our application. $ geddy
+$ cd todo_app +$ geddy+ +
Then open your browser to localhost:4000, and you'll find the hello world page.
-Open your browser to localhost:4000/todos and you'll get a list of the todos which should be empty. Go ahead and look around, you can create show edit and delete todo items. We're going to make a few changes though.
+So now we want to create a scaffold to manage our todo items. We will create a title and status property so that we have some attributes to use.
+$ geddy scaffold todo title:default status-
The first thing we'll do it edit the model some so open 'app/models/todo.js' in your editor, and make these changes.
--... -this.defineProperties({ - id: {type: 'string', required: true} - , title: {type: 'string', required: true} - , status: {type: 'string', required: true} -}); - -this.validatesPresent('title'); -this.validatesLength('title', {min: 5}); - -this.validatesWithFunction('status', function (status) { - return status == 'open' || status == 'done'; -}); -... --
Here we are making it so the title property is required and have a minumum of 5 characters. We also made it so the status acts like a boolean attribute but uses custom names instead of true/false. Now we'll need to edit the edit and add views to reflect the status changes.
+We are almost done. Now you have to restart geddy
+$ geddy+ +
Open your browser to localhost:4000/todos and you'll get a list of the todos which should be empty. Go ahead and look around, you can create show edit and delete todo items. We're going to make a few changes though.
+ +The first thing we'll do is to add some validation to our Todo model. So open 'app/models/todo.js' in your editor and add the following lines anywhere inside the constructor function
+var Todo = function () { ... -<label for="status">status</label> -<div class="controls"> - <select name="status", class="span1"> - <option>open</option> - <option>done</option> - </select> -</div> + // Add this inside the constructor function + this.validatesPresent('title'); + this.validatesLength('title', {min: 5}); + + this.validatesWithFunction('status', function (status) { + return status == 'open' || status == 'done'; + }); ... --
Now that we've made the needed changes, restart Geddy to update our model changes, now we've got a good todo application running and didn't really have to do anything. Scaffolding is very good when you don't need to do much. To learn more about models and applications keep reading and follow the resource application tutorial
+}; +Todo = geddy.model.register('Todo', Todo); + +Here we are making it so the title property is required and have a minumum of 5 characters. We also made it so the status acts like a boolean attribute but uses custom names instead of true/false. We should also change our edit
and add
views to limit the options, but we will do it as part of the resources tutorial, for now we will leave the views the way they are.
Now that we've made the needed changes, restart Geddy to update our model changes. Go and play with the app again, create a todo item, try to edit and test the validation rules. We've got a good todo application running and didn't really have to do much. Scaffolding is very good when you need something simple to get you started. To learn more about controllers and views keep reading and follow the resources tutorial.
Let's start by using the geddy
executable to generate a basic app-structure.
$ geddy app todo_app-
Now let's try out our new application by doing $ cd todo_app && geddy
command, your app should be running on port 4000. Visit http://localhost:4000 in your browser to see your app.
Now let's try out our new application by running geddy from your application's root
+ ++$ cd todo_app +$ geddy+ +
Your app should be running on port 4000. Visit http://localhost:4000 in your browser to see your app.
-Now, lets actually get started building our To Do list manager. First, we'll need to generate the todo
resource. We do this using the geddy
executable as well:
$ geddy resource todo name string+
Now, let's get started building our To Do list manager. First, we'll need to generate the todo
resource. We do this using the geddy
executable as well:
$ geddy resource todo title:string status
What did that do?
todo
model including the given model propertiestodos
controllertodos
view directorytodos
view directory. Please note the folder is empty since resource won't generate any views for you. /todos
(GET)Now lets restart Geddy so our changes are updated. $ geddy
Once it starts up point your browser to http://localhost:4000/todos.json to view the index action in JSON. We don't currently have any views but we're about to do that now.
To start creating our views, create a few files in 'app/views/todos', those being:
+To start creating our views, create a few files in app/views/todos
, those being:
_form.html.ejs
add.html.ejs
edit.html.ejs
index.html.ejs
show.html.ejs
We won't go into to much detail here, as it should be pretty self explanatory but I'll go through some things.
-First we'll create the _form partial template, this will hold all the form data for edit and add actions.
+First we'll create the _form.html.ejs
partial template, this will hold all the form data for edit and add actions
+ .
<% var isUpdate = params.action == 'edit' @@ -334,7 +353,8 @@-Creating the Todo views
, action = isUpdate ? todoPath(params.id) + '?_method=PUT' : todosPath , deleteAction = isUpdate ? todoPath(params.id) + '?_method=DELETE' : '' , btnText = isUpdate ? 'Update' : 'Add' - , doneStatus = isUpdate ? 'checked' : '' + , doneSelectAttributes = isUpdate && todo.status === 'done' ? "selected=true" : '' + , openSelectAttributes = isUpdate && todo.status === 'open' ? "selected=true" : '' , titleValue = isUpdate ? todo.title : '' , errors = params.errors; %> @@ -356,11 +376,11 @@Creating the Todo views
</div> <% if (isUpdate) { %> <div class="control-group"> - <label for="status">Status</label> + <label for="status" class="control-label">Status</label> <div class="controls"> - <select name="status"> - <option>open</option> - <option>done</option> + <select name="status" class="span6"> + <option <%=openSelectAttributes%>>open</option> + <option <%=doneSelectAttributes%>>done</option> </select> </div> </div> @@ -372,31 +392,36 @@Creating the Todo views
<% } %> </div> </fieldset> -</form> -
Here we created a couple variables so we can tell if it's for a edit or add action, then if we have any errors we dislay them. Also we are using a couple view helpers(contentTag) which are helpful with dealing with assets, links, etc. You can read more about our view helpers here.
+</form> + +Here we created a couple variables so we can tell if it's for a edit or add action, then if we have any errors we dislay them. Also we are using a couple view helpers (contentTag) which are helpful with dealing with assets, links, etc. You can read more about our view helpers here.
-Now that we've created a base for our add and edit actions, we'll do them now. They're simple sense they just use the _form partial
+Now that we've created a base for our add and edit actions, we'll do them now. They're simple they just use the _form partial. Add the following code to add.html.ejs
<div class="hero-unit"> <%= partial('_form', {params: params}); %> -</div> --
This template will be for both add and edit actions. We just create a partial from _form and include all the params.
+</div> + +The edit view is slightly different because we will need to pass the todo object to the partial. Modify app/views/todos/edit.html.ejs
with the following code:
+<div class="hero-unit"> + <%= partial('_form', {params: params, todo: todo}); %> +</div>+ +
Now that we have views that will create todo items let's add a simple show.html.ejs
just so we can test everything end to end. In the following code I just loop through the params.
Now that we have views that will create todo items let's create the show action so we can view the todo items we create.
<div class="hero-unit"> + <%- linkTo('Edit this todo', editTodoPath(params.id), {class: 'btn pull-right'}); %> <h3>Params</h3> <ul> - <% for (var p in params) { %> + <% for (var p in todo) { %> <li><%= p + ': ' + params[p]; %></li> <% } %> </ul> -</div> --
I will leave the show action to you for a exercise, but herre I just loop through the params and show them.
- +</div> +Finally we need to create the index action to link everything together.
<div class="hero-unit"> @@ -407,16 +432,16 @@-Creating the Todo views
<% for (var i in todos) { %> <div class="row todo-item"> <div class="span8"> - <h3><%- linkTo(todos[i].title, editTodoPath(todos[i].id)) %></h3> + <h3><%- linkTo(todos[i].title, todoPath(todos[i].id)) %></h3> </div> <div class="span4"><h3><i class="icon-list-alt"></i><%= todos[i].status; %></h3></div> </div> <% } %> -<% } %> -
For the index action we just have a link to add new items, and a list of ll the items, with a link to each of their edit paths. If you notice we're using special helpers here, that create links to the path specified.
+<% } %> -For the index action we just have a link to add new items, and a list of all the items, with a link to each of their edit paths. If you notice we're using special helpers here, that create links to the path specified.
+ +We're ready to start in on modeling our data. Geddy provides us with some pretty cool tools to do this:
These tools should look somewhat familiar to anyone who's used an ORM-system like Ruby's ActiveRecord, or DataMapper.
-Go ahead and open up app/models/todo.js
. Read through the commented out code there for some ideas on what you can do with models. 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.)
Go ahead and open up app/models/todo.js
. Read through the commented out code there for some ideas on what you can do with models. We'll be writing our model from scratch for this tutorial, so let's leave that commented out.
So, minus the commented out code, you should have a file that looks like this:
var Todo = function () { this.defineProperties({ - id: {type: 'string', required: true} - , title: {type: 'string'} + title: {type: 'string'} , status: {type: 'string'} }); }; -Todo = geddy.model.register('Todo', Todo); --
The `defineProperties` method takes any number of properties to be added to the model. The keys in the object will be added as properties on the model. The values are just objects that describe the properties. When we ran the scaffold command it created these for us. But we want to change it so they are all `required` so set `title` and `status` to match `id` now.
+Todo = geddy.model.register('Todo', Todo); -To learn more, check out the readme.
+The defineProperties
method takes any number of properties to be added to the model. The keys in the object will be added as properties on the model. The values are just objects that describe the properties. When we ran the scaffold command it created these for us. But we want to change it so they are all `required`. To learn more, check out the readme.
There's also more detailed validation API. While we're here, lets use that API to set up some more validations:
+There's also a more detailed validation API. While we're here, let's add some validation as well. The final code should look like this:
-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
.
For more information about Geddy's Models, you can check out the Model wiki page.
- -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 keep the data in memory. We'll hang a todos
array off of our global geddy
object to stick the data in. If you want to make this app more robust after the tutorial, you could write an adapter to put your data in a SQL database, or Redis, or even just write it to a text-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)); + this.defineProperties({ + title: {type: 'string'} + , status: {type: 'string'} }); -} -- -
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. This is in your application-memory, so it will disappear when you restart the server.
A model-adapter provides the basic CRUD methods a model needs. Our data source is pretty simple (just an array!), so writing our model adapter should be pretty simple too.
+ + this.validatesPresent('title'); + this.validatesLength('title', {min: 5}); -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
and add some code so that it's loaded into our app when it starts up. 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; -+
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.
We're working on making this nicer.+
For the status
property, we used a function to validate that the property is always set to either open
or done
.
We created a blank model-adapter object, and add the Todo
model adapter onto it.
For more information about Geddy's Models, you can check out the Model wiki page.
-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.
+Now that we've set up our todo
model, we need to define a way to store it. To keep our models persistance agnostic, Geddy uses model-adapters. By default it will store objects in memory using the memory
model adapter. You can change the default memoryAdapter in config/development.js
.
-
defaultAdapter = 'memory'-
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:
Now we've got a place to store our todo
's. This is in your application's memory, so it will disappear when you restart the server.
-this.save = function (todo) { - todo.saved = true; - geddy.todos.push(todo); -}; -+
Install a mongodb server if you haven't already and $ [sudo] npm install -g mongodb-wrapper
to install the required mongodb-wrapper and set defaultAdapter = 'mongo'
in config/development.js instead of the memory adapter. You will also have to specify the db configuration db: { mongo: { dbname: 'model_test' }
. For more information see the Model Adapter Wiki Page
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.
Controllers sit between the views and models. They are also the entry point of our code. When a user gets a page a function in a controller, also called a controller acton, will get invoked. The controller will usually interact with the model and pass it to the view. The pattern isn't as black and white, but for the purpose of the tutorial, let's move on to actually write some controller actions.
-Go ahead and take a look at the create
action in app/controllers/todos.js
To save a todo we need to edit the create
action in app/controllers/todos.js
. It's not doing much at the momment so lets modify it a little bit.
this.create = function (req, resp, params) { - // Save the resource, then display index page - this.redirect({controller: this.name}); -}; -- -
Pretty simple, right? It's stubbed it out for you. So let's 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 built-in sessions too, so this might be another good spot to improve your app after you finish the tutorial.
+ var self = this + , todo = geddy.model.Todo.create({title:params.title, status:'open'}); + + todo.save(function(err, data) { + if (err) { + params.errors = err; + self.transfer('add'); + } else { + self.redirect({controller: self.name}); + } + }); +}; -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 default status.
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.
Then we call we call the save
method. Internally, save does two things. It validates the model based on the rules we defined earlier. This is similar to calling todo.isValid()
. If the model was valid, it will delegate to the model adapter configured previously to actually persist the model. If either step fails, you will get an error collection as the first parameter of the function and we redirect the user back to the /todos/add route. Otherwise we redirect to the controller's default action self.redirect({controller: self.name});
.
Open up /app/controllers/todos.js
again and take a look at the index
action. It should look something like this:
Now that we we can create To Do items, we should probably list them somewhere. Lets change the index
action in the todos
controller.
-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
, to pass that list down into your view.
Open up /app/controllers/todos.js
again and replace the current implementaton with the following code.
this.index = function (req, resp, params) { - this.respond({todos: geddy.todos}); -}; -- -
Now that we can can load todo items you can test it by starting up Geddy and go to http://localhost:4000/todos and you can view the list of items.
- -Now that we have our index action working as expected, we should probably work on the show
controller action.
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 define a load
method in this adapter, for getting one of the todos from the list:
-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 calls the callback with a blank object. Now we need to use this method in the todos
controller's show action.
This is a simple example that finds a single item by iterating the entire collection, but you could write an adapter that does a SQL-call to a database, or makes an API-call to a Web service.
+ geddy.model.Todo.all(function(err, todos) { + self.respond({params: params, todos: todos}); + }); +}; -This part is a bit simpler and it follows a similar pattern. Instead of calling create in geddy.model.Todo
this time we simply call all
and we pass the data back to the view for rendering
Open up your todos
controller again and take a look at it's show
action. It should look something like this:
Now that we can can load todo items you can test it by starting up Geddy and going to localhost:4000/todos and you can view the list of items.
--this.show = function (req, resp, params) { - this.respond({params: params}); -}; -+
Lets use the load method that we just created:
+Now that we have our index action working as expected, we should work on the show
controller action to display todo details.
this.show = function (req, resp, params) { var self = this; - geddy.model.adapter.Todo.load(params.id, function (todo) { - self.respond({todo: todo}); - }); -}; --
Now we have a working show action in the controller to load items from the list.
-Now we have a working show action in the controller to load items.
-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:
Alright, now that we can view our todos let's edit the update
and edit
actions in the todos
controller. They 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); +this.edit = function (req, resp, params) { + var self = this; + geddy.model.Todo.load(params.id, function(err, todo) { + self.respond({params: params, todo: todo}); + }); }; --
This loops 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 do a SQL UPDATE or similar here instead.
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}); -}; -+ var self = this; -
You'll want to edit it to make it look like this:
+ geddy.model.Todo.load(params.id, function(err, todo) { + todo.updateAttributes(params); --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}); + todo.save(function(err, data) { + if (err) { + params.errors = err; + self.transfer('edit'); + } else { + self.redirect({controller: self.name}); + } + }); }); -}; -+}; -
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 its 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 (hooray for progressive enhancment).
The delete is really simple specially now that you're familiar with the pattern. This time you will have to call remove passing the id of the todo you want to delete. We will leave the details as an excercise. Remember that you can always compare your solution to the final version.
+At this point you should have a working To Do List app!
@@ -734,9 +614,11 @@If you want to explore a little more, here are some other things you could do:
Main#index
route to point to the Todos#index
action (hint, check out config/router.js
)geddy.log
npm install metrics
, and uncomment the metrics entry (metrics: { port: 4001 }
) in your config/environment.js
fileMain#index
route to point to the Todos#index
action (hint, check out config/router.js
)geddy.log