Putting the Page Contents into the Database
In this lesson, we create our first database table and Rails model.In the previous lesson, we created static pages, with all the contents in the view files. The problem with this approach is that to change any text, you must modify the code.
A better approach is to put the page contents into the database, so we can then provide an administrative interface that allows non-technical users to modify the pages. In this lesson, we do just that, creating the core of a simple content management system (CMS).
Setup
We begin with the code with which we ended Lesson 9. These zip files contain the beginning and ending states of the code:
- Learning Rails example app code as of the end of Lesson 9
- Learning Rails example app code as of the end of Lesson 10
See Lesson 8 for pointers on setting up your development environment.
In Lesson 8, we explained how to set up the MySQL database. In this lesson, we use SQLite, which is a little easier to get going (if you’re using Leopard, you don’t have to do anything to use SQLite). If you want to use MySQL instead, you just need to change the settings in database.yml. In particular, in the development section, replace the code in the sample application with something like this:
adapter: mysql database: learning_rails username: root (or other user name) password: secretword (whatever your password is; leave blank if none) host: localhost
You’ll also need to create the database, using the mysql command-line interface or one of the GUI interfaces (we’re partial to Navicat). With sqlite, this is done for you automatically.
Lesson Outline
Here’s the coding steps we take in this lesson. Watch the screencast for details and explanations (see link at the top of the left column).
1. Delete the four static views in views/pages
2. Create a scaffold for the new Page model with this command:
script/generate scaffold page name:string title:string body:text
3. Run the migration:
rake db:migrate
4. Start the server:
script/server
5. Delete the automatically generated layout file views/layouts/pages.html.erb
6. Create the four pages (home, about, contact, resources) through the admin interface:
Browse to http://localhost:3000/pages/new
7. Create a controller and view for the public view of the page:
script/generate controller viewer show
8. In controllers/viewer_controller.rb, add this line to the show method:
@page = Page.find_by_name(params[:name])
9. Replace the boilerplate text in views/viewer/show.html.erb with this line:
<%= @page.body %>
10. You can now access the home page using the explicit URL:
http://localhost:3000/viewer/show?name=home
11. Create a route to clean this up. Add this line to config/routes.rb, after the map.resources line:
map.view_page ':name', :controller => 'viewer', :action => 'show'
12. Update the route for the root (home) page (we created this line in Lesson 9):
map.root :controller => 'viewer', :action => 'show', :name => 'home'
13. Fix the navigation links in views/layouts/application.html.erb:
<li><%= link_to 'Home', view_page_path('home') %></li>
<li><%= link_to 'Resources', view_page_path('resources') %></li>
<li><%= link_to 'About Us', view_page_path('about') %></li>
<li><%= link_to 'Contact Us', view_page_path('contact') %></li>
14. Change the title tag line so that the title is pulled from the database:
<title><%= @page.title %></title>
This will work for our normal pages, but it won’t work for the admin pages, since there isn’t always a valid @page object for those pages. So we need another way to set the page title for those pages. We change the layout to this:
<title><%= @pagetitle || @page.title %></title>
The || is the Ruby OR operator. If the item before the OR is not false, then the second item is not evaluated, so this expression essential says “use @pagetitle, unless it is nil; if it is nil, then use @page.title”. This enables us to set the pagetitle with an explicit instance variable in the admin controllers, and still use the @page object in the normal page controller.
Now we need to set @pagetitle in the pages_controller. We want to set it to “Page Administration,” regardless of which action is executed. Rather than placing this code in every action, we can use a before filter, which runs the filter action before any other controller action. The code, which goes at the top of pages_controller.rb, is as follows:
before_filter :loadmetadata def loadmetadata @pagetitle = "Page Administration" end
Common Problems
Note that the home page route that we set requires that you create a page named home in the database. If you haven’t created a page with this name, you’ll get an error when browsing to the base URL. (To create a page in the database, browse to localhost:3000/pages/new)
Also, the navigation buttons assume there are pages with the names of about, resources, and contact, so if you haven’t created pages with those exact names in the database, you’ll get an error when you click on the corresponding button.
Onward!
We’re back to where we started! The site now works just like the one we created in Lesson 9, but now all the page contents are in the database, and we have a simple administrative interface to edit this text.
There’s one big problem with this simple implementation: anyone who can guess the URLs for accessing our admin interface can modify the site! In the next lesson, we’ll add a user log-in system to provide authentication for accessing the admin interface.
Resources
For more on the SQLite database, see our list of SQLite 3 tutorials, software, and FAQs.
Another Rails 2.0 scaffolding tutorial.
Peepcode has a two-part introduction to Rails, titled Rails From Scratch.
Ready to dive deeper into Rails? See our list of Ruby on Rails books
Comments on This Lesson
From: Rafa Date: 03/13/09 10:54 AM
Subject: Thank's from lesson 10
I have just finished this lesson succesfully.
Thank’s a lot.
From: Matt Fox Date: 12/20/08 01:07 AM
Subject: Thank you
Thank you so much for making these lessons. I have been working on understanding web development for a year now using RoR and your series has taught me more than any book or lesson I have found. Thanks again.
From: Ahad Bokhari Date: 12/17/08 05:37 AM
Subject: SQL works
In lieu to my sqlite3 problem BELOW SQL however works (I am also using NaviCat) for my GUI. Guess ill go with SQL till i solve the small issue. Cheers
From: Ahad Bokhari Date: 12/17/08 05:26 AM
Subject: SQLITE3 Problems
When i create an new app Rails doesn’t create a corresponding development.sqlite3 file in the db folder. I have tried to install the sqlite3 gem but it doesnt work. I have tried to update the installed gems → there is nothing to update.
I have even dropped the sqlite application file and sqlite3.dll file into my D:/ruby/bin folder → yet still errors.
Anyone out there facing the same problems?
From: Michael Slater Date: 11/10/08 08:35 AM
Subject: mysql vs. sqlite problems
Fidelio,
Starting with Rails 2, Rails apps are configured by default to use sqlite. To use MySQL instead, you just need to change the settings in config/database.yml.
In each of the sections (development, test, production) change the code to something like this:
adapter: mysql
database: learningrails_development
username: deploy
password: secret
host: localhost
Substitute, of course, the name of the database you are using, as well as the username and password you have set up for MySQL.
There’s a little more on this in Lesson 22
From: Fidelio Artur Date: 11/10/08 01:21 AM
Subject: migration
Hi to the team. Thanks for your work. Is pretty helpful. When I run the migration comnand it say
rake aborted no such command file to load – sqlite3 see full trace by running task with —trace.
I am working on mysql 5.0. Isn’t mysql suitable for rails 2.0? What can I do to make this feature operational. Salutation Artur
From: Fidelio Artur Date: 11/09/08 12:05 PM
Subject: migration
Hello when try the command reke db:migrate the result is: C:\Documents and Settings\SJPB\Desktop\admnstr\fil>rake db:migrate (in C:/Documents and Settings/SJPB/Desktop/admnstr/fil) rake aborted! no such file to load — sqlite3 (See full trace by running task with —trace) C:\Documents and Settings\SJPB\Desktop\admnstr\fil>
what can I do
From: Andreas Lyngstad Date: 10/27/08 05:06 AM
Subject: map.root
Hello
My approach
map.root :controller => ‘public’, :action => “index”, :name => Page.find(:first).name
is not good. It makes migrate on a empty database impossible. During the migration, routes.rb runs for some strange reason. When it does not find anything it returns an error.
@Micael Slater I do not think it is a good idea to do any finds in the routes.rb file
I think we’ll have to find a better solution for the routing of the main page.
From: Michael Slater Date: 10/22/08 06:46 AM
Subject: Error no viewer controller
Jaime, have you run the migrations (rake db:migrate)?
If so, make sure your code matches what’s in the archive linked earlier in this article.
From: Jaime Date: 10/22/08 01:40 AM
Subject: ERROR NO VIEWER CONTROLLER??
Hello..
AFTER all set in the controller viewer, when running the test on firefox web browser, localhost:3000/viewer/show?name=home, it gives me undefined method `find_by_name’ for #….. and also /usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:1618:in `method_missing’ app/controllers/viewer_controller.rb:3:in `show’…
I’m running ubunto 8.04TLS and all update version ex: ruby gem 1.3.0..
From: Michael Slater Date: 10/15/08 10:03 PM
Subject: Finding the home page
Andreas,
Your approach seems reasonable to me. The only downside is that the root page will always be the one with the lowest-numbered database ID, which might not always be what you want if you’ve changed things around.
The best approach would be to add an attribute to the page model for “home”, and find the page that has this attribute set.
From: Andreas Lyngstad Date: 10/15/08 02:28 AM
Subject: routes.rb
Hello Well,
I guess the sun has been shining for quite a while here. But, now at last I have a possible solution. The problem was that if the root is like this:
map.root :controller => ‘public’, :action => “index”, :name => “home”
It wont work if the user changes the name of, or deletes the home page. So, what if we make a find by id and call the name attribute instead. Like this:
map.root :controller => ‘public’, :action => “index”, :name => Page.find(1).name
It will make it possible to change the name, but it is a “no go” if the user deletes the page. So, my solution was this.
map.root :controller => ‘public’, :action => “index”, :name => Page.find(:first).name
Now, we will do a find on the first instance in the database whatever the id or name is and call the name attribute of this instance.
Does anybody see drawbacks with this approach?
Hope it helps someone!
From: Ronen Date: 09/26/08 03:36 AM
Subject: Steps 11, 12 - Resolved
Found the comment by James and tried what he suggested. - Restarted the mongrel server, but it didn’t resolve the issue. - Checked the version and it was 1.2.0. - I ended up rebooting my virtual machine and it works now.
From: Ronen Date: 09/25/08 06:05 PM
Subject: Lesson 10 - Steps 11, 12
Thank you very much for this free online course. It’s a great ‘helper’….
I’m getting the following error when I’m browsing to localhost:3000/home
“Only get, head, post, put, and delete requests are allowed”
I copied the entries from your lesson 10 files, and still get the same thing.
The original link from step 10 (http://localhost:3000/viewer/show?name=home) still works.
What am I missing?
From: Ramiz Date: 08/03/08 11:42 PM
Subject: script/generate scaffold
thank you it works :-)
From: Michael Slater Date: 08/03/08 11:36 PM
Subject: PagesHelper problem
The error is because in a previous lesson we created the pages controller, and that had the side effect of creating the pages helper. You need to delete that file before running the scaffold command. It’s in app/helpers.
From: Ramiz Date: 08/03/08 11:28 PM
Subject: script/generate scaffold
in learningrail_10 i get a error and i dont know what to do please help!!!
imac:learningrails_10 Ramiz$ script/generate scaffold page name:string title:string body:text The name ‘PagesHelper’ is either already used in your application or reserved by Ruby on Rails. Please choose an alternative and run this generator again.
From: Rick Date: 08/02/08 09:21 PM
Subject: remove pages_helper also
When I started with the learningrails_9 project and followed the screencast, I also needed to delete the PagesHelper file in the Helpers directory or else the scaffold script wouldn’t run (gave the error about that PagesHelper existing.) Once I deleted it, the scaffold command worked fine.
From: Michael Slater Date: 07/26/08 02:53 PM
Subject: Multiple filters
Gurvinder, to answer your questions:
1. Yes, you can have as many before_filter statements as you’d like, and you can list multiple actions on one statement.
2. Yes, some things require restarting the server, even in development mode, although most do not. I don’t have a crisp definition of what requires a restart — can anyone help out with this?
From: James Date: 07/19/08 11:49 PM
Subject: Error: ActionController::MethodNotAllowed
Error: ActionController::MethodNotAllowed – Only get, head, post, put, and delete requests are allowed.
Resolved by restarting the mongrel server. I had Rails 2.1 and Mongrel 1.1.4. Use CTRL+C to kill the running server and then restart it with the “ruby script/server” command. I also updated Mongrel to the latest 1.1.5 by performing “sudo gem update mongrel” or just “gem update mongrel” on Windows.
When in doubt, first try restarting the mongrel, passenger, webrick, glassfish server depending on which you are running in development. You are not supposed to have to do this very often during development but sometimes you have to restart the server. i.e. editing the database.yml file or adding a controller, etc. may require a restart of the server.
From: Gurvinder Date: 07/19/08 03:26 PM
Subject: Couple of Issues
Hi Guys
I like your Screen cast, but while i was working along with your screencast i found couple of issues which are not still cleared like:
1. Can we have multiple befor_filters? 2. When i used the command map.view_page in route.rb it did NOT work till the time i restarted the server….why i have to restart the server?
Waiting for your reply….
From: Yukti Date: 07/13/08 10:12 PM
Subject: Problem accessing these screen casts
Hi Micheal & Christopher
Hope you are doing good. I am Yukti and am following your tutorials to learn ROR. This is a Bible for me.Thanks a ton !! for the efforts that you both are putting. :)) Till chapter 8 it was all good for me but after that I am just not able to access the tutorials. Reason being the heavy screen casts . Please do something about this. Also if managing screen casts is that difficult, please arrange transcripts or screen shots . Also we can try with flv players.
Will be a great help
Care Yukti.Vig
From: Andreas Lyngstad Date: 07/04/08 09:41 PM
Subject: Subject: routes.rb
Thanks for comment, Christopher. I will try to do some work on this. It might be in a few days, because the sun is finally shining here in Norway. So, i guess we need to enjoy the outdoor while it lasts. I’ll get back to you on it.
From: Rafael Jamur Date: 07/03/08 10:43 AM
Subject: Excelent job.
Thanks a lot for this work.
From: Christopher Haupt Date: 07/03/08 01:12 AM
Subject: RE: routes.rb
Andreas: Good question. There are lots of strategies you could take here. For instance, you could introduce a specific, new “home” action, set that in the routes file to keep it fixed, then put logic in the controller to resolve which page is the “home page”. You might look for the name “home”, or perhaps add to the model so the user can mark any page as the “default” page, or some other even just have a static bit of text that is used if the page named “home” can’t be found.
Try some experiments and let us know what you come up with!
-Christopher
From: Andreas Lyngstad Date: 07/02/08 08:24 PM
Subject: routes.rb
Hello – I really love these screencasts. They give understanding of the rails structure that no other book or screencast. I have one question though. If you change the name of the first page (home) to something else, you also have to change it in the routes.rb map.root. How can we protect the users from doing this. Is it possible to lock the name on the first page or maybe rout to the id number instead?
From: Rushil Date: 06/23/08 05:24 AM
Subject: InvalidAuthenticityToken
Hey guys, I’m working on windows, and as far as I know, I was able to setup my environment for Ruby on rails perfectly. While following this lesson, when I get to the point of creating new pages to be added (after generating the scaffold and migrating) , I get the error ActionController::InvalidAuthenticityToken in PagesController#create … when I click on create. I tired googling but to no avail. I’d appreciate if you could help me out….. in case u need more details regarding the error, please let me know.
Thanks a lot! Really njoy ur tutorial!
From: Ben Date: 06/22/08 09:05 PM
Subject: Problem
Hi,
After doing what you showed and updating the viewer controller I get an “undefined local variable or method” on page. How does the controller recognize the model? I must be doing something wrong.
Thanks
From: Sonali Agrawal Date: 06/18/08 11:02 AM
Subject: Problem Solved
Hello Michael,
Thank you. There was no typo error. I just noticed that I had an older version of Ruby Gem. I updated to the latest 1.1.1 one, and now it works. Thanks again, and sorry for the stupid question from me!!
From: Michael Slater Date: 06/18/08 09:52 AM
Subject: MethodNotAllowed error
Sonali, check your routes.rb file. I suspect you have a typo in there. If you can’t spot it, try replacing your file with the one from the end of lesson zip file.
From: Sonali Agrawal Date: 06/18/08 09:10 AM
Subject: Action Controller: Exception caught error.
I have been following your tutorials from the start. The application was working fine. But all of a sudden after I did the changes, I am getting this error:
ActionController::MethodNotAllowed
Only get, head, post, put, and delete requests are allowed.
What can be the problem? I am not able to move ahead.
From: Walter Date: 05/14/08 05:57 AM
Subject: action/id for RESTful routes
Thanks Michael. I was seeing lots of information from “rake routes” and didn’t realize it was the default included RESTful behaviour. It makes sense now ;) Cheers.
From: Michael Slater Date: 05/14/08 04:02 AM
Subject: action/id for RESTful routes
Walter, those routes come from the “map.resources :page” line, which generates a whoe set of RESTful routes for the Page resource. pages/show/4 is the traditional route using the default route definition you mention. pages/4 is the RESTful route, in which the action is inferred from the fact that a get request is being made of the page resource and an id is being provided. A get request with no id triggers the index action; a get request with an id triggers the show action.
From: Walter Date: 05/13/08 03:22 PM
Subject: question regarding :id association in controller
if the default routes setup is :controller/:action/:id, how does rails know which action is applied in show and edit in the PagesController ie: pages/4/show and pages/4 both work for show, and pages/4/edit and pages/edit/4 for edit. It all works but why can it work both ways?
I see in /pages/index.html.erb that it just iterates the variable page, but it has no action association.
Other than that I’m fine with the tutorials, and am enjoying the series. Keep going!
Cheers
From: Michael Slater Date: 05/11/08 12:53 AM
Subject: No action error
Paul, it sounds like perhaps you skipped a step. Go back to Steps 10-13 in the lesson notes and make sure you’ve done all those items.
If you’re still getting an error, for what URL does the error occur?
From: Paul Denlinger Date: 05/10/08 07:48 PM
Subject: Error message after deleting static pages
Hi— First of all, thanks for a great series of lessons. I have a problem. After I deleted the static pages, and the views/layouts/pages.html.erb file, I could not connect and got an error message: “Unknown action, No action responded to home” Can you tell me what’s wrong?
From: Christopher Haupt Date: 04/27/08 12:42 PM
Subject: RE: Questions about how to forget about links
[Manuel] We’ll be automating the creation of the navigation links (tabs) in a couple of episodes and over time introduce additional refinements for organizing pages.
From: nate Date: 04/25/08 04:01 PM
Subject: I'm enjoying the course
Hi guys, I’ve been listening to your lessons while also working through the RailsSpace book. It’s great to see different approaches, and I really appreciate the extra effort you put into the abstract concepts, which is sometimes lacking in RailsSpace.
From: manuel Date: 04/20/08 02:59 PM
Subject: Question about how to forget about the links
Hi, I’ve been following your lessons and I think they are amazing. Thanks for all this information.
Now, I have a question:
Right now, every time I create a new page other than home, resources, etc. (the original ones) I have to go to layouts>application.html.erb to add that new page manually. How can I automatize this, I mean, that, once I create the new page, for instance, “how do we do what we do”, the navigation bar get updated by the application not by hand.
Can you put that in the lesson 11?
Thanks
From: Christopher Haupt Date: 04/19/08 02:12 AM
Subject: Viewer show action not working solution
[Working with Kenn offline] The find that is used by the viewer controller is case sensitive and uses the page model’s name attribute as a key to locate it. This means that you need to make sure the name you enter in the database (e.g. ‘home’, ‘about’, ‘resources’, and ‘contact’ in this lesson) are all the same case as used in places like the layout code that calls the viewer (see the nav bar code). The name attribute isn’t user visible, it is just a code word for us to use to locate and find a specific page by “name” behind the scenes. A good exercise for you all would be to update the pages and viewer controller to make entry and use of the name attribute always one case or another.
From: Kenn Date: 04/18/08 05:42 AM
Subject: Can't fiqure this out...
NoMethodError in Viewer#show
Showing viewer/show.html.erb where line #3 raised:
You have a nil object when you didn’t expect it! The error occurred while evaluating nil.body
Extracted source (around line #3):
1: <%= @page.body %>
From: Ljuba Date: 04/17/08 04:45 AM
Subject: Thanks Christopher
I just watched the updated version of lesson 10. Thanks a lot!
From: Christopher Haupt Date: 04/16/08 09:54 AM
Subject: Added title fix section to podcast
Per the discussion here, I’ve added a section to the screencast at the end that addresses a fix to the title handling. I’ve also updated the code archive posted here. If you want to rewatch just the updated section, it starts around the 27 minute mark.
From: Robert L. Crocker Date: 04/16/08 04:50 AM
Subject: Screencast #10
Definitely not a very useful app but great instructional tool. I found myself changing settings in the learningrails.css file just to see what happened. Very instructive. I tried the same thing Ljuba tried in regard to using the pages controller and got the same error. So I think he makes a good point but I’m still a little puzzled why @page(s) doesn’t have the information from the table.
The only thing I can think of is that instance variables don’t exist from controller file to controller file. They just exist between a controller and it’s views.
Will be very instructive to watch you guys fix what is apparently a very minor oversight.
From: Christopher Haupt Date: 04/15/08 09:20 AM
Subject: Fix for layout
…and if you want a quick fix for the layout issue below, change page.title in the application.html.erb layout file to this: (page and @page.title) ? @page.title : ‘Learning Rails Sample Application’
From: Christopher Haupt Date: 04/15/08 09:14 AM
Subject: RE: error with pages_controller
Good catch. We’ll be “cleaning” up some of these items as we progress through the lessons. There will be later episodes that cover testing too, so I expect us to try to catch a few of these there too. That said, we’ll likely have a few small logic errors, that while I’d like to say they are all “exercises for the viewer”, some are slipups! We’ll address this one.
From: Ljuba Date: 04/14/08 01:06 PM
Subject: error with the pages_controller
The only problem with the code at the end of this screencast is that that pages_controller doesn’t know about @page and so there’s an error when it tries to set the title of the administrative “pages”. Just fyi.
From: Ljuba Date: 04/14/08 01:00 PM
Subject: Nice touch
Also, nice touch making the command-line prompt learningrails_10!
From: Chris Madden Date: 04/14/08 11:58 AM
Subject: Great Job!
After attending your the Rails Quickstart Seminar in Feb, this is exactly what I need to keep going. Thanks again for these screencasts.
From: Ljuba Date: 04/14/08 05:54 AM
Subject: Wonderful, as usual
Completely agree with JohnLx about the pacing. Also, thanks for fixing the slow-motion effect of lesson 10. The screencast is much clearer now. Thanks again for making these!
From: JohnLx Date: 04/14/08 01:34 AM
Subject: Excellent Screencasts
Just wanted to comment that your first two screencasts have been excellent. The pacing seems just about right to adequately cover the necessary concepts without belaboring the basics.
Nice job and thanks for providing this service. I’m looking forward to the next lesson.
—John

From: John Date: 05/17/09 07:55 PM
Subject: Up to date...
Thanks for the great tuts. I just wanted to share a fix I just discovered regarding the scaffolding command. I’m running ruby 1.8.6 and rails 2.3.2. The scaffolding command line shown above was not working for me (<1 for 2> error), and I found some information about how the newer version of rails handles scaffolding differently. In the end however, all I had to do to get the line to run was to change “page” to “Page” in the scaffold command line.