Wednesday, October 17, 2007

Creating a simple news publishing system in Rails 2.0

In this tutorial we will create a simple news publishing system. We'll also take a look at some of the new Rails 2.0 features, like namespaced routes. Note that I wont do any testing in this tutorial, due to the limited scope. However, I recommend downloading the latest RSpec screencasts over at http://smartic.us/, to keep up-to-date on the latest testing techniques. Please note that this is my first attempt at writing a tutorial of this kind, so bear with me. Hopefully my writing will improve over time.

As usual, we'll start with creating a new project (I assume that you have Rails 1.2.5 installed). You might want to freeze to the latest edge version, but note that this is not required for completing this tutorial. Type this into your shell of choice:

rails newsmachine
rake rails:freeze:edge (optional)


Now we're ready to roll. Lets set up our database and create some models:


cd newsmachine
./script/generate model newsitem header:string author:string body:text
rake db:create:all
rake db:migrate


Note that in Rails 2.0 we don't have to specify the created_at/updated_at fields; they are automatically created for us by default. Nice. The new database creation raketasks are also very handy, but note that you probably have to do a little editing on your database.yml file first. I run PostgreSQL, so I had to change the adapter setting. Now we have a database ready, so lets try to create a admin interface. We will use the new namespace routing for this purpose.

./script/generate controller 'admin/newsitems' index new create
./script/generate controller newsitems index


Note that we create two different NewsItems controllers, with two different responsibilities. This helps us partition our code nicely into two clearly defined places. We avoid the "one controller to rule them all"-syndrome, since we get one controller for public consumption and one for admin-only stuff. Now lets delete the public/index.html file, and boot up our server:

rm public/index.html
./script/server



Lets add some routes into our routes.rb file:

map.root :controller => "newsitems"

map.namespace :admin do |admin|
admin.resources :newsitems
end


We are now ready to start writing some actual code. The first thing that we should be able to do, is creating new news items. Head to app/controllers/admin/newsitems_controller and enter the following:


def index
@newsitems = Newsitem.find(:all)
end

def new
@newsitem = Newsitem.new
end

def create
@newsitem = Newsitem.new(params[:newsitem])
@newsitem.save!
redirect_to :action => :index
end


This is pretty basic, but good enough for our example. Now over to index.html.erb, still within the admin scope:


<h1>Administration</h1>

<%= link_to "Create new news item", new_admin_newsitem_url %>

<hr />

<ol>
<% @newsitems.each do |n| %>
<li><%= n.header %></li>
<% end %>
</ol>


Try this out by putting your browser over to http://localhost:3000/admin/newsitems/. If it works: yay! If not, maybe you missed something. Or maybe my explanations just sucks. Let me know, leave a comment. Now lets create some news items. new.html.erb:


<h1>Create a new news item</h1>

<% form_for ([:admin, @newsitem]) do |f| %>
<%= label(:newsitem, :header, "Header:") %>
<%= f.text_field :header %>


<%= label(:newsitem, :author, "Author:") %>
<%= f.text_field :author %>


<%= label(:newsitem, :body, "Body:") %>
<%= f.text_area :body %>


<%= submit_tag "Create!" %>
<% end %>


The new form_for functionality is really nice. When we give it a ActiveRecord-object, it automatically identifies the correct paths etc. As you would expect, it also supports namespacing when we pass it an array. Another sleek 2.0 feature is builtin http-auth. Yep, plain old simple basic HTTP-authentication. Its a perfect fit for password-protecting small sites like this. First, add a before-filter to the admin controller (app/controllers/admin/newsitems_controller):


before_filter :authenticate


Then add the authenticate-method (put the code in as the last method in your controller):

private
def authenticate
authenticate_or_request_with_http_basic { |u, p| u == "admin" && p == "1234" }
end


There you go, a fully functional login-system written in one single line of code. Not bad. Go ahead and test it!

Ok, now we have a semi-functional admin interface without updating and deleting (but hey, at least we have auth!). Not bad! Lets implement a public view. Head over to index.erb.html for the regular newsitems controller (app/controllers/newsitems_controller):


<h1>Newsmachine gives you the news!</h1>
<%= render :partial => "newsitem", :collection => @newsitems %>


We need a partial, so create a new file called _newsitem.html.erb and enter this:


<div style=\"background-color: <%= cycle("#aaa", "#eee")%>">
<h2><%= newsitem.header %></h2>
<%= simple_format(newsitem.body) %>

<i>Written by <%= newsitem.author %> on <%= newsitem.created_at.to_s(:long) %></i>
</div>


Check it out, we now got a working site. There is a lot of stuff we could add from here, like Atom-feeds, image support, etc. I might write a part two of this, so any suggestions are greatly appreciated.

4 comments:

oren said...

I get this error:

Status: 500 Internal Server Error Content-Type: text/html


and the log shows me this:
A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb

Quon said...

must do follow to run well:
app\controllers\newsitems_controller.rb

def index
@newsitems = Newsitem.find(:all)
end

Anonymous said...

Thanks for this quick guide.

Carlos said...

I get this error message:

Routing Error

No route matches "/admin/newsitems/" with {:method=>:get}

I'm a complete noob so please bear with me :)