Skip to content
juanpin edited this page Sep 12, 2010 · 20 revisions

About 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.

Before You Get Started

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.

Create your Merb App

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.

Setup DataMapper and your Database

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 :datamapper

You 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

Create Your Model

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

Create Your Controller

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 :json

Add an index action

OK, 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,order

Looking pretty good. Let’s move onto the next action…returning a single task.

GET 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

Creating a Task

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 a Task

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

Deleting a Task

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

Setup Your Proxy

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

Your Done!

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 »

Related Links

Comments