Questions or comments, contact: socialface@gmail.com
Published: 4/20/08 Updated: 4/26/08
Screenshot: http://www.socialface.com/slapp/screenshot.jpg
Welcome to the Slapp tutorial. The primary goal here is to gently introduce the major components of the Merb micro-framework by building a simple chat wall style application.
The secondary goal is to be the best open and continuously updated Merb tutorial in existence. With time, our hope is that this tutorial will grow to represent ALL aspects of the Merb framework and development style.
This tutorial is Copyright 2008 Social Corp. and is licensed under a Creative Commons Attribution-Noncommercial 3.0 United States License, available at:
The associated source code is licensed under an MIT-style open source license and is available (via Gitorious):
Or directly browse the source:
And a downloadable version of the project is also available:
This tutorial was originally written by Slurry from #merb. Slurry relied heavily on the official Merb documentation[^a] and the #merb IRC channel while writing it.
Several resources were consulted in the process of learning RSpec for Merb, including John Hornbeck's Blerb[^b], Tim Connor's "Isolated controller and view testing in merb" article[^c] - although they were helpful, true Merb/RSpec mastery was graciously provided by The benburkert from #merb.
By design, Merb is an ORM-agnostic framework. That, of course, just means there are several different model layers to choose from. As of Merb v0.9.2 they are: DataMapper, Sequel, and ActiveRecord (from Rails, yes).
Although DataMapper and Sequel are both great choices, the majority of people looking at Merb right now are probably coming from a Rails background; accordingly, we're going to stick with ActiveRecord for this tutorial.
Inside a fast moving community like Merb, many times a problem may be solved by simply updating or reinstalling Merb-related gems. Let's take a moment and do exactly that:
# gem sources -a http://merbivore.com
# gem install merb activerecord merb_activerecord merb_helpers rspec merb_rspec
$ merb-gen app slapp
$ cd slapp
With the first set of commands, Merb, ActiveRecord, and RSpec should be installed and/or up-to-date now, while the second set should have created a blank Merb application to start out in.
From here, we need to inform Merb of our decision to use ActiveRecord, which testing framework we want to use, and that it should load the extra merb_helpers gem (form related stuff).
Edit: slapp/config/init.rb and uncomment lines: 27 and 42, while
adding: dependency "merb_helpers" somewhere before the
Merb::BootLoader.after_app_loads do lines:
use_orm :activerecord
...
use_test :rspec
...
dependency "merb_helpers"
...
Merb::BootLoader.after_app_loads do
### Add dependencies here that must load after the application loads:
# dependency "magic_admin" # this gem uses the app's model classes
end
Now that we've told Merb to: use_orm :activerecord, let's generate our first
model:
$ merb-gen model Post
Awesome, that didn't actually work. But hang on, you should have gotten a
brand new: slapp/config/database.yml.sample file to customize, right? [^1]
Cool, all you have to do is rename this file: database.yml and insert the
proper database connection information, which should resemble:
:development: &defaults
:adapter: mysql
:database: slapp_development
:username: slapp
:password: SL@rrYin08
:host: localhost
:socket: /tmp/mysql.sock
:encoding: utf8
:test:
<<: *defaults
:database: slapp_test
Let's re-run the merb-gen command from above, except this time we'll also add a set of default properties to the Post:
$ merb-gen model Post body:string created_at:datetime
If all went well, we should have our first model class. Using rake, let's
migrate the database to reflect this addition:
$ rake db:migrate
Slapp should have a valid working Post model now. In order to verify that this is the case, we're going to introduce RSpec[^2].
RSpec is, as you may already know, an alternative to testing with Test::Unit.
You primarily interact with RSpec through the use of rake tasks and the spec
command:
$ rake spec
Or, the more verbose:
$ spec spec/* --format specdoc -c
(IMPORTANT: the rake tasks used here are from Edge Merb and should be
published to the standard gems soon. Until then, use these equivalent tasks:
rake specs and rake spec TASK=controllers/pages)
Both commands run any testing code inside: slapp/spec/* and for brevity's sake,
we're going to use rake here.
Let's run the tests again:
$ rake spec
As you should see, we already have a Merb-created default test, but it isn't quite passing yet:
Post
- should have specs (PENDING: Not Yet Implemented)
...
Open: slapp/spec/models/post_spec.rb to see where this test came from:
describe Post do
it "should have specs"
end
Change the spec to something useful, perhaps:
describe Post do
it "should be valid when new" do
post = Post.new
post.should be_valid
end
end
This test may not seem like much, but it actually verififes that Merb, RSpec, ActiveRecord and our database are all installed and working correctly.
$ rake spec
And, if we run this new spec again, sure enough, there is a problem with the
database. More specifically, we forgot to create the slapp_test database
and clone the structure of slapp_development to it:
$ rake db:create:all
$ rake db:test:clone
Re-run the spec:
$ rake spec
...
Post
- should be valid
...
1 example, 0 failures
And you should be greeted with success: the 1 example, 0 failures line means
the specs have passed. Just as importantly, we've encountered and conquered
our first real world problem with RSpec.
While we could technically start Merb right now, without a controller, it wouldn't accomplish much. In fact, it wouldn't accomplish anything; let's create a controller then:
$ merb-gen controller Posts
As you can see, Merb's controller naming scheme is to pluralize the model
name (or resource/etc), without using the Controller suffix.
Take a look in: slapp/app/controllers/posts.rb and you should see our new
Posts controller with a single default #index action. In addition, Merb
should have created a new spec: slapp/spec/controllers/posts_spec.rb
with something like this inside:
describe Posts, "index action" do
before(:each) do
dispatch_to(Posts, :index)
end
end
Let's edit: slapp/spec/controllers/posts_spec.rb changing the contents to
match:
require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
describe Posts, "#index" do
it "should respond correctly" do
dispatch_to(Posts, :index).should respond_successfully
end
end
Specifying controllers is fairly straight-forward. In the example above, we
are documenting that the Posts controller (which is returned from dispatch_to)
has an #index action and that it is successfully callable from the outside
world (ie. it returns an HTTP 20x code)[^3].
We want to run the specs again, but since we haven't added any model or view tests, let's use the more specific controller-only rake task:
$ rake spec:controller
And our successful test results:
Posts index action
- should respond correctly
...
1 example, 0 failures
Beautiful. We now have a working controller and valid model - all we're lacking is a couple of good views.
Earlier when we created our Posts controller, Merb also created a stub view
for the #index action. Located: slapp/app/views/posts/index.html.erb, you
might take a look inside, otherwise, let's ignore this view for a few moments
and return to the controller.
Inside: slapp/app/controllers/posts.rb, we perform a simple ActiveRecord #find
and store the results as an instance variable inside the #index action:
class Posts < Application
def index
@posts = Post.find(:all, :order => "created_at DESC")
render
end
end
And verify the action is still callable:
$ rake spec:controller
...
1 example, 0 failures
Just as in Rails (or any other web framework), instance variables created in the controller are available inside the corresponding view.
In this case, we can now go back to: slapp/app/views/posts/index.html.erb
and replace the placeholder text with something to display the contents of the
@posts variable.
Wanting to stay modular, we're going to use the Merb #partial[^4] facility to
accomplish this.
Replace the contents of: slapp/app/views/posts/index.html.erb with something
similar what we have here:
<h1>Welcome to Slapp</h1>
<h2>A simple chat wall</h2>
<p>Recent Posts:</p>
<div id="posts" class="container">
<%= partial("/shared/post", :with => @posts) %>
</div>
Ignoring the HTML, the call to #partial should be pretty easy to understand -
we want to render the view located at: slapp/app/views/shared/_post.html.erb
multiple times for each object in @posts.
Now create the: shared/ directory and: _post.html.erb file:
$ mkdir app/views/shared
$ touch app/views/shared/_post.html.erb
And edit: slapp/app/views/shared/_post.html.erb to look something like:
<div id="post-<%= post.id %>" class="post">
<p class="body"><%= h(post.body) %></p>
<p class="created"><%= relative_date(post.created_at) %></p>
</div>
Calling: partial("/shared/post", :with => @posts) above, will repeatedly
render _post.html.erb passing a single post object into the view.
Now that we have a model, a controller, and a view, let's fire up Merb:
$ merb
$ curl http://localhost:4000/
You should see a generic welcome page. Navigate to:
$ curl http://localhost:4000/posts/index
And you should see the contents of: slapp/app/views/posts/index.html.erb. Of
course, since we haven't created any posts yet, there isn't anything to see.
Let's fix that with an interactive Merb session (which is simply IRB started within the content of our Merb application):
$ merb -i
~ Loaded DEVELOPMENT Environment...
...
>> Post.create(:body => "Memp went down")
Using a typical ActiveRecord #create method, we've got a Post now. Reload the
Posts#index page:
$ curl http://localhost:4000/posts/index
And again, with success, we should be looking at the new post.
Now that we've implemented our views and they probably won't change much, let's spec them.
First, create the directories and the spec itself:
$ mkdir -p spec/views/posts/
$ touch spec/views/posts/index_spec.rb
Then put the following code into: slapp/spec/views/posts/index_spec.rb
require File.join(File.dirname(__FILE__), "..", "..", "spec_helper.rb")
describe "posts/index" do
before(:each) do
@controller = Posts.new(fake_request)
@posts = [Post.create(:body => "Merb", :created_at => Time.now), Post.create(:body => "Rocks!", :created_at => Time.now)]
@controller.instance_variable_set(:@posts, @posts)
@body = @controller.render(:index)
end
it "should have a containing div for the posts" do
@body.should have_selector("div#posts.container")
end
it "should have a div for each individual post" do
@posts.each do |post|
@body.should have_selector("div#posts.container div#post-#{ post.id }.post")
end
end
it "should have the contents of each post inside a div with an id and class" do
@posts.each do |post|
@body.should match_tag(:div, :id => "post-#{ post.id }", :class => "post", :content => post.body)
end
end
after(:each) do
Post.destroy_all
end
end
When describing objects with RSpec, it's often necessary to maintain certain
aspects of the test before and after running the test. Not surprisingly, RSpec
provides #before and #after blocks we can use for this purpose.
Back to the code: the first thing we do inside the #before block, is to create
an @controller instance from our Posts controller. Here we use the Merb
fake_request helper[^5] to simulate an HTTP request.
Thinking back to our view:
...
<p>Recent Posts:</p>
<div id="posts" class="container">
<%= partial("/shared/post", :with => @posts) %>
</div>
We know we're going to need an array of posts to test against, coincidently,
this is exactly what the second and third lines of the #before block do:
...
@posts = [Post.create(:body => "Merb", :created_at => Time.now), Post.create(:body => "Rocks!", :created_at => Time.now)]
@controller.instancevariableset(:@posts, @posts) ...
Here, we're simply inserting an array of @posts as an instance variable
inside the @controller. Remember, this is no different than we had done before
when calling the #index action of the Posts controller, except we were using
an ActiveRecord #find then, instead of manually creating the posts with #new:
Our Posts controller from earlier:
class Posts < Application
def index
@posts = Post.find(:all, :order => "created_at DESC")
render
end
end
Finally, the fourth and final line of the #before block renders the view and
stores the response's body (in this case, HTML) inside the @body instance
variable.
...
@body = @controller.render(:index)
...
(essentially we're using the fake_request to "view" the Posts#index action..)
Now that our controller is setup and the view is rendering, we start listing our expectations of what should be in the view.[^6]
First, we document that the HTML should have an outer containing div to hold the divs of each individual post:
...
it "should have a containing div for the posts" do
@body.should have_selector("div#posts.container")
end
...
Next, we assert that the container div should actually contain the post divs:
...
it "should have a div for each individual post" do
@posts.each do |post|
@body.should have_selector("div#posts.container div#post-#{ post.id }.post")
end
end
...
Then, we drill into each of the post divs to verify the contents accurately match the text of the respective Post:
...
it "should have the contents of each post inside a div with an id and class" do
@posts.each do |post|
@body.should match_tag(:div, :id => "post-#{ post.id }", :class => "post", :content => post.body)
end
end
...
Finally, we use the #after block to destroy all of the posts created in the #before block:
...
after(:each) do
Post.destroy_all
end
...
Although we're not really finished yet, let's verify the entire app:
$ rake spec
...
Posts#index
- should respond correctly
Post
- should be valid
posts/index.html.erb
- should have a containing div for the posts
- should have a div with an id and class for each individual post
- should have the contents of each post inside a div with an id and class
Finished in 0.226266 seconds
5 examples, 0 failures
With the ability to view posts, it's now time to implement the equally important ability to create posts.
Open: slapp/app/views/posts/index.html.erb and add this form[^7] below the
posts:
...
<p>Post Something:</p>
<% form_tag(:action => url( :controller => "posts", :action => "create") ) do %>
<%= text_field(:name => "body", :size => 40) %>
<%= submit_button("Post Message!") %>
<% end %>
A fairly self-explanatory bit, here we are building a simple form with one text input and one submit button.
You'll notice we've set the form to submit to the URL of the #create action
of the Posts controller. We need to implement this action, but before we move
on, let's quickly spec this form.
Edit: slapp/spec/views/posts/index_spec.rb and add the following above the #after block:
...
it "should have a form to create new posts with a single input and submit button" do
@body.should match_selector("form[@action=/posts/create]")
@body.should match_selector("form[@action=/posts/create] input[@name=body]")
@body.should match_selector("form[@action=/posts/create] button[@type=submit]")
end
Just as earlier, we use #match_selector to assert the presence of the form,
the body input, and the submit button. The only thing new is our use of an
HTML attribute-based selector[^8] with form[@action=/posts/create]
Run the specs:
$ rake spec:view
...
4 examples, 0 failures
Once again we could start Merb to visually inpect the new form. Because we've used RSpec, though, that step isn't really necessary and instead, we can immediately move forward.
Speaking of RSpec, this time, as we work through the #create action, we're
actually going to be writing the spec before we write the code for that
particular step.
Mirror the contents of: slapp/spec/controllers/posts_spec.rb to what we have
below:
require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
describe Posts, "#index" do
it "should respond correctly" do
dispatch_to(Posts, :index).should respond_successfully
end
end
describe Posts, "#create" do
before(:each) do
@params = { :body => "It was a good game though" }
end
it "should redirect to #index after successfully creating a Post" do
lambda {
dispatch_to(Posts, :create, @params).should redirect_to("/posts/index")
}.should change(Post, :count)
end
end
Here, in the #before block, we've begun to describe the #create action by
preparing an @params hash with a single :body text key. Next, we list our
first expectation that the create action should redirect to the #index action
after it has created a post.
(this is exactly what our application would see if a browser had submitted this same information through our "Post Something" form)
We verify a post was actually created with a lambda expression, which RSpec
will call twice: once before and once after the associated {...} block has been
executed.
Both times, RSpec will call Post.count, if the two calls return different
values, we can be sure a Post was created and that our block (the action) was
successful.
Because we haven't coded #create yet, the specs rightfully fail:
$ rake spec:controller
...
Posts#create
- should redirect to #index after successfully creating a Post (FAILED - 1)
1)
'Posts#create should redirect to #index after successfully creating a Post' FAILED
count should have changed, but is still 0
...
Switch to: slapp/app/controllers/posts.rb and once again mirror the contents:
class Posts < Application
def index
@posts = Post.find(:all, :order => "created_at DESC")
render
end
def create
Post.create!(:body => params[:body])
redirect url(:action => "index")
end
end
Now that we've defined #create, it's back to the specs and this small addition
should be enough to pass the specs:
$ rake spec:controller
...
2 examples, 0 failures
And by passing the specs, of course, we now also have a working chat wall.
Sounds like good news, and we're almost finished too. All we need to do is verify that some amount of text is required to create a new post and that an exception is raised otherwise.
Right now, anyone can click "Post Message!" and a new post is created. Because we don't want a bunch of blank posts filling up the chat wall, we should probably verify that at least a small amount of text has been submitted before creating a new post.
Because this is a tutorial, we're not really going to handle things properly
and instead just kind of cop out and use an AR validates_length_of filter. ;-)
Open: slapp/spec/models/post_spec.rb and observe our existing spec:
...
it "should be valid when new" do
post = Post.new
post.should be_valid
end
Because we're going to validate the presence of body text, this spec is no longer valid and we instead need something like:
describe Post do
it "should NOT be valid when new" do
post = Post.new
post.should_not be_valid
end
it "should require at least two body characters to be valid" do
post = Post.new
post.should_not be_valid
post.errors.on(:body).should include("is too short (minimum is 2 characters)")
end
end
As is normal with RSpec, we first run the failing specs to establish our expectations of this particular behavior:
...
Post
- should NOT be valid when new (FAILED - 1)
- should require at least two body characters to be valid (FAILED - 2)
1)
'Post should NOT be valid when new' FAILED
expected valid? to return false, got true
./spec/models/post_spec.rb:7:
2)
'Post should require at least two body characters to be valid' FAILED
expected valid? to return false, got true
./spec/models/post_spec.rb:12:
...
And then implement the proposed fix, which in this case, is to add a single
line to: slapp/app/models/post.rb:
class Post < ActiveRecord::Base
validates_length_of :body, :minimum => 2
end
Run the specs again to verify our fix worked:
$ rake spec:model
...
2 examples, 0 failures
And our model is finished. Let's add one more spec to the Posts controller -
we need to document that when a post is submitted witout body text, we don't
really handle the error and instead return the exception raised by our ORM.
Edit: slapp/spec/controllers/posts_spec.rb and this final spec:
...
it "should raise an exception when insufficient body text is submitted" do
lambda {
dispatch_to(Posts, :create).should redirect_to("/posts/index")
}.should raise_error(ActiveRecord::RecordInvalid)
end
As you can see, we're simply calling #create without the @params hash. This
creates a blank Post without body text and in turn, will cause our AR
validation to fail, raising the expected RecordInvalid exception.
And with that, our first version of the chat wall is complete. Just as earlier,
you can try it out by starting Merb and going to the Posts#index action:
$ merb
$ curl http://localhost:4000/posts/index
From here, there are many things you might want to add, such as: pagination, dynamic posting/updates, spam/troll filters, text formatting, etc.
These are all pretty essential to any good chat app and all possible with Merb.
Also, don't forget to visit the official project page for the latest updates, to see other versions, or even create your own:
...
Until next time, questions/comments: socialface@gmail.com or Slurry in #merb
[^1]: The observant may have also just found their first "Merb" bug.. the generator claimed to have made a "database.sample.yml" file, although the file is really named "database.yml.sample". :-)
[^2]: RSpec links in order of approximate handyness to the beginner:
[^3]: Merb RSpec controller matchers:
[^4]: Merb partials:
[^5]: Merb fake_request helper:
[^6]: Merb RSpec view matchers:
[^7]: Merb Form Helpers:
[^8]: Hpricot CSS Selectors: