Quantcast
Channel: Building Web Apps
Viewing all articles
Browse latest Browse all 27

Lab 2. Building a Simple CMS

$
0
0

In this lab, we’ll create a mini content management system to enable an administrative user to edit the site’s content without having to modify the code.

To start this lab, load the lab2_start folder from the USB Flash drive. And if you have a question about how any of the code should look after completing these steps, take a look at the labs2_end folder.

1. Creating a text management system

a. Using the scaffold generator

We can create the heart of the code we’ll need using the scaffold generator, which creates a database migration, a controller, a model, views, and some test files, all with a single command.

There’s a graphical interface to the generators built in to NetBeans, but the current version of NetBeans doesn’t provide the proper interface for the Rails 2 scaffold. So we need to use the terminal. Open a terminal window, change to the root directory of your application, and enter the following command:

ruby script/generate scaffold content_block name:string body:text

(If you’re not using Windows, you can drop the “ruby” preface.)

This command tells Rails that we want to generate a model called content_blocks, with two fields: one called “name”, of type string, and one called “body”, of type text. (Strings are limited to 255 characters, whereas text fields can be arbitrarily long.)

Take a look at the files that the generator created:

  • models/content_block.rb — this is empty for now, but inherits a great deal of capability from ActiveRecord::Base
  • views/content_block/ — there are four view files here that create the scaffolded admin interface
  • controllers/content_block_controller.rb — the controller that implements the standard RESTful actions to create, edit, delete, and list the content blocks
  • db/migrate/001_create_content_blocks.rb — the migration file (in the NetBeans project view, find this at Database Migrations/migrate/

There’s more files in the test directory, but we’ll ignore those for now.

b. Creating the database table

The migration file provides the specification for creating the database tables. The t.timestamps line is added automatically, and it creates two fields: created_at and updated_at, which are automatically maintained by Rails. The id field is automatically generated, so it isn’t mentioned here.

To actually create the database table, we need to run the migration. In the NetBeans project pane, right-click on the project name and choose Migrate Database > To Current Version. The output window will show the actions that Rails takes. (If you prefer to work from the command line, the command is “rake db:migrate”.)

If you want, you can now examine the database (either with one of many MySQL graphical user interface programs, or by issuing commands with the mysql monitor) and see the table that was created and its fields.

c. Using the content blocks

Now you can browse to //localhost:3000/content_blocks and you’ll see the list of content blocks, which isn’t too interesting because there aren’t any yet. Click the New Content Block link, and you can now create the first content block. Name it “welcome” and enter some text, and click Create. You’ve now created your first database entry.

Notice that these pages are not using our default layout. That’s because the scaffold generator annoyingly creates a new layout for every model, and if there is a layout whose name matches the model, it is used instead of the default layout (which is named application.html.erb). So delete the automatically generated views/layouts/content_blocks.html.erb file, and access //localhost:3000/content_blocks again. Now the admin pages are using the layout.

Now, how do we use the data from the content block? We could write the code to find a content block by name each time we want to use one, but that would create a lot of redundancy. To avoid this, we’ll create a small view method, called a helper. There’s a helper file for each controller.

Open the file helpers/content_blocks_helper.rb, and add the following code between the two lines that are already there:

	def show_content name
	    content = ContentBlock.find_by_name(name)
	    if content
	        content.body
	    else
	      "No content for #{name}"
	    end
	end

This method first uses the find_by_name method to find the desired content block (Active Record automatically provides such a method for every attribute of every model). If the result is not nil, then the show_content_name method returns the body of the content block. If the result is nil, that means that there was no content block that matched the request name, so a message to that effect is displayed.

Now open the home page (views/static/home.rhtml.erb) and add the following line:

<%= show_content 'welcome' %>

Make sure you’ve saved all the files, and then browse to //localhost:3000. You should now see the content you entered when you created the “welcome” content block.

d. Using Textile markup

We don’t want our administrative users to have to enter HTML code, especially since they could “break” the page if they made an error in the coding. So we’ll use a simple markup language called Textile.

There’s two pieces of Ruby code we use to implement this. The first is the gem RedCloth, which you should already have installed. This is the code that translates Textile into HTML. The second is a Rails plugin called “acts_as_textiled”, which makes it trivially easy to add Textile markup to our content blocks.

To add the plugin, open a terminal window at the root of your application and enter the following command:

script/plugin install svn://errtheblog.com/svn/plugins/acts_as_textiled

How did we know this obscure address to enter? Just do a google search for the name of a plugin, or go to www.agilewebdevelopment.com/plugins, and you can find most anything pretty quickly.

If you look in the vendor/plugins folder (in NetBeans 6.0.1, you need to use the Files pane, not the Projects pane, to see this folder), you’ll see a folder into which this software has been installed.

Now, to turn our content_block body into textile markup, all you need to do is open the file models/content_block.rb, and add this line:

acts_as_textiled :body

That’s it. This plugin is smart enough to automatically display the Textile markup source when you’re displaying the body in a form field, as we are in the admin pages, but to render it into HTML anywhere else you use the body.

You’ll need to stop and restart the server before the plugin will function, though. Plugins add their own initialization code that is run when Rails starts.

Now browse to //localhost:3000/content_blocks, click the Edit link for the welcome block you created earlier, and edit the body to include some Textile markup. For example, surround a word or phrase with asterisks to make it bold, or with underscores to make it italic. Click Update to save the modified content block.

In the list of content blocks, you’ll notice that the HTML markup is now shown. That’s because the code generated by the scaffold to generate the list uses the <%=h %> wrapper for the attributes it display. The “h” causes HTML elements to be rendered as HTML entities, so the HTML text is displayed rather than interpreted by the browser. This is a security measure to avoid cross-site scripting attacks, but it has the side effect here of showing us the HTML markup that acts_as_textiled, with help from RedCloth, generated from the text in our content block.

Now refresh the home page, and you should see your welcome text rendered with bold, or italic, or whatever you’ve specified in the content block’s Textile markup.

2. Adding a File Uploader

a. Creating the asset scaffold

Now we have the basis for a content management system for text, but what about images or other uploaded files? We need a file uploader.

We can start by scaffolding another model, which we’ll call asset:

script/generate scaffold asset name:string

Go ahead and run the scaffold generator, but don’t run the migration yet, because we’re going to need to add some fields to it.

While we’re thinking of it, go ahead and delete views/layouts/assets.html.erb, the pesky layout file that the scaffold generator created but that whose only effect will be to prevent our usual layout from being used.

b. Adding attachment_fu

Uploading files involves a little complexity but, as usual, there’s a plugin that handles most of it for us: in this case, it’s “attachment_fu”. First, install the plugin with the following command in your terminal:

script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/

This is a very capable plugin, which will take care of generating thumbnails of uploaded images and lots of other things, but we’re going to use it in a pretty basic way.

You can look at the readme file in the vendor/plugins/attachment_fu folder to see what some of its requirements and capabilities are. From this, we can see that we need to provide a number of fields in our database model that is going to manage the uploaded files.

Open the migration file that the scaffold command generated, db/migrate/002_create_assets.rb, and add the following two lines after the line t.string :name:

  t.integer :parent_id, :size, :width, :height
  t.string :content_type, :filename, :thumbnail

This adds four integer fields and three string fields that are required for attachment_fu to do its work. Now run the migration to create the table, and restart the server so the plugin can run its initialization code.

c. Updating the model

Now we need to tell the asset model that we want it to use attachment_fu. Add the following two lines in the file model/asset.rb:

  has_attachment :storage => :file_system
  validates_as_attachment

The first line tells Rails to use attachment_fu for this model, and to store the uploaded assets in the file system (not in the database). The second line tells it to validate the entered data (such as the filename).

d. Updating the views

That’s all it takes for our asset model to deal with uploaded files. Now we need to add a little code to the view files to allow us to choose the filename. Open the file views/new.html.erb. Add the following code after the name field so we’ll have a place to enter the filename:

<p><b>Asset File</b><br />
       <%= f.file_field :uploaded_data %>
    </p>

The file_field helper will display a text field for the file name and a button that will bring up a file browser dialog so the user can easily pick a file.

This won’t actually work, however, without one more change. When we submit the form, it needs to use a special HTML format called multipart, so the form POST can provide not only the filename but the actual file contents. Edit the form_for line so that it looks like this:

<% form_for(@asset, :html => { :multipart => true }) do |f| %>

Now repeat these changes in the file views/edit.html.erb, so you’ll be able to edit assets after they have been created.

Finally, we want to see the asset after we’ve uploaded it, so open the file views/show.html.erb, and after the line <%=h @asset.name %>, add this line:

<%= image_tag @asset.public_filename %>

This uses the image_tag helper, which generates a standard HTML img tag. We use the method public_filename, which is provided by attachment_fu to give us the externally accessible filename for the image.

Now you can browse to //localhost:3000/assets, click New asset, enter a name (we’ll use “logo” for our example), click the Browse button to locate an image file, and finally click Create. The file is uploaded, and the database record to associate it with the name (and store other information about the file) is created. The image should then be displayed on the “show” scaffold page.

e. Using the image

Now we’ll add a helper, much like the one we created for the content blocks, to make it easy to display images. Add the following code to the file helpers/assets_helper.rb:

   def show_asset name
      asset = Asset.find_by_name(name)
      if asset
         image_tag asset.public_filename
      else
         "No content for #{name}"
      end
   end

Now, assuming you’ve uploaded an image and named the asset “logo”, you can add the following code to the home page:

<%= show_asset 'logo' %>

And the image should be displayed there.

3. Adding an admin page and log-in

a. Creating an admin page

We’ve been entering the full URL to get to the admin pages for content blocks and assets, and we should have an easier way. Let’s create an admin page.

In the file static_controller.rb, add the empty method for an admin page:

	def admin
	end

And then create a file named admin.html.erb in the views/static folder. (In NetBeans, right-click on the views/static folder, choose New > ERB, and enter the name “admin.html”. NetBeans will add the .erb suffix.)

Open the file views/static/admin.html.erb, and add the following:

<h1>Site Admin Dashboard</h1>
	<ul>
	  <li><%= link_to 'Content Block Admin', content_blocks_path %></li>
	  <li><%= link_to 'Asset Admin', assets_path %></li>  
	</ul>

Now add a link to this admin page to the layout, which you might put in the navigation bar, or in the footer:

<%= link_to 'Admin', :controller => 'static', :action => 'admin' %>

Now you can browse to the admin page when you want to access the content blocks and assets administration pages.

b. Adding authentication

We now have the heart of a content management system. But we’ve also made it possible for any visitor to modify the content of our site! Clearly we need to require log-in (authentication) before accessing these functions.

As usual, there’s a plugin that takes care of all the dirty work for us. Let’s install restful_authentication:

  script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/

You can view the readme in vendor/plugins/restful_authentication to learn more about it.

This plugin provides a generator of its own to help us set things up, so let’s run it:

	script/generate authenticated user sessions

In this command, “authenticated” is the name of the generator; “user” is the name of the model that will store user names and passwords; and “sessions” is the name of the controller that will manage user sessions.

This generator creates a migration for us, but we need to run the migration, so go ahead and migrate the database to the current version.

Finally, to make the methods provided by this plugin available in your controllers, you need to add the following line to the file controllers/application.rb:

include AuthenticatedSystem

The application controller is “mixed in” to all other controllers, so any code you put here is available to all controllers. So by putting the “include” statement for the authenticated system here, you’re effectively adding that code to every controller.

c. Adding user management

Before we can have log-in, we need to have users, so let’s add some user management.

The authenticated generator we ran in the previous section gave us a users controller and a view for creating users, so browse to //localhost:3000/users/new and create a login for yourself.

The generator also created a sessions controller, which is what handles the actual log in and log out operations. Logging in is creating a new session, and logging out is destroying a session. It’s convenient to have shortcuts we can use to refer to these, so let’s add some named routes. Add the following lines to the top of the config/routes.rb file:

  map.logout '/logout', :controller => 'sessions', :action => 'destroy'
  map.login '/login', :controller => 'sessions', :action => 'new'

This gives us two things. The text ‘/logout’ and ‘/login’ makes those simple URLs work. And the words after the “map.” provide shortcuts we can refer to anywhere in our code when we want to generate that URL, using either login_path, for example, to generate a relative URL, or login_url, to generate a full, absolute URL.

Let’s add a log-in button to the navigation bar. Open views/layouts/application.html.erb, and add this code to the end of the list of links that creates the nav bar:

<li><% if logged_in? %><%= link_to "Log Out", logout_path %>
    <% else %><%= link_to "Log In", login_path %><% end %></li>

The restful_authentication plugin provides us with a “logged_in?” method that we can use to determine if any user is logged in. If someone is logged in, then we display a log out link; if not, we display a log in link.

d. Using authentication to protect the admin pages

Now we have our entire authentication system in place, but we haven’t actually used it to require login for any pages. We need to invoke the authentication feature for the pages for which we want to require log-in.

Open the file controllers/static_controller.rb, and add the following line in the admin method:

   login_required

That’s all it takes to protect that method. (“login_required” is another method provided for us by the restful_authentication plugin.) Try accessing //localhost:3000/admin, and you should be presented with a login screen. Go ahead and log in with the credentials you created in the previous step, and you should see the Log In button in the nav bar change to Log Out. Log out now so we can continue exercising the authentication.

Note that while we’ve protected the admin page, you can still browse directly to the content blocks and assets pages, as well as create users, if you know the URLs. Let’s fix that.

Open the file controllers/content_blocks_controller.rb, and add the following line right after the first line that defines the controller:

	before_filter :login_required

This invokes the same method that we added to static_controller/admin, but because we’ve put it in a before_filter, it is executed as part of every method in the controller, protecting all of them.

Make the same change to the assets controller and the users controller.

What’s Next?

We’re done! You now have a working, albeit somewhat primitive, content management system. To make this a more full-featured CMS, some things you might want to add include:

  • User admin pages, so you can list the users, delete users, and enable users to request new passwords
  • A page database, so the page title, metatags such as description and keywords, tab to be highlighted, and content blocks and images to be displayed, could be stored in a database.
  • An image processing library, such as Image Magick, so attachment_fu can generate thumbnails and other image sizes from uploaded images.

Viewing all articles
Browse latest Browse all 27

Trending Articles