Copyright (c) 2006 Ezra Zygmuntowicz
Merb.
Lightweight MVC Ruby app server. For high performance dynamic pages.
** Dependencies **
mongrel
erubis
json or fjson
mime-types
archive-tar-minitar
rspec
Install these gems first then you can build the merb gem from svn trunk like so:
$ sudo gem install mongrel erubis json mime-types archive-tar-minitar rspec --include-dependencies
$ svn co http://svn.devjavu.com/merb/trunk merb
$ cd merb
$ rake install
To generate a new merb app after the gem is installed:
$ merb -g myapp
**FEATURES**
*Mongrel handler*
built in that parses incoming requests
including multipart uploads and post as well as ?query=strings. Puts the
params into params and the cookies into cookies when it instantiates your
controller class.
*RouteMatcher and route compiler*
Reads your route definition and compiles
a method on the fly that will match the request path against each route and do the right thing.
*** NEW RESTFULL ROUTES ***
note the r.resource :posts macro. That makes it possible to use a restfull crud style controller for the posts resource
Merb::RouteMatcher.prepare do |r|
r.resources :posts
r.default_routes
r.add '/', :controller => 'files', :action => 'index'
end
The r.default_routes routes adds the standard routes:
/controller/action/id.xml
/controller/action/id
/controller/action.xml
/controller/action
/controller.xml # index action
/controller # index action
*Controllers*
Classes with built in render method and template handling
with instance vars available in the views automatically. Merb also supports
layouts. It will look for a layout named after your controller class first and
then fall back to application.herb if no layout exists named after your controller.
You can use render :layout => :none.
Merb does not automatically render for you in your controller actions, you have
to call render yourself. I consider this a big advantage over the way rails does
it for a few reasons. The main reason is that in rails you can only render once
per action, so it knows if you haven’t rendered it shoudl auto render. Merb on
the other hand, returns to the browser whatever the return value of your
controller’s action method is. This opens up more possibilities imho because
now you can return any string from your action and that will be sent down
the pipe. So Merb’s render method just returns a string and needs to be the
last thing you call in your action. You can render multiple times and capture
the results into @ivars and then render a master template with many embeded
templates. Also if you return a handle on a File or IO object from your action
then merb will hand that over to mongrel to be streamed out to the client. And
if you return a Proc object from your action, it will be called and the
return value sent to the client.
That last point has some cool connotations if you think about it. Merb does
have a mutex lock around the call to your controller’s action anywhere that
you can call AR objects. Merb’s lock is way smaller then rails giant lock
though and allows for many more concurrent requests to be handled by one
process. By returning a Proc object from your action, you allow merb to
release the lock and the proc is called in multi threaded way. This allows
for all kinds of cool streaming and ‘futures’ where you return the proc and
release the mutex. It’s basically like handing over the proc to mongrel and
mongrel handles calling it in a thread safe manner.
class Test < Merb::Controller
def hello
# params, headers and cookies are available here.
@name = params[:name]
render
end
end
You can also render partials like so:
<%= partial(:comments) %>
This assumes a _comments.rhtml file in the same view dir as the current
controller/view
Partials compile the template ands returns a string. So you can also call
them and assign them to a var if you want:
def someaction
@one = partial(:one)
@two = partial(:two)
end
partials can also render views from other controllers by specifying the path
partial('/shared/foo')
Merb also allows for returning javascript instead of html for ajax actions
You have to use the render_js instead of normal render
def ajax_action
@posts = Post.find :all
render_js
end
# ajax_action.jerb
$('comments').update('<%=js partial(:posts) %>');
# _posts.herb
<% @posts.each do |p| %>
-
<%= p.title %>
<%= p.body %>
<% end %>
*Restful Controllers*
restful controllers use a different dispatch system based on the request method verbs. Merb
supports multi return values based on the accept header via respond_to
class Posts < Merb::Controller
# GET /posts
# GET /posts.xml
def index
@posts = Post.find :all
respond_to {|format|
format.html { render }
format.js { render :js => 'index' }
format.xml { render :xml => @posts.to_xml }
}
end
# GET /posts/1
# GET /posts/1.xml
def show
end
# GET /posts/new
def new
end
# GET /posts/1;edit
def edit
end
# POST /posts
# POST /posts.xml
def create
end
# PUT /posts/1
# PUT /posts/1.xml
def update
end
# DELETE /posts/1
# DELETE /posts/1.xml
def destroy
end
end
*Controllers have powerful before and after filters*
Use the before method in your controllers. before accepts either a symbol, string or a Proc/lambda object. If you give it a symbol it will call a method with the same name as the symbol. If you give it a proc that takes one argument it will call the proc with the current controller as that argument. You can use :only and :exclude as options to your filters to exclude or include actionsfrom certain filters. :only and :exclude take :symbols or [:sym, :sam] array of symbols.
class Foo < Merb::Controller
before :setup_user, :only => :foo
before lambda {|c| c.headers['X-Foo] = 'bar' }, :exclude => [:foo, :baz]
def setup_user
# blah blah
end
def foo
# blah
end
def regular_action
# blah
end
end
To stop the before filter chain you use throw :halt with a few options:
# halts the filter chain and calls filters_halted which you can override
# in your controller to specialize it.
throw :halt
# halts the filters and calls the method named after the symbol:
throw :halt, :other_action
# halts the filter chain and returns the result of the Proc being called
throw :halt, Proc.new{ |c| c.redirect "/foo" }
# halts the chain and returns whatever is in the string
throw :halt, "You don't have permissions dude!
"
or even render templates:
throw :halt, render 'foo'
throw :halt, partial 'foo'
After filters accept a symbol, string or Proc and call that proc with the controller:
after Proc.new {|c| Tidy.new(c.body) }, :only => :index
Sessions are available when you start merb with the sql_session set to true or the
memory_session set to true. See generated app for migration too add session table.
Helpers: dist/app/helpers/global_helper.rb will be available to all of your views.
Helpers named after your controller plus _helper.rb will be included in the views
for that controller only.
*The merb server*
right now you add your routes in
the appdir/dist/conf/router.rb file. So by default it runs on port 4000
$ cd /path/to/your/merb/app
$ merb
Or to start merb on a different port:
$ merb -p 3500
To start a cluster of merb servers you specify the first port and then how many
servers you want spawned. SO this command will start a merb instance on ports
3000, 3001, 3002
$ merb -p 3000 -c 3
To start a Merb IRB console where all your models and other classes are pre loaded
use the -i flag
$merb -i
*File uploads*
This is one of the things that Merb was written for. Rails doesn't allow
multiple concurrent file uploads at once without blocking an entire rails backend for each file upload. Merb allows multiple file uploads at once.
When a file is uploaded with Merb, it gets put in a Tempfile. So
you just want to copy it to the right place on the filesystem.
def upload
puts params[:file].inspect
FileUtils.mv params[:file][:tempfile].path, MERB_ROOT+"/uploads/#{params[:file][:filename]}"
render
end
A file upload will have a hash of params like this:
{
:filename => File.basename(filename),
:content_type => content_type,
:tempfile => ,
:size => File.size(body)
}
*Merb app layout*
A Merb app contains everything it needs to run in production in the
MERB_ROOT/dist directory. So for deployment you only need to deploy the dist dir. This
keeps your test code and development plugins separate from your main app and lets you
not deploy them to the live server. You deal with two things with this setup, MERB_ROOT
and DIST_ROOT. MERB_ROOT is the root of the whole tree. And DISTROOT is MERB_ROOT+/dist
You will cd into MERB_ROOT to run the merb command line. ANd when you deploy live you
will put the dist dir into another empty MERB_ROOT on the production server.
merb_app:
Rakefile
README
scripts
test
spec
unit
plugins
dist
app
controllers
models
views
conf
lib
public
plugins
schema