Slapp: A simple chat wall Merb tutorial.

Questions or comments, contact: socialface@gmail.com

Published: 4/20/08 Updated: 4/26/08

Screenshot: http://www.socialface.com/slapp/screenshot.jpg

Introduction

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.

License

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:

Credits

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.

Decision Time

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.

1.) A Model Start

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].

2.) RSpec is Given / RSpec is Earned

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.

3.) Controlling the Situation

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.

4.) The View from Above

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.

5.) Fire Up Merb

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.

6.) That Spec'ial Kind of View

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

7.) Forming An Opinion

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.

8.) Finishing Up

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

Final Thoughts

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

Footnotes

[^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: