July 24, 2007 @ 09:06 AM by nap · 0 comments
Often after I'm finished building the bulk of a web app, I find there are some secondary pages that need to be built. These pages are largely informational, such as an about page, a contact page, an FAQ, etc. They're relatively static in terms of the content, but it's always nice if we can supply our client (or ourselves) with a nice CMS-style interface to make updating them easy, and within the context of our existing application layouts. Keep it simple, keep it DRY.
The obvious thing is to cook up some sort of PagesController from scratch. This is nice because it'll make use of your existing facilities, your authentication/authorization system, layouts, etc. It is custom, after all, and a custom fit is almost always the best fit. But it's a fair bit of work for something that's probably not 'core' to the application, and takes cycles away from other places they could be better spent.
On the other hand, you can integrate with a 3rd-party CMS or blogging package like Typo, Radiant CMS, or Mephisto. They're all great packages and do what they do really well. The downside is you've got to write a fair amount of glue to hook everything together and make it look (and feel) uniform.
Another option is to use Matt McCray's Comatose plugin, a micro CMS. It's got all the basic functionality you want for this sort of stuff out of the box and it couldn't be much easier to use. The real bonus is integration is almost completely seemless, which makes it (imho) the best of both worlds for this sort of project.
Installing the plugin gets you a ComatoseController and a ComatoseAdminController. You add an entry (or multiple entries, if you like) in your routes file to tell your application when to invoke the ComatoseController. You might prefer a scheme where all URLs starting with /pages are passed to Comatose, for example. Then you log into the admin controller (which also needs an entry in routes) to create the pages. All the basic management tools we need are here; pages are organized hierarchically and can be edited with great ease, using a variety of markup filters. Each page gets a number of attributes, including a title, keywords, author, etc.
Basically it's everything we need for the bare-minimum no-frills CMS experience and nothing we don't. Which is just the way I like it. Check it out for your next project.
UPDATE
Anyone having issues with Comatose and authentication should check out this bug report. If you're specifying an alternate session key, you should put it in environment.rb instead of ApplicationController.
ActionController::Base.sessionoptions[:sessionkey] = "yourcustomsessionid"
Comatose controllers inherit directly from ActionController::Base instead of from your application controller. So if you specify the session key in application.rb, the Comatose-driven sections of your app will be blissfully unaware of it. This means a method like logged_in? (which checks the session for your login status) will always report back as false.
July 13, 2007 @ 11:13 AM by nap · 0 comments
One of the nice features of Rick Olson's excellent restful_authentication plugin is the store_location facility. If you set up a :login_required before filter in your controller, the access_denied method will get called if the user isn't logged in. The requested URL will be stored in the session and the user will be redirected to the login page. After logging in, redirect_back_or_default is called by SessionController#create, which pulls the location out of the session and redirects the user back to where they intended to go in the first place. Very slick.
However, if you've built an application which has a login box in the sidebar (I recommend simple_sidebar), restful_auth has no idea where you were coming from before the post to the create action. So you get redirected to the default (usually '/') specified as a parameter to redirect_back_or_default. This isn't usually what the user expects. They expect to be returned to the page they were looking at before if they log in from the sidebar. Oh noes!
Here's a little recipe you can use to provide that...
First, a simple change to authenticated_system.rb in your lib/ directory (this file is created by the authenticated generator). Change the store_location method to this:
def store_location(location = request.request_uri)
session[:return_to] = location
end
Before this change, store_location took 0 parameters and always set the return_to location to the current request URI. Now, we've modified it so it can (optionally) take the location to store as a parameter. If this parameter isn't supplied, it defaults to the old behavior.
Next, change the create method in your session controller so it checks the HTTP referer. Just add one tiny little line to the top of the method:
def create
check_referer
...
end
Now let's write that check_referer method (it should be protected, not public):
def check_referer
referer = request.env['HTTP_REFERER'] || ""
if referer.match(request.domain) &&
!referer.match(session_url) &&
!referer.match(login_url)
store_location(request.env['HTTP_REFERER'])
end
end
What this little utility method does is check the HTTP referer. If the referer is from the current domain, and isn't the session#create URL or '/login' (in my application, there's a login_path named route), we go ahead and store the location of the referer. This will later be plucked out by redirect_back_or_default. Note that you'll need to regex match to make sure the referer value isn't anything you don't want to store. For instance, if the user navigates to the login page directly and logs in from there, you don't want to redirect back to the login page on success. You probably want to redirect to the default page, which is whatever you're specifying as the default with redirect_back_or_default.
So that's it! An easy way to handle redirects from sidebar login blocks, without muddying the already elegantly designed redirects that happen when access is denied.
If you want to write a functional test for this, try something like this:
def test_redirect_to_referer # for login sidebar
location = url_for(:controller => 'foo', :action => 'index')
@request.env['HTTP_REFERER'] = location
post :create, :login => 'quentin', :password => 'test'
assert_response :redirect
assert_redirected_to location
end
July 11, 2007 @ 08:03 PM by nap · 0 comments
JRuby continues to develop in interesting ways. Earlier today, Zed Shaw (Mongrel's dad) announced the release of Profligacy 1.0, which you should take a look at if you've got a thing for building cross-platform desktop apps in Ruby. It's pretty cool.
Profligacy is a (wildly extravagant?) library that purports to take the pain out of using Swing components with JRuby. It uses LEL, Layout Expression Language, to bring a sort of wiki-ish flavor to component layout. This is truly bizarre at first glance, but damn cool at the same time. Basically the Ascii art you lay out with LEL translates into a Swing GroupLayout. Ah-wha? Yep. Check out some examples. I told you it was cool.
It also does some other neat stuff like auto-converting procs to Listener interfaces, but I won't go on about that, as you can go check the release notes instead. If you've ever worked in Swing before, you know how painful it can be. Profligacy makes it significantly less bitter to swallow. Now stop reading blogs and go build your first cross-platform Ruby desktop app. You know you want to.