A down-to-earth guide that covers everything you need to know to get started, and then some.
Notes before you start:
- MERB_ROOT is the main directory of your application.
If you have questions, or part of the wiki needs updating, or anything else, feel free:
Table of Contents
|
Getting Started
Installing Ruby, RubyGems, and Other Libraries
Ubuntu
Just for reference, in the rest of this guide, I alias 'sudo aptitude install' as simply 'install'.
First thing you need is Ruby and for fun, irb, the Interactive Ruby Shell. Most gems expect the documentation tools ri and rdoc to be installed too, so let's get those now as well. Lastly, to build from source we'll need ruby1.8-dev, build-essential. The Ubuntu repositories stay up-to-date enough here, so we'll simply install from them.
install ruby irb ri rdoc ruby1.8-dev build-essential
Rubygems is another story. As of the time of writing this, 0.9.4-4 is in the repositories, whereas the most recent version is 1.2.0. Head here to get the .tgz link for the latest rubygems.
wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz
tar -xzvf rubygems-1.2.0.tgz
cd rubygems-1.2.0
sudo ruby setup.rb
You now have ruby and rubygems installed. The commands "ruby" and "irb" work fine, but since we built rubygems from source we have "gem1.8" instead of "gem". Let's fix that…
sudo ln -s /usr/bin/gem1.8 /usr/bin/gem
Just to be absolutely certain we have the latest and greatest, lets try to update rubygems.
sudo gem update --system
We now have a happy Ruby/Rubygems installation. Time to get started on the merb-specific stuff!
Edge vs. Rubyforge
We'll be installing from source using sake and git.
Note: this needs to be redone for Thor, since that's better supported and more "official" for merb. Though frankly, it seems a little more complicated to me, so care will have to be taken in making this section easy. Until I update this section though, check out this merbunity article.
todo add more
[Sake] is a tool that's like Rake, but instead of using the Rakefile in the current directory, it uses a collection of sake recipes in a central location on your computer. Which means, once you install a sake recipe, you can invoke it from any directory.
Let's install the requirements: ParseTree (v2.1.1) and Ruby2Ruby (v1.1.8). (We find this out by trying to install sake and failing.)
sudo gem install ParseTree -v 2.1.1
sudo gem install ruby2ruby -v 1.1.8
sudo gem install sake
Finally, we're ready to install the sake recipe for merb stuff, from wycat's merb-more git repository:
sake -i http://github.com/wycats/merb-more/tree/master%2Ftools%2Fmerb-dev.rake?raw=true
Before we get merb, we still need to snag a few dependencies for merb itself (that we haven't gotten already).1
install git-core libxml2-dev libopenssl-ruby
sudo gem install rspec english erubis json_pure rack mime-types hpricot libxml-ruby memcache-client mongrel openssl
Now let's get merb!
mkdir ~/src
cd ~/src
sake merb:clone
sake merb:install:all
In the future, when you want to update merb, do the following:
cd ~/src
sake merb:gems:refresh
You're now on Merb edge.
Generating Your First Site
cd into your work directory and execute
merb-gen app
merb-gen app is the generator you use to get the bare app started. It will generate a new app in a subdirectory. Let's create an app now.
merb-gen app MyFirstMerbApp
All the files are now in place!
cd into your new app.
Running Your First Site (Locally)
Technically, your app is now ready to test! Simply run
merb
Now open up your web browser and head to [[http://localhost:4000/]]. Neat huh. Try merb —help to figure out some of the flags you can execute with, like —port X to change the port.
Configuring Your First Site
In your ./config/ directory, you'll find 3 important files: init.rb, rack.rb, and router.rb.
init.rb
init.rb is where the vast majority of "initialization" code goes. Any time you're referring to external library code that needs to be loaded in or have other settings that need to be initialized, there's a good chance it'll go here. (note that database configuration actually goes somewhere else).
rack.rb
While important, you probably won't need to modify rack.rb, though it's worth noting the line of code about static content in the middle.
router.rb
Whenever you want to access controllers and actions in a way other than http://localhost:4000/:controller/:action/:id, you'll modify router.rb. The file is pretty well-documented, but you can get lots more information here.
Building Your Site
This guide is designed so it can be used either as a step-by-step tutorial or as a reference, but with that in mind, having a small project to code might be advantageous. Let's create a Comet-style chat application, shall we? We'll of course cover links, forms, authentication, flash variables, assets, databases, mailers, and even some of the more advanced merb features.
Sample app design
So that the rest of this talk will make sense, I'm going to take a few moments to scope out exactly what this app should do.
Idea + front-end
On the main homepage, the chat box takes up the majority of the window. Within the chat box, you can see who's signed in on the right and send your own messages via the field on the bottom. Along the right side of the screen is a nav bar, with a login/register field on top, Home, Contact, and About links below that. And that about wraps up the app!
Back-end
We'll have a Message model embodying the chat messages flying around and a User class embodying the…users. We'll use MerbAuth to handle user stuff, jQuery to handle the AJAX, DM to handle the database interaction, sqlite for the database (for simplicity's sake), and memcached+memcached to handle caching.
Models, Views, and Controllers
Alright, so MVC is pretty much the same as in Rails. You have a Model representing an "Object" that's going to be passed around in your application, is related to other Models in your application, and probably has a database representation. Views are pages that are predominantly HTML with snippets of Embedded RuBy (erb). Controllers are the things that tie those two things together. In the interest of encapsulation and DRY coding (Don't Repeat Yourself), most people are in favor of keeping controllers "thin" and models "fat", in terms of where the code is. That is, whenever possible and feasible, put the code relating to a model in the model, not in the controller.
Controllers: Home
Let's create our first controller and call it something original, like "Home". When you want a new controller, model, etc, check out merb-gen.
merb-gen controller home
Try "merb-gen controller" if you're curious about other options.
This generated a few files — your sparse controller, the index view for that controller, a spec file, a helper file, and another spec file. The helper file, like Rails, defines methods that can be used in any of the views invoked from its respective controller ("Home" in this case). The specs we can ignore for now, and everything else is coming right up!
Let's look at the first file, the controller itself (app/controllers/home.rb). (Note that all models/views/controller stuff are in /app).
For me, it looks like this:
class Home < Application # ...and remember, everything returned from an action # goes to the client... def index render end end
We have our predictably-named Home class, which inherits from Application (see application.rb in the same directory). And we have our own method, index. See here for what "render" does. Basically, all your controller methods should explicitly say "here's what I'm returning". render just happens to say "render the view for controller Home action index".
Alright, so now that we have our home controller and index view, how do we see it? Well, simple. Boot up merb and head to http://localhost:4000/home/index. Actually, the index is the default view when a view isn't specified, so http://localhost:4000/home will also work. Still, it's lame to have to type "/home" at all. So let's fix this!
Routing to the rescue…open up your config/router.rb, scroll down to the bottom of the file and you'll see this:
# Change this for your home page to be available at / # r.match('/').to(:controller => 'whatever', :action => 'index')
How about…we change this to something more useful.
r.match('/').to(:controller => 'Home', :action => 'index')
Now restart your merb server and check out http://localhost:4000/. Muy excitado, no? Let's create another page, called Contact page, where users can go and shoot you an email. This will demonstrate how to use link helpers, form helpers, and merb mailer.
Edit your app/controller/home.rb and add the following:
def contact_us @name = "Max" render end
Now create and edit app/views/home/contact_us.html.erb. Put something friendly, like:
Hi, my name is <%= @name %>. Let me know what you think of my site!
Restarting merb and heading to http://localhost:4000/home/contact_us and you'll see the new page. Amazing. Now let's link those two pages to each other.
Because of Merb's lean and mean "load only what you need" mentality, it's not quite as simple as plopping a link_to in there. Since you could easily go <a href="/home/contact_us">Contact Us</a>, link_to might not be strictly even necessary. But we will because we can.
First thing, the link_to helper is not loaded by default — you need to include the merb-assets lib. As an added bonus, you also get image, CSS, and Javascript helpers. Yeah I don't know why link_to is in there, either. Anyhow, to load merb-assets, pop open config/init.rb and scroll down to the ==== Dependencies section, and add this:
dependency "merb-assets"
There! Now you have link_to. Now open up your app/views/home/index.html.erb and add this to the end:
<%= link_to("Contact Us", "/home/contact_us") %>
Hm, that doesn't seem like much less work than simply doing an <a> tag. Truth be told link_to is most helpful with the url method, but we can't use url without named routes or resources, neither of which we have yet2.
Okay, what next. Let's add a form to that page so it actually does something. First add the merb-helpers dependency to your config/init.rb after merb_assets3. merb-helpers gives you form helpers, plus a couple other things (date-time helpers and tag helpers).
Another note: there are two types of forms: forms that aren't bound to a model (the "form" method) and forms that are bound to a model (with the "form_for" method). The former follows.
Hi, my name is <%= @name %>. Let me know what you think of my site! <br /> <br /> <%= form(:action => '/home/contact_us_submit') do %> <%= text_field(:name => "name", :label => "Your name") %><br /> <%= text_field(:name => "email", :label => "Email address") %><br /> <%= text_area(:name => "comment") %><br /> <%= submit("Submit comment") %> <% end =%>
I think most of that is fairly self-explanatory. Other methods include password_field, hidden_field, file_field, select, check_box, radio_button, and radio_group.
As you may have guessed, we need to write a contact_us_submit method now. Before diving right in, let's see how we access data submitted from a form. In your app/controllers/home.rb, add the following method definition:
def contact_us_submit puts params.inspect end
Routers (or how to set up your URLs)
The router is a special object that serves a few purposes in your application:
- It maps URLs to controllers and specific actions
- It validates segments (portions between /s of the URL) passed in via the urls (with pattern-matching)
- It generates lots of nice named routes4 to use in your app
First thing you want to do is take a look at your MERB_ROOT/config/router.rb file. Within that file, there are actually plenty of examples of what routes look like, but in case you want to read it here, here are some more examples:
When the user hits the /about URL, he's directed to the "background" action of the "information" controller
match("/about").to(:controller => "information", :action => "background")
To access this, you just need to link to '/about' in your templates.
If you want to make the URL easily accessible within your application, you can add .name
match("/about").to(:controller => "information", :action => "view").name(:about_page) # Now you can do this in your app: The url for the about page is <%= url(:about_page) %>
Resources
Resources are your friends. If you want RESTful routes, you'll probably end up using a resource. What exactly am I talking about? Well, basically, if you have a model that you want to perform CRUD operations on, you'll probably want to access it as a resource. Since the wiki has an excellent writeup on what RESTful Merb routes look like, I'll just redirect you there. Also worth noting is with the :collection and :member options, you can even add extra methods to your RESTful resources, instead of being limited to the set of routes that saying "resources" in the router gives you.
Placeholders
You can also place "placeholders" in the URLs of your routes. Think of these as variables that router passes to your actions (because, well, they are).
match("/say/:something").to(:controller => 'communicate', :action => "say")
Now, in your "Communicate" controller, in your "say" action, you have access to the value of that "something" placeholder in the form of `params[:something]`.
There are a number of interesting topics around placeholders: optional placeholders, nested optional placeholders, defaults, and constraints (aka regular expressions). Let's take a look.
Optional Placeholders
These are as simple as a pair of parentheses
match("/say(/:something)").to(:controller => 'communicate', :action => "say")
There, now /say and /say/something_or_other are both valid URLs.
Nested Optional Placeholders
You can also nest optional parameters, for instance:
match("/say(/:something(/:insightful))").to(:controller => 'communicate', :action => "say")
Now /say, /say/something, and /say/something/interesting_for_once are all valid URLs and will pass in variables to the action accordingly.
Defaults
Defaults are a simple matter of calling the .default method, like so:
match("/say(/:something)").default(:something => "blah").to(:controller => 'communicate', :action => "say")
Now if you say simply /say, instead of params[:something] being nil, it will now be "blah". Exciting, no?
Other notes
To view all your routes, type
rake audit:routes
from your MERB_ROOT directory.
Helpers
Helpers are non-essential classes that make writing code faster, more flexible, or less error-prone. Sometimes though, the functionality they implement is not easily replicated without the class at all, like in the case of flash variables and Merb Mailer. Granted, they will slow down your application ever so slightly (as calling methods instead of directly outputting content always will), but you were planning on caching anyway, right?
Links
There are three ways to get links in your Merb application.
The pure HTML way
If you have an article you want to edit, you could simply put <a href="/article/<%= id %>/edit">Edit Article</a> in your template.
The partial Merb way
Another way you can do this is by typing <a href="<%= url(:edit_article, article) %>">Edit Article</a> in your template. Two caveats though: first, that article is set up as a Resource in your routes. Otherwise, the :edit_article route won't exist. The other caveat is now you're passing in an instance of the article object, not a primitive.
The full Merb way
The way most people do it would be by typing <%= link_to 'Edit Article', url(:edit_article, article) %>. In addition to the previously mentioned way's caveats, there's one more: you need to load the link_to method, in merb-assets. Open up your config/init.rb and add
dependency "merb-assets"
Forms
Source[1]
There are two formats for forms: forms that are attached to a model5 and forms that aren't attached to a model. Forms that are attached to a model are normally for CRUD6 (sans R) operations on that model (i.e. create a blog post, edit a comment, or delete a spam comment), whereas forms not attached to a model can be used for anything else, i.e. submitting feedback. Authentication is actually usually associated with a model, as you're creating (or deleting) a Session object.7
Unbound Form
<%= form :action => url(:submit_feedback) do %> <%= text_field :name => 'name', :label => "Your name: " %> <br /> <%= text_area :name => 'comment', :label => "Comments: " %> <br /> <%= check_box :name => 'reply', :label => "Want a reply?" %> <br /> <%= select :name => 'gender', :label => "Gender", :collection => [['m', 'Male'], ['f', 'Female']], :prompt => '--Select--' %> <br /> <%= submit "Submit Feedback" %> <% end =%>
In the controller that this form submits to, the values in the form are available through the params hash, i.e. params[:name]8.
Bound Form
You probably won't need to write these as often, not because you use them less, but rather because when you generate a Resource, forms for the basic Create/Update/Delete operations are created for you. Regardless, here's an example.
<%= form_for @message, url(:messages) do %> <%= text_field :to, :label => "To" %> <%= text_area :message, :label => "Message" %> <%= check_box :urgent, :label => "Urgent?" %> <%= submit "Send!" %> <% end =%>
In the controller, the values in the form are available through the params hash under params[:message] (in this case), i.e. params[:message][:to], but usually you'll just want something like Message.new(params[:message]).
Nested Resources?
I don't think there's any special consideration for "nested" or resources that are a "child" of another resource. For instance, if you wanted to make a file attachment for the message, in the "new" method you'd probably just have
@message = Message.new @file = Attachment.new @file.message = @message.
in the "new" method. Alternatively, if you're using DataMapper, you could have something like:9
@message = Message.new @attachment = @message.attachments.create
Assets (or Javascript, CSS, and Images)
Everything in your MERB_ROOT/public folder is directly available to the outside world. Within the public folder, you can create /images, /javascripts, and /stylesheets. To use the helpers, you actually have to use these directories10. Also, you need to be sure to add 'merb-assets' as a dependency in your config/init.rb if you haven't already.
The official documentation on this is actually pretty good, so I think I'll let you read that and just summarize the important details here.
Directories
Put your javascript in your MERB_ROOT/public/javascripts directory, your stylesheets in your MERB_ROOT/public/stylesheets directory, and your images in your MERB_ROOT/public/images directory.
Methods and Usage
There are 7 important methods:
- js_include_tag/css_include_tag: These take a string or array of strings (with or without extension) that name the assets you want loaded. Call in your <head> tag of your layout. Examples: <%= js_include_tag 'prototype' %>, <%= js_include_tag 'jquery.js', 'livequery' %>.
- include_required_js/include_required_css: These generate tags that have been required in other templates used in the request. They don't need arguments. Call in your <head> tag. Example: <%= include_required_js %>
- require_js/require_css: Call these methods anywhere in your layout with the same arguments that you'd pass to {js|css}_include_tag and the tags will get automatically generated and thrown to the include_required_{js|css} calls. Example: <%= require_js 'carousel' %> in a view, part, whatever.
- image_tag: This simply generates a standard issue <img> tag. The first argument is the filename (inside /public/images) and any additional options you provide are included in the tag. Examples: <%= image_tag 'logo.png' %>, <%= image_tag 'glow.jpg', :class => 'glow' %>
[5]
Digging Deeper
If you want to change the name of the public directory to something else, or if you have a load balancer that serves these files (recommended for production), check out config/rack.rb. There's a line of code in there that you can tweak to configure this behavior.
Also, as a side note…you don't really need to memorize these. For one, you only use them in layouts, and for another…after the first js and css file, you can just copy paste after that.
Flash Variables
Merb Mailer
Authentication
Using a Database
Comparison
ActiveRecord
Datamapper
Debugging
Query Logger
To enable the query logger, put something like this in your config/init.rb in the Merb::Bootloader.after_app_loads block:
DataObjects::Sqlite3.logger = DataObjects::Logger.new('log/dm.log', 0)