Create a simple code snippet web app with Rails
In this article you will see how to create a simple source code snippet rails application. I will do that taking a TDD approach, just to introduce the required mindset, for the benefit of those rails programmers that would like to get their own feet wet with testing, and are looking for an initial step by step practical example.
I assume that you are familiar with ruby and basic rails concepts. You should have a working environment with a recent version of Ruby (>= 1.8.7), RubyGems (>=1.3.0) and, of course, Rails (>=2.1).
Initial Sketching
We create a new rails application called snippety:
$ rails snippety; cd snippety
and install nifty_generators by Ryan Bates as a plugin by running:
$ script/plugin install git://github.com/ryanb/nifty-generators.git
so that we can quickly generate a simple layout with the command:
$ script/generate nifty_layout
A code snippet will have the following attributes:
- name
- language
- body
Let’s scaffold a basic snippet model:
$ script/generate nifty_scaffold snippet name:string language:string body:text
Migrate the database running rake db:migrate, start your server with script/server and point your browser to localhost:3000/snippets to see a basic but functional snippet view.

Soon after playing with the web interface we realize that our fresh snippety is lacking a few things:
- language is in a free text field instead of a selectable list of available languages
- snippet name, language and body are not required neither on create nor on update
- snippet body is not hightlighted according to the snippet language
- two different snippets can have the same name
- the list of snippet show a tabular list instead of a vertical list of snippets
These represent snippety’s business value, we should implement that functionalities, but where to start? You may argue that we should tackle directly syntax highlitghting, the main purpose of the application. But I propose to start by constraining a snippet to have a unique name, a language and a body; after that we will try to get syntax highlighting.
Suppose that you do not agree with me at all for my decision of starting with snippet model validation. I advice that you should not be afraid when the cost of doing what is implied by my decision and the cost of doing yours is equivalent. And this is the case since we can’t ship for a potential stakeholder a unit of work missing either syntax highlithing or with a potentially inconsistent source code snippet collection. We need both of them and the sequence is not critical —they could be carried on in parallel.
Before start, I rearrange our previous list according to their priority and express each of them with an expectation instead of a negation:
- each snippet should have a mandatory unique name, language and body
- each snippet body should be rendered hightlighted according to its language
- index action should list snippets in a blog-style way
- snippet language should be chosen from a selectable list of languages instead of free text
Now that we know where to head for, we can start our first iteration.
Task 1: Snippet Validation
Snippet business logic requires a mandatory unique name, a language and a body that we will implement with TDD.
Test Driven Development basically means that for any new requirement we add a new test asserting what should happen for a case; then we will implement code so that the new test —and all of the remaining others, are satisfied.
Let’s start by editing test/unit/snippet_test.rb and implementing a test method named test_should_have_a_name that fails when name is not present, like that:
1 require 'test_helper' 2 3 class SnippetTest < ActiveSupport::TestCase 4 def test_should_have_a_name 5 snippet = Snippet.new 6 assert_nil snippet.name 7 snippet.valid? 8 assert_not_nil snippet.errors.on(:name) 9 end 10 end
Take a break to understand what this test is saying. On line 5 we are instantiating a new snippet. On the next line we assert that our snippet has no default name, that is snippet.name should evaluate to nil.
Then we run the active record validation on line 7 by sending the valid? message on the snippet object.
We have fulfilled our preconditions, so on line 8 we assert that we should have an error for the snippet object on the name symbol.
That is sufficient to express that a snippet validation should fail when it has no name. Run rake test:units on your console to see the following failure:
1) Failure:
test_should_have_a_name(SnippetTest)
[./test/unit/snippet_test.rb:8:in `test_should_have_a_name'
<nil> expected to not be nil.
1 tests, 2 assertions, 1 failures, 0 errors
The next step is to make the test pass by implementing the simplest thing that should possibly work . Let’s modify app/models/snippet.rb so that it looks like that:
class Snippet < ActiveRecord::Base validates_presence_of :name end
and run rake test:units again.
Started . Finished in 0.076376 seconds. 1 tests, 2 assertions, 0 failures, 0 errors
The test passes: we are done. Now it’s your turn: using the above code as a guide try to write a test that fails when the language attribute is not set on a snippet object. Then implement the simplest thing that makes your test pass. Do the same for the body attribute and finally confront your snippet unit test suite with mine.
To accomplish task 1 we still need a unique name attribute for our snippets. Consider the following test method:
25 def test_should_have_a_unique_name 26 snippet_one = Snippet.create(:name => 'Hello World', 27 :language => "ruby", 28 :body => "puts \"Hello World\"") 29 30 assert_nil snippet_one.errors.on(:name) 31 32 snippet_two = Snippet.create(:name => snippet_one.name, 33 :language => "ruby", 34 :body => "def hello; #{snippet_one.body}; end") 35 36 assert_not_nil snippet_two.errors.on(:name) 37 end
We instantiate two snippets object, we assert that the first one is created and saved without errors on name in line 30, while the second one on line 32 is expected to have an error on name, having the same of the first. Run the usual rake test:units to see:
Started
...F
Finished in 0.099406 seconds.
1) Failure:
test_should_have_a_unique_name(SnippetTest)
[./test/unit/snippet_test.rb:33:in `test_should_have_a_unique_name'
<nil> expected to not be nil.
4 tests, 8 assertions, 1 failures, 0 errors
As you may have guessed we need to change app/models/snippet.rb like that:
class Snippet < ActiveRecord::Base validates_presence_of :name, :language, :body validates_uniqueness_of :name end
Let’s try our tests again:
Started .... Finished in 0.095361 seconds. 4 tests, 8 assertions, 0 failures, 0 errors
All tests are passing now. We have validation for our snippets.

The first task is complete. Time to move on the next one.
Task 2: Snippet Highlighting
For this task we will use the library CodeRay by Kornelius Kalnbach. Check if it is already installed on your system with: $ gem list coderay, if it is not listed you can install by running:
$ sudo gem install coderay.
Our aim is to let snippety being able to use the coderay gem to render an highlighted version of a snippet body according to its language syntax.
So we configure that dependency in config/environment.rb by adding the line:
config.gem "coderay"
We still don’t know how to interact with that library. However that knowledge is not that far away, by running $ ri CodeRay you can see its usage; I report here what is relevant to us:
--cut--
Highlight Ruby code in a string as html
require 'coderay'
print CodeRay.scan('puts "Hello, world!"', :ruby).html
# prints something like this:
puts <span class="s">"Hello, world!"</span>
Highlight C code from a file in a html div
require 'coderay'
print CodeRay.scan(File.read('ruby.h'), :c).div
print CodeRay.scan_file('ruby.h').html.div
You can include this div in your page. The used CSS styles can be
printed with
% coderay_stylesheet
--cut--
We are basically told that:
- we can highlight a string calling scan method on the CodeRay class
- we can obtain coderay’s stylesheet with the command $ coderay_stylesheet
It seems that we can optionally call div to wrap scanned result around a div tag, we will inspect that later.
By now we start by the second thing, that seems simpler to integrate into snippety. Inside root folder of snippety, run:
$ coderay_stylesheet > public/stylesheets/coderay.css
You should obtain the stylesheet file. Include it from your application layout, as you can see on line 6 in app/views/layouts/application.html.erb file:
3 --cut-- 4 <head> 5 <title><%= h(yield(:title) || "Untitled") %></title> 6 <%= stylesheet_link_tag 'coderay', 'application' %> 7 <%= yield(:head) %> 8 </head> 9 --cut--
The stylesheet should be loaded as you should see by looking at the source of a page.

Now it’s time to explore CodeRay from the rails console:
$ script/console Loading development environment (Rails 2.2.2) >> CodeRay => CodeRay
Good, the gem has been loaded. Let’s try the same example of the user manual:
>> CodeRay.scan('puts "Hello, world!"', :ruby).html
=> "puts <span class=\"s\"><span class=\"dl\">"</span><span class=\"k\">Hello, world!</span><span class=\"dl\">"</span></span>"
A bunch of span tags with their own css class, but no div mentioning a CodeRay css class. Let’s try again callint the div method on it:
>> CodeRay.scan('puts "Hello, world!"', :ruby).html.div
=> "<div class=\"CodeRay\">\n <div class=\"code\"><pre>puts <span class=\"s\"><span class=\"dl\">"</span><span class=\"k\">Hello, world!</span><span class=\"dl\">"</span></span></pre></div>\n</div>\n"
It looks much better now. It has a div with a CodeRay class, code is inside a pre tag so that multiline code will be shown on separate lines.
We now have enough ingredients for the following test:
def test_should_render_highlighted_html plain_body = %Q(puts "Hello, world!") hightlighted_body = %Q(<div class=\"CodeRay\">\n <div class=\"code\"><pre>puts <span class=\"s\"><span class=\"dl\">"</span><span class=\"k\">Hello, world!</span><span class=\"dl\">"</span></span></pre></div>\n</div>\n) snippet = Snippet.new(:name => "Hello", :language => "ruby", :body => plain_body) assert_equal hightlighted_body, snippet.highlight end
Where it is instantiated a ruby snippet having the content of puts “Hello, world!” as body and with the requirement that it should be rendered by the same markup that we’ve lastly seen in the console. We run our unit test suite as usual and we get:
Started ....E Finished in 0.098842 seconds. 1) Error: test_should_render_highlighted_html(SnippetTest): NoMethodError: undefined method `highlight' for #<Snippet:0x211b460> ./test/unit/snippet_test.rb:44:in `test_should_render_highlighted_html' 5 tests, 8 assertions, 0 failures, 1 errors
It complains since we do not still have any highlight method. So we add it to app/models/snippet.rb:
def highlight end
rerun the test to find that now we’ve got a different problem:
Started ....F Finished in 0.102812 seconds. 1) Failure: test_should_render_highlighted_html(SnippetTest) [./test/unit/snippet_test.rb:44:in `test_should_render_highlighted_html' <"<div class=\"CodeRay\">\n <div class=\"code\"><pre>puts <span class=\"s\"><span class=\"dl\">"</span><span class=\"k\">Hello, world!</span><span class=\"dl\">"</span></span></pre></div>\n</div>\n"> expected but was <nil> 5 tests, 9 assertions, 1 failures, 0 errors
It fails since highlight method actually returns nil. We are ready to implement source highlighting by writing the implementation that we have already seen under the console and hopefully making the tests pass:
def highlight CodeRay.scan(self.body, self.language).html.div end
We try our tests again:
Started ..... Finished in 0.111017 seconds. 5 tests, 9 assertions, 0 failures, 0 errors
And they do pass. We can highlight source code snippets by now and we have a test that confirms that. But we can’t show highlight source code for anyone until we modify the snippets views.
Your first instinct could be to look for snippets views, manually customize them and finish the work.
That would be great, however by this way you will end without any test for your controller and views, and this is not good. We instead maintain discipline and proceed with the next task.
Task 3: Action Views Customization
Let’s explore functional tests with $ rake test:functionals
Started ......... Finished in 0.259097 seconds. 9 tests, 10 assertions, 0 failures, 0 errors
You may wonder how it is possible that you already have a suite of 9 different functional tests yet passing without even having noticed that.
You should thank Ryan for this, he is so good that nifty_generators come with functional tests not only for Test::Unit but also for Shoulda and RSpec.
That simply means that our work for functional tests will be less than expected. So open test/functionals/snippets_controller_test.rb with your editor and have a look at the first method:
1 require 'test_helper' 2 3 class SnippetsControllerTest < ActionController::TestCase 4 def test_index 5 get :index 6 assert_template 'index' 7 end 8 --cut-- 9
As the name of the method suggests it is testing the index action of the snippets controller. On line 5, there is call to an HTTP request, in particular the get method; the symbol index, that actually stands for the index action of our snippets view, is passed as argument to the get request. That request is expected to produce a response rendering the index template view for the snippet controller. This is fine and it works, we would like to add the expectation that a list of snippets is rendered on the template. To do that we modify the method in that way:
4 def test_index 5 get :index 6 assert_template 'index' 7 snippets = assigns(:snippets) 8 assert_select 'div#snippets' do 9 assert_select 'div.CodeRay', :count => snippets.size 10 end 11 end
On line 7 we are assigning to snippets the fixtures set contained in snippets.yml that you can see under the test/fixtures directory, and we expect that our template contains a snippets id div tag, and inside it, a number of div with CodeRay class matching the number of the snippets.
Running our functionals test we see:
Started ....F.... Finished in 0.227583 seconds. 1) Failure: test_index(SnippetsControllerTest) Expected at least 1 element matching "div#snippets", found 0. <false> is not true. 9 tests, 11 assertions, 1 failures, 0 errors
Our test is failing, indeed we have no div#snippets for our view; we can implement that and produce the div.CodeRay listing with the following index.html.erb:
<% title "Snippety" %> <h2><%= link_to "Create a new code snippet", new_snippet_path %></h2> <hr/> <h2>View available code snippets</h2> <div id="snippets"> <% for snippet in @snippets %> <h3> <%=h snippet.name %> - <%=h snippet.language %> </h3> <small> <%= link_to "Show", snippet %> | <%= link_to "Edit", edit_snippet_path(snippet) %> | <%= link_to "Destroy", snippet, :confirm => 'Are you sure?', :method => :delete %> </small> <%= snippet.highlight %> <hr/> <% end %> </div>
Check our functionals tests:
Started ......... Finished in 0.22947 seconds. 9 tests, 13 assertions, 0 failures, 0 errors
And they pass. Infact, you can now see div#snippets and div.CodeRay by looking at the source code of the index page:

By the way clearing our 3rd requirement. Now, let’s try to modify our show action to properly display a snippet. We add line 16 in snippets_controller_test.rb:
13 def test_show 14 get :show, :id => Snippet.first 15 assert_template 'show' 16 assert_select 'div.CodeRay', :count => 1 17 end
to expect a div.CodeRay element on our page. The test fails since our generated show view action does not know anything about syntax highlithing, as you can see:
Started ......F.. Finished in 0.306102 seconds. 1) Failure: test_show(SnippetsControllerTest) Expected at most 1 element matching "div.CodeRay", found 0. <false> is not true. 9 tests, 14 assertions, 1 failures, 0 errors
so we produce the following template for show.html.erb:
<% title "#{@snippet.name} - #{@snippet.language}" %> <small> <%= link_to "Edit", edit_snippet_path(@snippet) %> | <%= link_to "Destroy", @snippet, :confirm => 'Are you sure?', :method => :delete %> | <%= link_to "View All", snippets_path %> </small> <%= @snippet.highlight %>
and tests are now happily passing:
Started ......... Finished in 0.271848 seconds. 9 tests, 15 assertions, 0 failures, 0 errors
We can take a break now and have a look at our application front end. After creation of a couple of code snippets, Snippety now looks like that:

While editing or creating a code snippet we have no selectable list for available languages. It’s time to address the issue.
We start by adding the requirement that a language should be presented inside a select box by placing that assertion on line 22 of snippets_controller_test.rb:
19 def test_new 20 get :new 21 assert_template 'new' 22 assert_select 'select#snippet_language' 23 end
expressing that a template should contain a select element with snippet_language id, a parameter corresponding to the language attribute in the snippet model.
Having no select box on our view, rake test:functionals fails as shown:
Started .....F... Finished in 0.282672 seconds. 1) Failure: test_new(SnippetsControllerTest) Expected at least 1 element matching "select#snippet_language", found 0. <false> is not true. 9 tests, 16 assertions, 1 failures, 0 errors
We find that the relevant code to modify is placed inside _form.html.erb, a view partial:
1 <% form_for @snippet do |f| %> 2 <%= f.error_messages %> 3 <p> 4 <%= f.label :name %><br /> 5 <%= f.text_field :name %> 6 </p> 7 <p> 8 <%= f.label :language %><br /> 9 <%= f.text_field :language %> 10 </p> 11 <p> 12 <%= f.label :body %><br /> 13 <%= f.text_area :body %> 14 </p> 15 <p><%= f.submit "Submit" %></p> 16 <% end %>
that we change creating a local variable holding an array of languages on line 8 and changing the text_field into a select box on line 10, as follows:
1 <% form_for @snippet do |f| %> 2 <%= f.error_messages %> 3 <p> 4 <%= f.label :name %><br /> 5 <%= f.text_field :name %> 6 </p> 7 <p> 8 <% @languages = [ "Ruby","JavaScript","Java","C"] %> 9 <%= f.label :language %> 10 <%= f.select :language, @languages %> 11 </p> 12 <p> 13 <%= f.label :body %><br /> 14 <%= f.text_area :body %> 15 </p> 16 <p><%= f.submit "Submit" %></p> 17 <% end %>
$ rake test:functionals says that:
Started ......... Finished in 0.3083 seconds. 9 tests, 16 assertions, 0 failures, 0 errors
we have no failures. It means that we have a working select list:

even if we have caused some eyebrows raising since a good candidate model property is placed inside a local variable of the view layer.
We should refactor that, then we do it without making the tests fail. You can see how in snippet.rb:
1 class Snippet < ActiveRecord::Base 2 LANGUAGES = %w(Ruby JavaScript Java C) 3 4 --cut-- 5 end
and in _form.html.erb:
7 <p> 8 <%= f.label :language %> 9 <%= f.select :language, Snippet::LANGUAGES %> 10 </p>
And tests are still passing:
Started ......... Finished in 0.43375 seconds. 9 tests, 16 assertions, 0 failures, 0 errors
As a side effect, the form partial change provided also a working implementation for the edit action, without its own test. As a simple exercise you can modify the test_edit methods on your functional test suite for covering test on that action. If you are tired of using rake to launch your test suite, I advice autotest.
Conclusion
Initial requirements for snippety have been cleared. If it were for real, it would be now released to get feedback from the users. They maybe would like more languages, line numbering and so on.
What is important is that now you should be more familiar with TDD. As you have seen the concept is easy to grasp and you can use effectively on your project.
Test::Unit is great but implementation of test methods can be difficult to understand and generally is not really meaningful. Shoulda by Thoughtbot addresses this problem with a DSL compatible with Test::Unit but with meaningful names. You can learn by their nice tutorial.
To complete the picture, I should tell you something about RSpec, the original Behaviour Driven Development framework for Ruby, that is the next step of TDD. The best thing I can do, is to point you to a great talk by Dave Astels and David Chelimsky where it is explained what BDD is, and where does it come from. By the way if you are interested The RSpec Book has been recently released in beta version.
I hope you’ve found it useful and thank you for your time.
Info
- loading...