Rails #4: A real blog
This is the fourth article in my introduction to Rails. In the previous articles, we created a web application that let us edit articles, added support for comments to our articles, and added some nice AJAX effects. But a real blog needs feeds, a decent front page and some article formatting. In this article we will add all these. The article contains no groundbreaking features, but mostly dots a few i’s and crosses a few t’s both when it comes to the blog we’re building, and details in Rails.
My pretty, pretty articles
Our articles are currently not formatted. We want to support a simple markup language. As it turns out textile is perfect for the job. It transforms stuff “like _this_” to stuff “like this”. Let’s add textile support:
Ruby support for textile is available in the RedCloth gem. Install it by typing
gem install RedCloth
on the command line.Import RedCloth in your Rails application. In
config/environment.rb
add the linerequire 'RedCloth'
(after the other line that starts with ‘require’).Let’s add a helper for textile for all views. In
app/helper/application_helper.rb
, add the following:def textile(text) RedCloth.new(html\_escape(text)).to\_html end def intro(text) paras = text.split /\\n\\n/ textile paras[0] end
Update the views: In
app/view/articles/show.html.erb
change<%=h article.content %>
(“h” is short forhtml_escape
) and replace with<=textile article.content %>
. Similarly changeapp/view/comments/_comment.hml.erb
and replace<%=h comment.body %>
with&l;%= textile comment.body %>
. (We will deal with the article index later)Try adding some textile markup to your articles and test out the effect. You can put your test data in
test/fixtures/articles.yml
and load it withrake db:fixtures:load
if you’re too lazy to type it in the forms.
A good first impression
If you’ve followed the article series, when you go to http://localhost:3000, you are presented with the default Rails-constructed start page. Lets make a more inviting web page and start at the articles page instead:
In
config/routes.rb
, add a root route:map.root :controller => 'articles'
. The comments in the file describes this further.Files in the
public/
folder will take precedence over the routes, so you also need to deletepublic/index.html
.If you go the http://localhost:3000, you will now be presented with the articles list. It looks horrible. Let’s clean up
app/views/articles/index.html.erb
. Here is my final version (theintro
function was defined earlier in this blog post):Welcome to my blog ================== < % for article in @articles %> < %= link\_to h(article.title), article %> (by < %= article.author %>) -------------------------------------------------------------------------------------------- < %= intro article.content %> < % end %> < %= link\_to 'New article', new\_article\_path %>
We also need rss feeds for all our pages. Luckily, Rails comes with lots of built in support for this. First: We want to add auto discovery of the link in browsers that support this.
app/view/layouts/articles.html.erb
is applied around each page from the articles-controller, with the contents being displayed where the layout says<%= yield %>
. Add the following to the inapp/view/layouts/articles.html.erb
:<%= [auto_discovery_link_tag](http://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#M001021) :rss, { :controller => 'articles', :format => :rss } %>
. If you refresh the page, you may notice a small RSS icon in the address bar of your browser! If you want to make the blog look nicer, the layout is also a good place to start.Clicking the RSS icon lets you go to the RSS action. However, this just takes you to a blank page. You have to go to
app/controllers/articles_controller.rb
add a line to theindex
action’srespond_to
block withformat.rss
. Refreshing will now give you a proper error message: “Missing template articles/index.rss.erb”Create a file named
app/views/articles/index.rss.builder
. Files with “.builder” extensions are regular Ruby files, but in these files, a special object named xml is available (much like the “rjs” files we worked with earlier). We can use this to create RSS feeds. (w3schools have a useful RSS syntax reference). Here is mine:xml.rss :version=>"2.0" do xml.channel do xml.title "My cool blog" xml.link articles\_url xml.description "I have made a blog - take a look" for article in @articles xml.item do xml.title article.title xml.link article\_url(article) xml.description intro(article.content) end end end end
Refreshing the feed will now give you a complete RSS feed and the option to subscribe to it if your browser supports this.
Did they respond to my comment?
Many blogs lack a RSS for comments. This means that you’ll have a hard time keeping track of whether someone responded to our comment. We want to do better, and learn some more about routing at the same time:
Add another auto discovery link to
app/views/layouts/articles.html.erb
after the first one:<%= auto_discovery_link_tag :rss, { :controller => 'comments', :article_id => @article, :format => :rss }, { :title => 'Comments' } %>
.Go to an article that has some comments, and click the RSS icon by the address bar. In Firefox, this will how a menu of the two feeds, both labeled “Subscribe to ‘Comments’”. Click the link to view the comments feed.
Creating the feed will be just like the article feed: You have to add
format.rss
to therespond_to block
for theindex
action ofapp/controllers/comments_controller.rb
and addapp/view/comments/index.rss.builder
. Go ahead and do that now.In
app/view/comments/index.rss.builder
, I made the item link point toxml.link comment_url(comment)
. You will have to define comment_url inapp/helpers/comments_helper.rb
(pay attention to the:only_path
parameter):def comment\_url(comment) url\_for :controller => 'comments', :action => 'show', :article\_id => comment.article, :id => comment, :only\_path => false end
We have let
comment_url
andcomment_path
in bothapp/controllers/comments_controller.rb
andapp/helpers/comments_helper.rb
point to the comments view. This is a pretty lame page, and we don’t want to make separate pages for the comments anyway. Instead: Hyperlink to an anchor to the comments::controller => 'articles', :action => 'show', :id => comment.article, :anchor => dom_id(comment)
in both the helper and the controller. Now all our links are fixed with one fell swoop! Deleteapp/views/comments/show.html.erb
while you’re at it. This will break your tests intest/functional/comments_controller_test.rb
so make sure you fix them. Bothtest_should_create_comment
andtest_should_update_comment
should redirect toassert_redirected_to article_path(:id => assigns(:article), :anchor => @controller.dom_id(assigns(:comment)))
.test_should_show
is no longer valid and can be deleted.
We now have a nice feed for the articles and comments, with hyperlinks that take us to the right place. However, we have opened a problem: If you try to subscribe to comments from the front page, you will get angry error messages about the fact that you can’t find the related article to search for comments for. We could avoid displaying this link when there’s no active article by doing something like <%= auto_discovery_link_tag .... if @article %>
, but instead, we’ll use the chance to learn how to create a route for all comments.
- The problem starts with
app/controllers/comments_controller.rb
in the methodfind_article
. Change the contents of the method to@article = Article.find(params[:article_id]) if params[:article_id]
. This moves the problem to theindex
action. Change the first line to useComment.find
if there’s no article:@comments = @article ? @article.comments.find(:all) : Comment.find(:all)
(For extremists, this could be written:(@article ? @article.comments : Comment).find(:all)
). - You will also have to update
app/views/comments/index.rss.builder
to deal with @article being nil. For the channel link, I did:xml.link @article ? article_url(@article) : all_comments_url
. But what isall_comments_url
? - Enter
config/routes.rb
. Add the following route:map.resources :comments, :name_prefix => 'all_'
. Your view now works. And what is more, you can even go directly to http://localhost:3000/comments to view all comments. But in order for this to work, you will have to updateapp/views/comments/index.html.erb
and remove the link to create a new comment.
An issue of trust
We have created a blog with quite a few nice features: Comments, AJAX-support, RSS feeds and formatting. If you have been following along, I hope you have been impressed with the speed with which you can create an application in Ruby-on-Rails. However, if you’ve been working with development for any time, you have probably seen quite a few examples of technology which starts of promising, but then falls apart when you try to create something that could actually be usable in the real world. So the next time, I plan to do the responsible thing and add security and authentication, so that not just anybody can create and edit articles! In the process, we will get to use sessions and more advanced active record relationships.