Simple things should be simple, complex things should be possible. — Alan Kay
Here's a tiny little tip for handling those boiler-plate pages that aren't part of your app's functionality but you usually need anyway. It's good for setting up about, contact, copyright, etc. You can always throw those pages into /public as static html files, but if you want them to get styled with layouts, they need to be rendered as actions. This is a way to do that simply. It's not rocket science, but I haven't done a noob post in ages and I'm getting over a cold and I haven't posted in too long so gimme a break.
Say you want to have a simple landing page and a few typical boiler-plate pages. Let's start with the routes.
In config/routes.rb
map.root :controller => 'home'
map.home ':page', :controller => 'home', :action => 'show', :page => /about|contact/
In app/controllers/home_controller.rb
def index
# render the landing page
end
def show
render :action => params[:page]
end
Throw your about.html or about.html.erb and other pages into app/views/home and you're good to go. If you've set up page caching, this won't even slow your app down.
The :page => /.../
bit in the route constrains it to match only those specific urls. If you want, you can change that to a constant, like HomeController::
PAGES, so it's easier to manage.
If you want to link to those pages, you can use the route helper methods, home_path
and home_url
link_to 'About', home_path('about')
You could always unroll the routes and have a separate route for each page, but I find this way a bit drier. But if you'd rather have a specific named route helper for each page, that's an okay way to go. Either way, you get to use layouts in your pages, and have a nice simple way to get them rendered.
Is there an advantage to this over just relying on the fact that rails will fall back to looking for a view template with the action name, if the action is missing from the controller?
IMHO, this is the more simple approach to simple pages.
@Brian, it's simpler in some ways, but more complicated in others. I guess it's a matter of style and what you prefer. I don't like routes with wildcard actions - too many opportunities to accidentally expose a security hole. Also, your call to link_to won't work in that form in a different controller - you'll have to specify :controller => 'home' too if you're on another page. Me, I'd rather add a 2nd route and a 1-line method to keep things cleaner everywhere else.
Josh: Thanks for the post! I've been wondering how to handle this type of thing for a while. One question I've got is, if I'm building a RESTful rails app, should I worry about making this type of functionality RESTful as well? I don't think these pages fit into a RESTful model, but perhaps there is an accepted way of doing so. I guess not everything needs to be RESTful.
Thanks!
@Clayton: You definitely don't need this to be RESTful. Feel free to create a PagesController with a Page model... but that's really a lot more work than what Josh outlined.
I generally always just made a "pages" controller and then create a new action for every page. E.g, "about", "action", etc, and made a custom route for each.
Maybe not as dry, but I feel like it makes more sense. Maybe a little bit faster in the back end too vs. the regexp matching in routes like you indicated?
Maybe I'm just noob though =D
@Clayton, as Danger said, there's not need to go nuts trying to make everything restful. If those pages are only going to be read, they don't need CRUD type behavior.
@Daniel, that seems like overkill to me, unless you need to set up different data for each page to display. But then if you're going to be grabbing stuff to display out of the database or some other random data source, then you probably want something a bit more sophisticated anyway. But if you're worried about speed, just use page caching and it won't be an issue at all.
@Daniel I ofter make it the same way... :)
I am also a fan of the Page Model/Controller technique. That way I can set up the content to use textile for display, and let my clients do basic maintenance on the those pages. This way is nice though, if you know for sure that these pages will rarely if ever change.
I usually use a more general approach: every view in the page directory gets rendered. With this route:
every page has a nice .html extension, you can easily add views, leaving the controller completely empty. Link to pages with:
@clayton: it doesn't make any sense for static pages to be RESTful, it isn't really a resource, just 'pages', there are no collections or any actions to be perfomed.
Thanks for the replies everyone, that helped clear up my questions.
@Lar, @Iain: I do something similar sometimes. It all depends on whether I'm doing mostly static pages, or have dynamic content. One size doesn't always fit all! It's good to see so many variations on how to do this sort of thing.
Nice piece, John. Given Alan's admonitions, perhaps this easiest of easies should be easier.
I wonder if it wasn't error not to have this already in place? In particular, perhaps the standard railties should be set up with a home controller building the "welcome to rails" page, tooled to extend to other static pages as you suggest, page caching in place with a rake tool to expire the "static" home pages.
Consider the virtue of this to rails pedagogy, so we can get people into views right after learning the rails and script/server commands. In this way, the first thing they can do is build a static "helloworld" view. As we show the liitations of that approach, we use that to motivate the virtue of a model and controller. Then the fun begins...
This device could be practical, too. For example, why not use the built-in home controller and views to build cached standard error pages as well? This way, we rebuild them using our layouts or a minimalist version of it, squash the cache, and there we are. All of a sudden, the source for the static infrastructure of the site is in our app tree where it should be.
Back to our pedagogy perspective, the concepts of page caching could be introduced much earlier when teaching young railsies as well, starting with the home controller in a naked project. The hypothetical "static public view" necessary to introduce the simplest modes of caching never existed in any of my projects, but now it exists on a stock build home page.
I take a different approach of adding a custom method to the map object. It's not the prettiest, but it allows me to have named routes. You can find it here.
http://railscasts.com/episodes/79
How do you get that to work? I'm getting a "uninitialized constant ApplicationController" error when I do something like this in routes.rb:
Perhaps I'm missing something.
@George: Hey, I looked at a project and saw that I'm using a non-namespaced constant I set up in environment.rb rather than a constant in the HomeController class. I think I had the same load-order problem you had and punted by using the top-level constant instead of figuring out how to use the namespaced one, then forgot about that when I wrote this up. Sorry about the bad directions there.
Why the show/page indirection? Seems like just
should do the trick, too.
@Henrik: Hah. I have a mental block against using invisible action methods. Really, I don't trust them at all, but not for any good reason.
To the people worrying about restfulness: The method outlined in the blog post and all alternative versions given in the comments are perfectly restful, simply because they don't do anything but allowing idempotent GET requests on a certain resource ("about" information etc.) returning a certain representation of it (the HTML in your view template). As David A. Black recently pointed out, resources != controller + model: http://dablog.rubypal.com/2008/3/23/splitting-hairs-over-resource Read a little more about the web and REST than Rails documentation/blogs - it surely helps :)
I like creating a simple admin interface for static page content so site admins can change the content. We just released a screencast for Rails beginners that walks through the creation of a very simple scaffolded page model. I'd appreciate any comments on the approach we took.
One question is where to put the view action that renders the page. We chose to create a controller just for this; it seems a little heavy to have a controller with just one action, and with just one line of code in it at that, but it seemed the simplest to explain.
Another question is what to do if you want site admins to be able to put in little bits of code to access helper functions. Rendering the page body from the database as ERb works, of course, but is dangerous. Liquid is one way to go. Any other suggestions?
There is a nice way that Markus is using, without specifying page names and using caching to boot. http://snafu.diarrhea.ch/blog/article/4-serving-static-content-with-rails