-
Notifications
You must be signed in to change notification settings - Fork 3
Building With Merb 1.0
Merb is a lightweight MVC-style Ruby framework for building web applications. Merb was originally conceived as an alternative to Ruby on Rails and improves on many aspects of Rails 2.0. As of December 23, 2008, the Merb project has begun to be merged with Rails, and a version with the best features of both will be released in late 2009 under the name Rails 3.0. Merb is considerably more efficient (and thus faster) than Rails and is well-suited to serving a RESTful web service such as the one we will need for this tutorial.
You need to have Merb version 1.0.8 or newer installed.
You can install it by running:
$ sudo gem install merb
If you already have it installed, you can update Merb by running
$ sudo gem update merb
This tutorial assumes that you will use Merb’s default database (sqlite3) and database ORM (DataMapper). Using the defaults eliminates the need for any configuration.
Sqlite3 comes preinstalled on OSX and many flavors of Unix. You can check
whether it’s installed by typing sqlite3
on the command line, which will bring you to a sqlite3 prompt if it is installed.
Make sure you have installed the database driver that allows DataMapper to connect to sqlite3:
$ sudo gem install do_sqlite3
On the command line:
$ merb-gen app todos
This will set up a basic Merb app. You will need to keep your Merb server running in addition to your sc-server instance, so it’s 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.
If you are using Merb’s default database (sqlite3) and database ORM (DataMapper) there is no need for any configuration.
Merb’s generator has already set up a database.yml
file which defaults to SQLite and a database named sample_development.db
.
Merb has a command, merb-gen resource
, which creates a model, controller, and views all at once. We are building a web service for our SproutCore app so we don’t need views. Consequently, instead of using the merb-gen resource
command, we’ll create a model and controller separately.
Next, we need to create our model objects. Models are easy to create in DataMapper, very much like ActiveRecord in Rails (if you are familiar with that), except that you 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, Serial
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 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.
Note: If you get an error that refers to “uninitialized constant Nokogiri,” you’ll need to update the “webrat” gem:
$ sudo gem update webrat
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.
>> exitYou need a tasks controller. Create a new Task model using the generator command:
$ merb-gen controller tasks
Open app/controllers/tasks.rb it will have the following content:
class Tasks < Application
def index
"Hello World"
end
end
You will need to register this controller as a resource in the router. Open config/router.rb and add:
Merb::Router.prepare do
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:
If everything went as planned, you should see “Hello World.”
Merb has the hability to format your responses in several different formats, but we only want to provide JSON in our response for this tutorial. Add this to the top of the controller, above the index action:
only_provides :json
If you want to support XML as well, you will need to tell the controller that we support both YAML, JSON and XML. Just replace only_provides with the following line. (You will have to add .json, .xml to your request urls)
provides :xml, :json, :yaml
OK, we have our basic app ready. 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}",
: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 in the browser:
If it works, you will download a file containing this:
{"content":[{"order":1,"description":"My Task","guid":"\/tasks\/1","isDone":false}],"self":"\/tasks"}
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 to the controller:
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
Try it in the browser:
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'] unless json_hash['description'].nil?
task.order = json_hash['order'] unless json_hash['order'].nil?
task.is_done = json_hash['isDone'] unless json_hash['isDone'].nil?
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?
json = json['content'] if json
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?
You’ll find that sc-server includes a handy proxy tool that solves this problem. 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
You should see a page from Merb. You’ve 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 »
The Building With Merb instructions were originally written by Charles Jolley for Merb version 0.9.3.
This version of the Building With Merb instructions (suitable for Merb 1.0.8) was written by Daniel Kehoe on 17 January 2009.
This was updated to work with SC 1.0 on April 17 2009 by Juan Pinzon