-
Notifications
You must be signed in to change notification settings - Fork 3
Building with Merb
Merb is a lightweight MVC-style Ruby framework for building web applications. It is similar to Ruby on Rails. So similar in fact that if you are used to Ruby on Rails, this tutorial will be very easy to follow. For the purposes of this tutorial, we are also going to use ActiveRecord to store the tasks data in your server.
You need to have merb and DataMapper installed. (DataMapper is an ORM much like ActiveRecord.) You must have version 0.9.3 or later of these installed. Make sure these are installed by running:
$ sudo gem install merb merb_datamapper
This tutorial also assumes that you have mysql installed and running. You can substitute any alternate database that you want. Also note that in a production system, we highly recommend that you consider alternatives to using a database at all as it can dramatically improve performance. For the sake of this tutorial, however, we will use the database because it is the simplest to get going.
On the command line:
$ merb-gen app todos
This should setup a basic merb app. You will need to keep your merb server running in addition to your sc-server instance, so its best to open a new terminal window and change to the directory of the new merb app you just created and run:
$ merb
If everything goes as planned, you should now be able to visit http://localhost:4000 to see your app running.
You’ll be using the DataMapper ORM to setup a basic data model. The first thing you need to do is enable this. Find the config/init.rb file and uncomment the line:
use_orm :datamapperYou will also need to add a database.yml file to configure your connection. Add a file called “database.yml” in your config directory and put this inside of it:
development: &defaults
# These are the settings for repository :default
adapter: mysql
database: todos_development
username: root
password:
host: localhost
test:
<<: *defaults
database: todos_test
production:
<<: *defaults
database: todos_production
Finally, we need to create a new database. If you are using mysql, you can do something like this:
$ sudo mysqladmin create todos_development
Next, we need to setup our model objects. Models are easy to create in DataMapper, very much like ActiveRecord if you are familiar with that, except that can describe your schema directly in your classes.
Create a new Task model using the generator command:
$ merb-gen model task
Inside of this model we need to define the properties we want to store for the task. (Note how this will be the same as we have in the client, but in a more complex app it often won’t be. Your client and server are isolated; that’s why we design it like this…)
In app/models/task.rb add:
class Task
include DataMapper::Resource
property :id, Integer, :serial => true
property :description, String, :nullable => false
property :is_done, Boolean, :default => false
property :order, Integer
end
You now have your basic model setup. Let’s go ahead and get the DB configured. DataMapper has an auto-configuration system that will reset your database with the proper schema. Go back to the command line and run:
$ rake dm:db:automigrate
Your database should now be configured. You can try it out. On the command line type:
$ merb -i
To get the interactive console in merb. Now create a new task:
>> t = Task.new :description => "My Task", :order => 1
=> #<Task description="My Task" is_done=false order=1 id=1>
>> t.save
=> true
Your task should now be added to the DB. Try to find it again:
>> Task.first :description => "My Task"
=> #<Task description="My Task" is_done=false order=1 id=1>
OK, your model is all set. Exit the shell so you can continue. Let’s get the controller written.
>> exit
You need a tasks controller. Add a new file to app/controllers/tasks.rb with this content:
class Tasks < Application
def index
"Hello World"
end
end
Also, you need to register this controller as a resource in the router. Open config/router.rb and add:
Merb::Router.prepare do |r|
r.resources :tasks
end
Let’s see if that did the trick. Kill your merb server, if you still have it running, and restart it. Then visit http://localhost:4000/tasks. If everything went as planned, you should see “Hello World”
Also, we want to provide JSON in our response. While we’re at it, we might as well support XML and JSON as well, so let’s tell the controller that we support both YAML, JSON and XML. Add this to the top of the controller, above the index action:
provides :jsonOK, we have our basic app setup. Let’s get some actions working. The first thing we need to get going is the index action. This action should return JSON with the tasks inside. To get started, we need a helper method that can return the JSON data structure for a task. Add this to the bottom of the controller:
protected
def json_for_task(task)
{ :guid => "/tasks/#{task.id}",
:type => 'Task',
:description => task.description,
:order => task.order,
:isDone => task.is_done }
end
Finally, we need to update the index action to do the right thing. Replace the current index action with the following:
def index
tasks = Task.all.map { |task| json_for_task(task) }
ret = { :content => tasks, :self => '/tasks' }
display ret
end
The code above constructs the JSON hash we would like to return (a hash with a content property that contains an array of records.)
Give it a try. Looks good. Returns something like this:
{"content":[{"description":“My Task”,“type”:“Task”,“guid”:“\/tasks\/1”,isDone,orderLooking pretty good. Let’s move onto the next action…returning a single task.
When we want to retrieve a single task, we need to get just the JSON for that task. This should be easy. Just add a new action:
def show
task_id = params[:id]
task = Task.get(task_id) rescue nil
raise NotFound if task.nil?
ret = { :content => json_for_task(task), :self => "/tasks/#{task_id}" }
display ret
end
We will accept JSON from the client. We are going to need a new helper method here. Add this method below the json_for_task:
def apply_json_to_task(task, json_hash)
task.description = json_hash[:description] if json_hash[:description]
task.order = json_hash[:order] if json_hash[:order]
task.is_done = json_hash[:isDone] if json_hash[:isDone]
end
Now we can start creating some tasks. Add the following action just before the “protected” line:
def create
json = JSON.parse(request.raw_post) rescue nil?
raise NotFound if json.nil?
task = Task.new
apply_json_to_task(task, json)
task.save
# Return the location header with the new URL
url = headers['Location'] = "/tasks/#{task.id}"
ret = { :content => json_for_task(task), :self => url }
status = 201
display ret
end
Updating is nearly as easy. Just add this action:
def update
json = JSON.parse(request.raw_post) rescue nil?
json = json['content'] if json
raise BadRequest if !json
task_id = params[:id]
task = Task.get(task_id) rescue nil
raise NotFound if task.nil?
# Update task
apply_json_to_task(task, json)
task.save
# Return the updated JSON
ret = { :content => json_for_task(task), :self => "/tasks/#{task_id}" }
display ret
end
This one is easiest. Just find the task and destroy it.
def destroy
task_id = params[:id]
task = Task.get(task_id) rescue nil
# if task was found destroy it. If it was not found, do nothing
task.destroy unless task.nil?
"200 Destroyed"
end
SproutCore applications can only communicate with the specific host/port they were loaded from. Since you are running the sc-server tool for development purposes, this is a problem because you load your app on http://localhost:4020 but your Merb app is running on http://localhost:4000. How do we fix this?
Well it turns out that sc-server has a handy proxy tool built in that will help you do this. We will want to proxy all requests for http://localhost:4020/tasks to http://localhost:4000/tasks. This is easy to setup. Just open the sc-config file in your SproutCore project and add the following line to the bottom:
proxy '/tasks', :to => 'localhost:4000'
Now restart your sc-server. Remember you need to keep both the sc-server and your merb app running. Now visit
http://localhost:4020/tasks
You should see a page from Merb. Congrats, you just created your first SproutCore-friendly web service. It’s worth noting now that you’ve not only done that, but you’ve just built a very nice API you can let others use to access your Todo’s service as well. Not bad for a few minutes of work.
This concludes the server-technology specific section of the tutorial. Please continue the tutorial with Step 7 below.
Continue to next step: Step 7: Hooking Up to the Backend »