5. emphasizes efficiency
and hackability
Instead of emphasizing a complete, monolithic framework with everything built in for the 80-20 case, Merb emphasizes making it easy to build what you need. That doesn’t
mean we don’t want to make it easy to get started. Merb’s defaults provide the get-up-and-running feel that you get from more monolithic frameworks.
7. improved api
We try to learn from other frameworks in Ruby and in the programming world at large. When we find something we like, we use it. When we find something we think we can
make easier to interact with, we do that.
8. give you as much
headroom as possible
Our goal is to maintain as small a footprint as possible to give your app more system resources to use.
9. no &:sym in merb
For instance, we don’t use Symbol#to_proc inside Merb, which is a convenience but dramatically slows down operations that use it.
10. slowdowns are a bug
Slowdowns are not unfortunate. They are a bug. If you notice that a commit to Merb makes it slower, file a ticket on LightHouse. We will take it seriously.
23. based on rack
Rack is based on Python’s WSGY, which allows a web framework like Merb to interact with a bunch of web servers (like Mongrel, Thin, Ebb, Webrick, CGI, FastCGI)
24. class ApiHandler
def initialize(app)
@app = app
end
def call(env)
...
end
end
use ApiHandler
run Merb::Rack::Application.new
Here’s an example Rack handler.
25. def call(env)
request = Merb::Request.new
if request.path =~ %r{/api/(.*)}
[200, {“Content-Type” => “text/json”},
Api.get_json($1)]
else
@app.call(env)
end
end
Here’s what the call method looks like. You return a tuple of [status_code, headers, body].
27. Merb::Router.prepare do |r|
r.resources :posts do |post|
post.resources :comments
end
end
We support simple stuff, like resources or even nested resources.
28. Merb::Router.prepare do |r|
r.match(%r{/login/(.*)},
:user_agent => /MSIE/).
to(:controller => “account”,
:action => “login”,
:id => “[1]”
end
But we also support regexen.
29. Merb::Router.prepare do |r|
r.match(%r{/login/(.*)},
:user_agent => /MSIE/).
to(:controller => “account”,
checks request.user_agent
:action => “login”,
:id => “[1]”
end
Or any part of the Request object.
30. Merb::Router.prepare do |r|
r.match(%r{/login/(.*)},
:user_agent => /MSIE/).
to(:controller => “account”,
:action => “login”,
:id => “[1]”
end
You can use any capture from your regex in #to hashes via “[capture_number]”
31. Merb::Router.prepare do |r|
r.match(%r{/login/(.*)},
:method => “post”).
to(:controller => “account”,
:action => “login”,
:id => “[1]”
end
Here’s another example of using the Request object.
32. Merb::Router.prepare do |r|
r.match(%r{/login/(.*)},
:protocol => “http://”).
to(:controller => “account”,
:action => “login”,
:id => “[1]”
end
33. accepts header,
meet provides
Your client tells Merb what it can accept. You tell Merb what a controller can provide.
34. class Post < Application
provides :json, :yaml
def show
@post = Post[params[:id]]
display @post
end
end
Here, you tell Merb to provide JSON and YAML. Then, you tell it to serialize the @post object into the appropriate form depending on what the client requested.
35. display flow
display @foo
get content_type
XML
Look for foo.xml.* yes render
Here’s how Merb determines what to render given a specific parameter to display. This workflow assumes the client asked for XML.
36. display flow
display @foo
get content_type
XML
Look for foo.xml.*
@foo.to_xml
no?
37. display flow
display @foo
get content_type
XML
Look for foo.xml.*
@foo.to_xml
38. default mimes
In order to make this work simply and seamlessly, we need Merb to know about a set of default mime types. Here’s the list.
39. Merb.add_mime_type(:all, nil, %w[*/*])
Merb.add_mime_type(:yaml, :to_yaml,
%w[application/x-yaml text/yaml])
Merb.add_mime_type(:text, :to_text,
%w[text/plain])
Merb.add_mime_type(:html, :to_html,
%w[text/html application/xhtml+xml
application/html])
The first parameter is the internal name for the mime used by Merb. It is also the extension to use for a URL if you want that mime to be used (http://foo.com/index.json will
use the :json type).
40. Merb.add_mime_type(:xml, :to_xml,
%w[application/xml text/xml
application/x-xml],
:Encoding => quot;UTF-8quot;)
Merb.add_mime_type(:js, :to_json,
%w[text/javascript
application/javascript
application/x-javascript])
Merb.add_mime_type(:json, :to_json,
%w[application/json text/x-json])
The second parameter is an Array of mime types from the Accept header that will match the mime. The first item in the list is the mime-type that will be returned to the client.
41. class Post < Application
provides :json, :yaml, :xml
def show
@post = Post[params[:id]]
display @post
end
end
Here’s the example again.
42. class Post < Application
provides :json, :yaml, :xml
def show(id)
@post = Post[id]
merb-action-args
display @post
end
end
We can use the merb-action-args to simplify our controllers.
43. a benefit of modularity
Let’s take a look at an simple benefit of Merb’s modularity.
44. class Mail < Merb::Mailer
before :shared_behavior
def index
render_mail
end
end
Mailers look the same as controllers.
45. class Mail < Merb::Mailer
before :shared_behavior
def index
render_mail :foo
end
end
You can pass them a symbol, which will use a different template.
46. class Mail < Merb::Mailer
before :shared_behavior
def index
render_mail :html => :foo,
:text => :bar
end
end
You can also use special templates for html and text, without any fancy methods.
47. class Mail < Merb::Mailer
before :shared_behavior
def index
attach File.open(“/foo/bar”)
render_mail :html => :foo,
:text => :bar
end
end
You can attach a file, which will automatically figure out the mime and use it.
48. class Mail < Merb::Mailer
before :shared_behavior
layout :mailers
def index
attach File.open(“/foo/bar”)
render_mail :html => :foo,
:text => :bar
end
end
You can use layouts, just like regular controllers.
49. send_mail Mail, :index,
{:to => “wycats@gmail.com”}
You call mailers in your controllers like this.
50. class Mail < Merb::Mailer
before :shared_behavior
layout :mailers
def index
attach File.open(“/foo/bar”)
render_mail :html => :foo,
:text => :bar
end
end
51. class Mail < Merb::Mailer
before :shared_behavior
layout :mailers
def index
mail.to = [“wycats@gmail.com”]
attach File.open(“/foo/bar”)
render_mail :html => :foo,
:text => :bar
end
end
You also have access to the raw mailer object (a MailFactory object).
57. describe MyController do
it “returns some json” do
@mock = fake_request(
:accept => “application/json”)
c = MyController.new(@mock)
c._dispatch(:index).should ==
{:foo => “bar”}.to_json
end
end
This is the raw way of doing testing. You probably wouldn’t do this, and it uses a private method (_dispatch).
58. describe MyController do
it “returns some json” do
dispatch_to(
MyController,
:index,
{:foo => :bar},
{:accept => “application/json”}
)
@controller.body.should ==
{:foo => :bar}.to_json
end
end
You can use Merb’s helpers as well.
59. describe MyController do
it “returns some json” do
dispatch_to(
MyController,
:index, controller
{:foo => :bar},
{:accept => “application/json”}
)
@controller.body.should ==
{:foo => :bar}.to_json
end
end
60. describe MyController do
it “returns some json” do
dispatch_to(
MyController,
:index,
{:foo => action
:bar},
{:accept => “application/json”}
)
@controller.body.should ==
{:foo => :bar}.to_json
end
end
61. describe MyController do
it “returns some json” do
dispatch_to(
MyController,
:index,
{:foo => :bar},
{:accept params “application/json”}
=>
)
@controller.body.should ==
{:foo => :bar}.to_json
end
end
62. describe MyController do
it “returns some json” do
dispatch_to(
MyController,
:index,
{:foo => :bar},
{:accept => “application/json”}
) request environment
@controller.body.should ==
{:foo => :bar}.to_json
end
end
69. use_orm :datamapper
use_test :rspec
dependency “merb_more” # or what you need
... other dependencies ...
Merb::BootLoader.before_app_loads do
DataMapper.setup(:salesforce, ...)
end
Use before_app_loads for things that need to know about the structure of the framework (where your controllers/models/etc. are). The dependency method automatically
handles the appropriate time to load the plugin.
70. merb != Rails
We’ve had a lot of back-and-forth about things that are not the same between Merb and Rails. Here’s a list of things that you should be aware of when coming from a Rails
background.
72. provides vs. respond_to
As shown above, we use a completely different API than Rails does for deciding what mime-type to return.
73. :except vs. :exclude
We use before :foo, :except => :bar instead of before_filter :foo, :exclude => :bar
74. logger vs. merb.logger
We do not have a Kernel method for our logger. You get access to our logger by doing Merb.logger.info!
75. fooscontroller vs. foos
Since we don’t use const_missing to figure out where to load things, there are no naming requirements for any class. All classes are loaded at boot-time.
76. url_for vs. url
We do url(:resource, @resource) instead of url_for_resource(@resource).
77. css_include_tag :foo, :bundle => :base
vs.
stylesheet_link_tag :foo, :bundle
=> :base
We use css_include_tag instead of stylesheet_link_tag. We also use js_include_tag for consistency.
79. no rjs
We don’t use RJS. We recommend using Unobtrusive JavaScript techniques and a merb_jquery plugin along these lines is available.
80. mandatory render
Merb actions return strings. This provides massive additional flexibility when using actions from other methods. Our render method just returns a string, and is thus required at
the end of actions.
86. it’s considered a bug if
There’s a bunch of notions that are typically not considered bugs, but which we do.
87. it’s considered a bug
★ symbol#to_proc ★ non-documented
★ alias_method_chain ★ params
★ merb gets slower ★ options keys
★ private api use ★ returns
★ public api change* ★ yields
*without deprecation period and notice in public API changelog
alias_method_chain is ok in your app code, but it’s a bug if we use it in Merb.