Posted by Chuck Vose
Mon, 08 May 2006 06:15:00 GMT
Intro and Concept
One of the first things I tried to do in Rails was to figure out how to make a nice, generic website that anyone could update. After about sixteen iterations I’ve finally stumbled on one that I think will finally work.
As of now it’s only partially complete. I’m really only using this method to generate the titles and meta information but each time I have to make a change to the website I find more and more information going into what I call the welcome message.
The idea is as follows:
- Making an action for each page is almost always rediculous and hard to update. So that’s pretty much out.
- Making a loader page that loads a Page object and displays the content works for the most part if you don’t have special content (and as long as you remember to change the :parameter if you ever try to paginate a series of pages.)
- Making the content an optional part of an initialize function in the application.rb will ensure that you can always access page related information on any page (ever dynamic pages).
In order to do this I have a function called `initialize` in my application.rb that parses the @params and looks through the database for a entry in the welcomes table. As I said, I’m only using parts of this but it would be easy to extend; initially this was just to display a welcome message on any page they wanted.
If there’s an entry in the welcomes table with controller params[:controller], action params[:action], and id == params[:id], then it returns a raft of information such as the page title, a background image, meta values, etc. These override the defaults in the page (and there must be defaults) and make the page dynamic.
The beauty of this approach is that it’s very easy to take an action that actually has an action (such as /admin/delete_user) and pop a new title on it, or meta values, or custom css, or whatever you can think of. If you have this table contain content you could have a blank template by default that gets overridden by the content in the row.
Steps
- Create the table and figure out what you want to be able to apply to any action. Titles and meta values are a great start. Make sure to make a unique index on the controller, action, and id rows (This is very important).
- Create an initializer function that searches this new table for the controller, action, and id in the params.
- Create before_filters in each controller you want this to be available in. This may be possible in the application.rb but I haven’t tried it (and it can be extremely picky).
- Create forms that make editing these items easy. Definitely note that it’s entirely possible to have /page/page_loader override /page/page_loader/3. This may be intended and a good idea but it’s pretty important to note that it could completely wreck things until you figure out why things aren’t working.
- Lastly, create sane defaults in the initializer function for pages that don’t have entries. This is incredibly important.
Conclusion
I realize that I breezed over all the details, this was intentional. Because I’ve not actually done this entirely in a site (only as far as titles and whatnot) I don’t want to give fake code samples. I’ll get there eventually but it may be a while until I start a new project.
At any rate, the idea is sound and is working in part very well in my sites so far. There are a lot of gotchas, mostly in the defaults, but there’s also a lot of room to innovate. The idea of the defaults cascading is a wonderful extension especially when so many sites are in tree/nested set formations.
Keeping the attributes away from the actual code is a great way to make it easy for customers to interact with the website. Customers often want to change little things and it’s important that they be able to change as much as possible even when the code is mostly hidden in a template or you’ll end up making little changes for the rest of your existence.
Posted in Rails, MySQL | no trackbacks
Posted by Chuck Vose
Thu, 20 Apr 2006 19:39:00 GMT
Holy crap I finally figured out why unit tests exist! To give me something to think about in the shower. Yep, simple as that.
No, wait, there are two really good uses off the top of my head: packaging and testing filesystem crap.
The first use is if you’re exporting a plugin for later use (or for general distribution). In this scenario there’s a fairly good chance that the user importing your plugin has messed up their install or is using a database that isn’t the same version or even the same type as the one you’re using.
In either case if you have unit tests and none of them fail then you know that the database probably isn’t at fault.
The second use is for filesystem crap. I can still count the number of times I’ve messed up the permissions on a folder only to find it after a half hour of debugging. This should never happen and there are wonderful tools to prevent it. `ri File` and `ri FileUtils` should get you started if you’re curious.
Specifically I’ve been working on a photo upload admin because I don’t understand file_column. In this I occasionally have to make a directory (okay, every time I upload something that isn’t a graphic) and it would be really nice to know that when I post it onto our production server that it’ll work when I release it into the wild.
Under this column is a wonderful addition to test_helper to check that things are correctly set in other places. Since rake is seperate from the dispatchers it could check their permissions, check the permissions of the public and log folders, and a myriad of other wonderful tests that could help newbies get on their way.
Rake is already pretty good at this but it serves as an excellent example of one use for unit testing.
Posted in Rails, Programming | no trackbacks
Posted by Chuck Vose
Thu, 20 Apr 2006 19:04:00 GMT
Overview
A customer of ours has a splendidly restrictive environment which I have the pleasure to work within. Not only is there no Rails, there’s no PHP and presumably no database to hook into even if there were dynamic languages. This is probably complete crap but it led to an interesting problem solving situation regardless.
What do you do with a client that wants hundreds of pages to be redesigned when you don’t have access to handy timesaving tools like Rails or PHP?
Long before I started research I told my boss to continue with the site anyways; I thought that caching would work exactly the way I needed. This turned out to be completely wrong but the site was well underway so I had to figure something out.
After trolling the net for a long time I stumbled on a website that took ASP pages and made static pages out of them by spidering; basically wget tweaked a little bit and resold for far too much money.
So I endeavored to do exactly the same for Rails.
Routing
In order to make this work I knew that I needed to do be able to represent pages by their name; something like activities.html would pull up the page with the title ‘Activities’. Once I had the idea it was a short jump to the following routing rules:
map.connect '', :controller => 'page', :action => 'splash_page'
map.connect ':id', :controller => 'page', :action => 'index'
map.connect ':controller/:action/:id'
The first allows the blank page to be a custom welcome page as is standard for us. The third is the standard route just in case, but the second is the monkey-maker.
I realize now that I could have just made .html but I’ve justified it to myself by saying that the titles make for better presentation.
The Code
The code below is the default action for the controller and essentially fills the spot of the routing code with a couple of tweaks.
The index bases decisions on whether there’s just a number, whether there’s a .html, or just straight text.
def index
# If there's a number in the id assumed that it's the id of a page.
if params[:id] =~ /^\d+$/
@page = Page.find(params[:id])
# If there's a .html in the id assume we want the page with that title.
elsif params[:id] =~ /^.*\.html$/
# This allows users to use any case for the link name.
# Otherwise exporting to windows/mac might break from
# multiple pages being named the same. For instance
# Index.html != index.html in Linux but does in Windows/Mac.
# Therefore sending both versions will break a
# lot of archiving utilities in Win/Mac.
if params[:id] =~ /[A-Z]/
redirect_to :action => :index, :id => params[:id].downcase
end
# Otherwise strip the .html and the _ then query the database.
@page = Page.find(:first,
:conditions => ['title = ?', html_to_title(params[:id])])
# Finally if theres simply a name we should redirect to
# the .html version like we did above.
elsif params[:id]
redirect_to(:action => :index,
:id => title_to_html(params[:id])
end
# If the page somehow makes it through that battery
# and the routing rules above just redirect to the
# homepage keeping the flash intact.
if @page.nil?
flash[:notice] = flash[:notice]
redirect_to :action => :splash_page
end
end
Finally there are the helper scripts used above:
def title_to_html(title)
title.downcase.tr('\ \\\/', '_') + '.html'
end
def html_to_title(title)
title.gsub(/\.html/,'').humanize
end
Wget
Now that we have pages that are entirely named correctly we can simply use wget to create a perfect copy on her server.
For easy reference I used the following:
wget -m -nH www.example.com
Conclusion
After hours of tweaking I’ve arrived at a very stable and comfortable arrangement. The client knows that pages are simply the name of the page with underscores for spaces and for the most part things are working nicely.
Changing page titles is still a pain and probably always will be. We dealt with this by making a links area above the text which adds some order while solving the interpage problem. I think there are currently only two links in the actual pages that point to a page on the server.
When using wget it’s especially important to make sure that all the links are the same style. For instance having a / in front of some links but not in front of others can lead to weirdness when you’re in a sub-directory and having mixed case will break windows servers (though I’ve attempted to remedy this).
Another caveat is that wget doesn’t follow javascript links, pop-ups, or roll-overs. All pages that can only be accessed through a form or a js action need to be manually copied with the rest of the files.
If nothing this project makes for an interesting comparison in benchmarking. If you were benchmarking the caching ability of a Rails app it would be lovely to see the speed of the straight html as well.
Posted in Design, Rails | no trackbacks