|
Peter Marklund's Home |
Rails Internationalization with new gibberish_translate Plugin
We needed to internationalize the user interface of a Rails application that we are building and looked at a plethora of alternatives, such as Globalize, Globalite and the localization plugin by DHH. Finally we settled for the Gibberish plugin. Gibberish uses an unusual hybrid approach with both inline english texts in the code and keys for message lookups. A typical Gibberish lookup looks like this:
To complement the Gibberish plugin I've drafted the gibberish_translate plugin that adds a script for extracting all message lookups from a Rails app and a controller with a web UI for doing translations. The plugin also keeps track of which english texts a translation was made from so that you can flag when english texts are changed. The plugin avoids using the database and works directly against the YAML message files in the lang directory.
Rails Gotcha: Date Arithmetic, Time Zones, and Daylight Savings
Timezones and daylight savings can cause us programmers a lot of headache. This is evidenced by the fact that probably the most popular post in this blog ever is the one about a timezone aware datetime picker. Today I spent several hours debugging a problem a client was having with an eternal loop caused by the daylight saving transition October 28/29 in conjunction with 1.day.since. One of the chapters in the Code Review PDF by Geoffrey Grosenbach is about keeping time in your Rails applications in UTC. After todays exercises I must say I could not agree more with Geoffrey. In fact, I would go as far as to say that date arithmetic in Rails 1.2 is not reliable unless you set your ENV['TC'] variable.
Ruby on Rails 101: Presentation Slides for a Five Day Course
I've decided to share the presentation slides that I developed for the five day introductory Ruby on Rails course that I held in June here in Sweden. All in all it's 340 slides available under a creative commons license. You can download the slides as a PDF file here or view them over at Slideshare. To give you an idea of what's inside, here are the chapters:
- Rails Introduction
- Ruby
- Migrations
- ActiveRecord Basics
- ActionController Basics
- ActionView Basics
- Testing
- ActiveRecord Associations
- ActiveRecord Validations
- ActiveRecord Callbacks
- ActionView Forms
- Filters
- Caching
- AJAX
- Routing
- REST
- ActionMailer
- Plugins
- ActiveSupport
- Rails 2.0
- Deployment
- Resources
- Parting Words of Advice
I hope the slides will be useful in helping people learn and teach Rails. I'd like to thank David Heinemeier Hanson for creating such a wonderful framework and Dave Thomas for doing such a great work documenting it.
Rails Search Plugin Review: acts_as_fulltextable
The other day I had the opportunity to install and evaluate the recently announced acts_as_fulltextable Rails plugin. The raison d'etre of the plugin is to offer easy access to the MySQL full-text search engine. The plugin uses a FulltextRow ActiveRecord model stored in a MyISAM table with a polymorphic reference (id and model name) to the model that you want indexed and a single column to hold the search index text. All you need to do is basically add an acts_as_fulltextable declaration to the model that you want to search and this will then create a has_one :fulltext_row relationship and the appropriate callbacks to populate the index on create, update, and delete.
The acts_as_fulltextable plugin mostly exceeded my expecations, especially in terms of easy setup and maintenance. It makes site-wide search (across all models) as well as model specific search (constrain your search to one or more models) very easy. A strength of the plugin is the simplicity. There is very little code and the architecture is straight forward and easy to grasp.
Something to be aware of is that MySQL uses OR searches by default. I wanted to follow the Google convention of AND searches and found myself having to prefix all query terms with a plus sign to accomplish this. Is there a better way? A more serious issue is that MySQL doesn't seem to support word stemming. It turns out that the acts_as_fulltextable plugin does a gsub on search queries to add a trailing * to each search term. MySQL calls this truncation. The star ("*") works as a wild card. If you are aware of this feature you can use it to manually work around the absence of stemming, just search for house* and that will match both "house" and "houses". It will also match "household" which may or may not be what you want.
A documented gotcha that I ran into is that if you combine the leading plus sign and the trailing star (i.e. use both boolean AND and truncation) then stop words will not be removed from your query and thus you may end up with zero results. This is very annoying. What is the solution? I don't know. Maybe go with OR searches, live with the stemming issue (without the star), or find a way to remove stop words yourself. Stop words are supposedly configurable in MySQL.
The acts_as_fulltextable plugin was created because of stability issues with Sphinx and Ferret and because Solr was considered overkill. Lucene/Solr appears to be the most well documented, reliable, scalable, and flexible open source search engine out there. My guess is that Solr is still your best best if you are a search power user and your requirements are high. Because of its simplicity and ease of maintenance though, acts_as_fulltextable offers a pretty attractive lower end alternative.
Rails Plugin: mysql_requirement
I added the plugin mysql_requirement that allows me to check the encoding/charset settings of the MySQL server as well as its version when my Rails application starts up and abort otherwise.
Rails Tip: Configuration Parameters
The PeepCode Code Review PDF has some nice advice about how best to deal with configuration parameters in your Rails applications. Traditionally most of us have probably just stuck global Ruby constants in our environment.rb files, but there are more structured ways of doing it. I've started using the app_config plugin and it seems to work fine so far. To make sure I haven't forgotten to define a parameter in an environment I access my parameters via a custom config_param method:
# Method to supplement the app_config plugin. I want to crash early and be alerted if
# I have forgotten to define a parameter in an environment.
AppConfig.param(name) do
raise("No app_config param '' defined in environment , " +
"please define it in config/environments/.rb and restart the server")
end
end
RailsConf Europe 2007: Rails and the Next Generation Web
A sponsored presentation at the conference that I think stood out in how professionally it was executed and also touched on interesting topics was the one by Craig R. McClanahan from Sun. As I learned later, Craig has one of the most impressive track records I've seen in the Java community, being the creator of Struts and a contributer to a wide range of technologies including Tomcat and JavaServer Faces. In his talk Craig was urging Rails developers to move beyond the traditional three tier architecture of web applications. In plain english this means moving away from the classic scenario of a dumb browser talking to a single Rails application connected to a single database. Three developments in the industry today that are moving us away from this architecture are:
- Moving application logic to the client with JavaScript and transfering data instead of markup
- Server side mashups - refactoring a monolithic big app into multiple small apps
- Massively scaled applications - shifting vertical scaling to horizontal scaling
Craig was urging Rails plugin developers to avoid hard wired dependencies on ActiveRecord. Examples of plugins that succeed with this are acts_as_authenticated, make_resourceful, and paginator. Craig ended with three pieces of advice:
- Leverage duck typing to provide functionality without assuming an underlying base class
- Expose services with REST
- Think of your application as an internal mashup
The presentation slides are available online.
Ruby on Rails Deployment on FreeBSD
I did a Ruby on Rails FreeBSD deployment for a client the other day and I thought I'd share what I learned in an instructional format here. Previously I had mostly deployed on Linux (Suse, CentOS etc.) and I was curious to see what the FreeBSD experience would be like. I googled for instructions and immediately found the RailsOnFreeBSD page in the Rails Wiki. Other than that I couldn't find much Ruby on Rails and FreeBSD specific instruction out there. Note - most of the instructions in this post are not specific to FreeBSD but are generic Ruby on Rails deployment steps for Unix.
We were migrating from Windows to FreeBSD and the goal was to eliminate single points of failure. We settled on two application servers with FreeBSD 6.2 on HP hardware, both running the web and app tiers in the vanilla Rails deployment stack, i.e. Apache 2.2.4, mod_proxy_balancer, Mongrel cluster, and Mongrel. A load balancer external to the Rails system would load balance between the two Apache servers. The database we use is MySQL 5 and it sits on a separate server. The idea is to add another db server with some form of MySQL replication. We have yet to decide which replication to use and recommendations are welcome. For deployment we use Capistrano 2.
Rails Migration Gotcha: Forgetting to set active_record.schema_format to :sql
If you rely on any SQL not supported by Rails migrations such as foreign keys - don't forget to set config.active_record.schema_format = :sql in your environment.rb. Otherwise your test database will be out of synch with your development database. I really would prefer if Rails used the SQL dump format for setting up the test database by default as that would be more reliable.
The reason I came across this now was actually that I found what seems to be a bug with Rails migrations. Given this create_table statement in my migration:
create_table :users do |t|
t.string :username, :null => false
t.string :role, :null => false
t.timestamps
end
Rails will create this statement in the dump file:
create_table "users", :force => true do |t|
t.string "username", :default => "", :null => false
t.string "role", :default => "", :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
The difference is subtle but important to me. In the dump format Rails insists that the default value is the empty string. This is not what I want when I say that a column is not null, since with MySQL 5, even with SQL mode set to traditional, the column will then be defaulted to the empty string when someone tries to set the column to NULL.
RSpec Tip: Keeping Controller Specs DRY
I am not yet converted to the idea of testing/specing views in separation so I usually invoke integrate_views at the top of my controller specs. I also have a bunch of helper methods that I want to reuse across my specs. To encapsulate those needs and DRY up my specs I came up with this little method that I keep in my spec/spec_helper.rb file:
# Describe a controller the way we want to do it for this app, i.e. with
# views integrated and with certain controller spec helper methods available
describe controller do
include ControllerSpecHelper
integrate_views
block.bind(self).call
end
end
Rails Discussion: ActiveRecord vs SQL
Here is my Rails quote of the day, form a Rails mailinglist:
"When I was at the first RailsConf I was talking to someone about having used SQL for 15 years and that I was struggling with AR. At that moment someone grabbed me by the arm. I turned to look into the familiar face of Martin Fowler! He said to me, "If you know SQL that well you should just use SQL.""
The UPS Illusion of Guaranteed Delivery
My girlfriend was in a hurry to send 6 copies of her thesis (a 1 kg package) from Stockholm to Copenhagen. She sent it on wednesday and it said on the UPS website that the package would be delivered "no later than" end of office hours on thursday, i.e. the next day. My girlfriend chose UPS because she wanted to be sure the package would be delivered on time. Her examination is next friday and the printed thesis needs to reach the university well in time before that. For the delivery my girlfriend paid about three times as much as she would have paid the postal service. This means she paid about 600 SEK over the 200 SEK that the postal service would have charged.
Well, it's now Saturday and the UPS can't really tell us why the package has not been delivered yet. According to the UPS tracking service, the package has been sitting in the destination city for two days without any delivery attempts having been made. UPS cannot explain why. It's like the package is lost in limbo. When my girlfriend calls to explain the problematic situation she is in and asks for help she is not met by service mindedness or understanding. She is met by accusations and unfriendliness. They say she should have known to choose an even more expensive form of delivery for about 1000 SEK to be guaranteed the delivery date. They also say she misinterpreted the conditions. Apparently, when the UPS say "latest delivery" on a certain date, that means something else to them than it means to most people. The latest delivery date isn't really a part of the contract. There is no money back and there is no excuse when delivery is made at some arbitrarily later date.
Everybody knows the Postal service doesn't make guarantees about the delivery date. There is mutual understanding about the contract. A lot of us probably live under the illusion though that UPS makes guarantees like that. Well, it's time to wake up and smell the coffee because they don't. This naturally leaves the question open as to what it is that motivates the steep UPS prize premium?
RailsConf Europe 2007 Notes: Dave Thomas Keynote
The Keynote by Dave Thomas was to me the most inspirational and profound talk at the conference. I have the greatest admiration for Dave Thomas. I can't offer Dave's entire talk in every detail here, nor do I know if that would be appropriate. What I have is key fragments from sentences in the talk presented sort of in the format of a poem. Hopefully someone who didn't hear the talk can make something out of it. Enjoy...
RailsConf Europe 2007 Notes: BDD, RSpec, and Story Runner
For me, RailsConf Europe 2007 in Berlin started out with a BDD/RSpec tutorial with David Chelimsky, Dan North, and Aslak Hellesøy. In the first part of the tutorial the presenters gave an overview of the theory and background of Behaviour Driven Development (BDD). All I can offer here is an unstructured list of keywords and notes that I was able to scrabble down while listening:
Domain Driven Design. Having engineers and business people speak the same language. SOA Using contracts that say: this is all I'm going to do for you Focusing on outcomes and reducing features The Agile Alliance The false civil engineering analogy of building bridges SCRUM Problem: programmers deleting tests they don't understand Format for user stories: index cards The given-when-then format Book: "User Stories Applied" All features should be traceable to a persona Keeping personas in FaceBook is a fun idea Example Driven Development
RailsConf Europe 2007
I sometimes tell people how the best decision I made in life was to get into salsa dancing. It was how I met my girlfriend Janne and it has simply been countless hours of fun and magic. It has also made my confidence and social skills grow which has helped me in other areas of life as well. Visiting RailsConf Europe 2007 has to rank right up there with one of those great decisions and it feels like a milestone. It gave me inspiration on many levels and I enjoyed it more than I've enjoyed any previous conference. What made all the difference for me was the networking. Meeting so many friends and colleagues from the past and having the opportunity of talking to thought leaders in the Rails ecosystem is just amazing. Now that three intense days of excitement is over I miss it already.
Rails Deployment Tip: FiveRuns.com
If you are a Rails developer you have probably heard of FiveRuns with their RM-Manage service offering, providing monitoring and statistics for your Rails apps. I've wanted to try the service for quite a while and today I finally got around to doing so on a production server at one of my clients. All I can say is you have to try it to believe it - it's a super slick, user friendly, and powerful service.
There was a minor glitch in the setup. The FiveRuns monitoring client didn't find our Rails app since it was in a non-standard location. I entered the FiveRuns Campfire chat room and got live support within minutes and quickly had a resolution. Once we installed the FiveRuns Rails plugin and restarted the server we had live Rails response time and profiling statistics within minutes. The first impression I have of the service is just great. We will be evaluating the service over the next 30 days and I'll post here any new findings we make. Barring any really big issues coming up, I consider FiveRuns to be a huge value add to the Rails ecosystem, almost like a milestone, and I will be recommending it wholeheartedly to all my clients from now on.
Rails Tip: Nested Layouts
This is just a feature I always wanted in Rails - nested layouts, i.e. the ability to have one master layout (application.rhtml) for your whole site, and then to have layouts within that that differ from section to section. Today I stumbled across this hack - just a single helper method that allows the use of nested layouts. Sweet. I'll re-post the code here:
end
Rails and Transactional Saves
Correction: ActiveRecord::Base#save does use transactions in Rails by default, see Jarkkos comment.
In Rails, models are not saved within a database transaction by default. This means that if you are updating some other record in a callback like after_save then the record will still be saved even if an exception is raised in the callback. If you care a lot about data integrity this is not satisfactory. One way to remedy the problem might be to simply override the save and save! methods in your model like this:
That's the workaround I'm going to use for now.
Ruby as a Bash Substitute
It's really nice how Ruby can liberate me from languages that I don't like as much and am not as fluent in, such as Bash, and Perl. Today I was tearing my hair over a little bash database backup script that I needed to write until I realized I'm much better off writing it in Ruby. Bash translates amazingly easy to Ruby with the syntactic similarity, the availability of the back ticks (`), and libraries such as FileUtils. Once I switched to Ruby the backup was solved faster and with more confidence.
This experience got me thinking of the expression "when all you have is a hammer, everything looks like a nail". If I'm not mistaken the Pragmatic Programmers use not only the advice "use the right tool for the job" but also "master your tools". There is a pretty obvious contradition between those two pieces of advice. It's important to not be afraid of picking up new tools, but it's also important to not underestimate the cost involved in learning the new tools in depth. Some tools I seem to pick up and learn quickly, others I can struggle with for years without ever really feeling confident or happy with them.
Rails Testing: Switching from Test::Unit to RSpec, What are the Advantages?
It's been a couple of months now since I switched from Rails built in Test::Unit framework to using RSpec and I've realized that RSpec has already brought some major advantages for me. Just to clarify, I still run all my old Test::Unit tests and I still write Test::Unit integration tests to complement my specs. The Rails testing framework and RSpec can run nicely side by side through the rake command. Here is what RSpec has given me:
- It has made me more spec/test driven. Now for the first time I mostly write the specs before I write the code. I've found that if I postpone writing tests the test coverage will suffer a lot. A lot of times the tests don't get written or I end up writing just the most rudimentary tests. Tests/specs need to be written while you are in the process of writing the code. That's when you have the domain logic loaded into your head. Push it off for a few days, or even just half a day, and you will need ramp up time to load the logic into your head again. A lot of times, there is no room for such ramp up time.
- Specs are more accepted and appreciated by clients than tests. I have clients who appreciate and really understand specs (in Word documents or PDFs) but are not in the tradition of test driven development. It makes more sense to them that I spend time writing specs, especially since I can show them the nice green HTML spec doc that RSpec generates. Clients who want to control and monitor the details of what you do will like your specs even more. Specs communicate better than tests.
- RSpec is syntactically nicer than Test::Unit and thus it reads better.
Another realization that has come to me lately, is that yes, regardless of what code you are writing TDD/BDD is a good idea. However, if you are writing any kind of API or framework that is to be reused across several applications, tests/specs and well defined interfaces are almost a necessity. If you don't have them, you might be better off opting for no reuse. Reuse is the holy grail of software development, and it's hard.
Rails Gotcha: The Contract of 1.month.ago
At first glance the contract of the beautiful 1.month.ago construct in Rails is maybe obvious. However, what should it return if the current time is the 31st of March? Remember that february only has 28 days. Well, according to Rails 1.2.3 the answer is 1st of March, but according to Edge Rails the answer is (and I agree with Edge Rails here) 28:th of February.
The 1.month.ago issue is something that bit me and that you should be aware of. So with Rails 1.2.3, if you want to be sure to get the beginning of the previous month, you need to say 1.month.until(Time.now.beginning_of_month). At least that's the workaround I'm using now.
Rails Deployment: Rapid Setup with the Machinify Gem
I used the Machinify Gem by Bradley Taylor (Mr RailsMachine) the other day to install a Rails stack on a piece of Ubuntu 7 Xen VPS and I must say it worked really nicely. The Gem uses rake to install the common stack of MySQL 5, Apache 2 with mod proxy load balancer, Mongrel Cluster, and Mongrel. The gem also takes care of installing all the obscure Debian packages needed that you don't even want to know about.
There is a small glitch currently in the Machinify gem in that the Mongrel Cluster version is hardcoded in its mongrel.rake file. Once I corrected that and ran rake stack:install again it worked like a charm. Related to this issue, I was trying to find out how to fetch the current version of a Gem and the best I could come up with was:
# On the command line:
gemwhich capistrano
=> /usr/local/lib/ruby/gems/1.8/gems/capistrano-2.0.0/lib/capistrano.rb
# In Ruby:
`gemwhich capistrano`[%r{-([0-9.]+)/lib/[^/]+$}, 1]
=> "2.0.0"
If you are on Cent OS you should check out RubyWorks by ThoughtWorks (the installer, not the singer).
When the Swedish Post is Vastly Superior to UPS/TNT/DHL et al
It's a curious thing. When it comes to politics I'm traditionally a quite liberal and market oriented person. I usually get annoyed when I find areas of society were competition and free trade are restricted and I'm not a great fan of state monopolies. However, when it comes to delivering packages the swedish state owned postal service are offering me a service that is vastly superior to that of TNT/UPS/DHL.
Why is that? Well essentially it's because the postal service uses the approach of delivering to a pick-up place in my area, usually a kiosk or store of some kind, usually with generous opening hours. This has worked like a charm for me so far. Private delivery firms (UPS et al) try to deliver to me in person. Delivering a package directly into a persons hand sounds like a great service, but it's a bit harder than it sounds. I'm not saying delivering to someone in person cannot be made to work reliably, however, the way it's currently implemented there are some serious issues. For a personal delivery to be a success, it requires that the delivery person and the recipient be in the the exact same place at the same point in time, so that the package can be handed over and signed. Now in this day and age with GPS systems and mobile phones and the internet and all that, the problem sounds solvable. In a lot of cases, at least for me, delivery is failing though. For a taste of other peoples experiences, see Lars Pind wishing for a doorman or this swedish discussion saying postal service seems like a luxury. Some reasons personal delivery is failing might be:
- No appointment. The delivery company will not give you a notice about when they are showing up, they just show up and knock at your door. After, the first delivery attempt (for me, always a failure so far), they may or may not be able to tell you what day they will try next time, although they probably can't give any guarantees, and they certainly can't give you an exact time.
- No communication to prevent failure. If the delivery person shows up and you are not there, he doesn't (from my numereous experiences with TNT/UPS/DHL) call you. He will just leave a note saying delivery failed. End of story.
- No direct communication after failure/inefficient organization. I have never been able to talk directly to the driver to make an appointment/change address etc. In the cases where I have talked to the driver and made agreements they have not been met (delivery was not made in the agreed time interval). I have then been corrected and informed that I need to talk to customer support about deliveries, never with the driver directly. In the case of Apple Store it's even worse. You cannot talk directly to the delivery company and change an address, you have to go via Apple Store. After the first failed delivery to my home I called Apple same day and changed address. However, since Apple needs a full day (!) to communicate the address change to UPS, the second delivery attempt the following day was again made to my home address instead of the work address that I changed it to.
- No convenient pick-up/fall back. If delivery fails three times you have to pick up the package at some far off and inconvenient industrial area outside your town (Stockholm in my case) that has restricted opening hours and which would basically require you to make an excursion there and take time out from your day job. If you cannot make it to pick up the package it is returned to the sender and the delivery failure is completed. Yes, this has happened to me.
RSpec Gotcha: Incompatibility with Engines 1.2.0
If you are trying to get RSpec to work in a Rails application that uses Engines you may find that RSpec errors out when you try to run it. It seems the reason is related to the file testing.rb in the engines plugin. If you comment out the line that requires that file (in vendor/plugins/engines/init.rb) you will have worked around the issue:
#require "engines/testing" if RAILS_ENV == "test"
Rails Deployment: A Little Checklist
After having fought myself through a couple of Ruby on Rails deployments, it seems there are a lot of little annoying things you need to remember. Some of them are not super critical, at least not in the short run, but they can grow into bigger problems over time. Here is the list:
- Backups and failover. I put this first because I think it is so important. What happens if a hard drive or server crashes? Can you fail over to a different server. How long will this take? Are your backups working? Have you tried doing a recovery? Backing up a MySQL database can be as easy as doing mysqldump from a cron job. You can then use rsync to copy those backup files over to a different server. Remember to also backup any data files that live outside the database. such as media files that you have symlinked under the public directory.
- Documentation. Document how your server is set up and configured. What programs are installed where, where are the config files, how do you restart the servers? This is useful since human memory is short and unreliable and also since we may need to introduce new sys admins and developers to the project.
- Monitoring. You should use an external monitoring server that is entirely external to your system that alerts you by SMS or otherwise when your server is down. You may add to this an internal job that pings your server and restarts your mongrels if they are not responding. You should also monitor disk space and CPU and other parameters of your server. The FiveRuns.com service looks promising when it comes to monitoring.
- Setting the clock. On two recent deployments (Suse and Fedora) I've had issues with the clock being wrong. You can schedule the ntpdate command from a cron job to sync your clock.
- Clearing out old sessions. Set up a cron job to delete old records from the sessions table. I recently worked with a Rails app where this had not been done and the sessions table consequently had millions of rows.
- Clearing out old Capistrano releases. For some reason Capistrano defaults to keeping all releases forever. The fix is easy - add an after_deploy callback that invokes cleanup.
- Log rotation. If you don't rotate your log/production.log file it can quickly grow into hundreds of megabytes or even a few gigabytes. I don't know if that slows your server down, but such log files are not very easy to work with. You can use logrotate to do the rotation, see the Rails Wiki. Here is a sample from etc/logrotate.conf:
# Rails logs: /usr/local/rails_apps/platform/shared/log/*log { daily missingok rotate 100 compress delaycompress notifempty copytruncate create 0666 rails www }
Rails Deployment: Dealing with 404s
You probably use Jamis Buck's Exception Notifier plugin to be notified by email of exceptions thrown on your production server. Typically though you don't want to be notified of 404s, at least not when they are caused by an RSS reader requesting an RSS feed that no longer exists, or some spam robot or search engine fetching URLs fetching URLs no longer supported. When the Exception Notifier plugin catches an exception and it is one of ActiveRecord::RecordNotFound, ActionController::UnknownController, or ActionController::UnknownAction, it figures that it is a 404. By default then no email is sent and instead the method render_404 is invoked which in turn renders public/404.html. This is quite appropriate. Unofortunately though you can't trace in the log what the request looked like that caused the 404. The other limitation is that if no route matches a n incoming request, then an ActionController::RoutingError is thrown, yielding a 500 and an exception email. To get consistency in how 404s are dealt with, i.e. make sure they are always fully logged and tracable, but never cause email notifications, I override the render_404 method from the Exception Notifier plugin in my ApplicationController:
I then add a catch all route at the end of my routes.rb file:
map.connect '*anything', :controller => 'application', :action => 'render_404'
Rails Deployment: Looking up User ID from a Session ID
Suppose you get an exception notification and you want to know which user was browsing the site when the exception occured. The exception email has the HTTP_COOKIE header with the _session_id. Assuming you are using ActiveRecordStore and that you store the user ID in the session, you can look it up with an incantation like the following:
CGI::Session::ActiveRecordStore::Session.find_by_session_id('c5934a07b12ae9d90cf6be4e7d48b361').data
I keep forgetting the module path to the Session class so I thought I'd post this note. The corresponding Rails file is active_record_store.rb in ActionPack.
Rails Testing: Quoting angle brackets in assert_select
This is just a little detail but today I rediscovered the way to quote angle brackets in argument values in HTML::Selector expressions in your assert_select commands (and corresponding response.should have_tag commands in RSpec). I tried using back slashes before I realized that I needed to enclose the argument value in single quotes. Notice the nested angle brackets ([ and ] signs) below:
it "Should have the allow_public_messages radio buttons on the edit contact info page" do
get :edit_my_options
response.should be_success
response.should have_tag("input[name='profile[allow_public_messages]']")
end
RSpec RAILS_ENV Gotcha
I've had issues with RSpec running in both development and production environment which certainly isn't what I want and it seems others have had the same issue. The resolution is simple, force RAILS_ENV to be set to "test" in your spec/spec_helper.rb file.
Teaching the First Rails Course in Sweden
In the last week of June I had the privilege of teaching the 5 day Ruby on Rails course I Go Ruby at the wonderful Ulvhäll Herrgård in Strängnäs, just a one hour train ride west of Stockholm. To the best of my knowledge it was the first Ruby on Rails course held in Sweden, and this of course made us feel special. The relaxed location and format of the course - a summer camp with a small group of students - also worked well to create a pleasant atmosphere.
I have previously taught evening courses in Ruby on Rails at companies like Great Works and Connecta, but this was the first time that I got to teach a longer course. We had a small group of eight students with very different experience levels and this of course posed quite a challenge for me. I found that my exercises were a tad too difficult and also that I was possibly covering too much material, at least for the less experienced students.
I developed all of the course material myself - over 300 slides plus exercises - a major undertaking consuming about a month of full time work. The basic contents of the course roughly corresponds to the Agile Web Development with Rails book, but important additions were made in areas such as Ruby, testing, and Rails 2.0.
There are tentative plans to make the course material available online at an affordable price. I am also hoping to offer two day Rails courses in Stockholm during the autumn. Eventually I'd like to offer a two day introductory course, and then one or more advanced one or two day courses on advanced topics. Stay tuned for announcements on this, and let me know if you are interested.
Rails Testing: The form_test_helper Plugin
I've been using the form_test_helper plugin in my integration tests lately and I think it's a great way to increase test coverage of your views. The form_test_helper uses the assert_select CSS-like selectors and makes it easy to click links and submit forms and thus simulate something that comes pretty close to manual testing in the browser, unless of course you need to test AJAX functionality, in which case you need to look at Watir or Selenium. Here is some sample code:
Rails + RSpec: First Impressions
I started using RSpec on a client project yesterday and I've been pleasantly surprised by how easy it is to get started with and how nicely it plays with Rails. I now have a handful of specs under the spec directory and they get run by rake automatically right after my tests. I can also use rake spec:doc to generate the documentation of my specs:
Rails Testing: Checking for Broken URLs in Links, Images, Forms, and Redirects
I've added the ability to check for broken URLs in links, images, forms, and redirects to my Http Test plugin. How does this work? Well in your controller and integration tests an ApplicationController after filter is used to extract all the URLs on the page. For each URL we make sure it can be resolved to a route, a controller, and an action and/or template. URLs to files under the public directory will be recognized. The same check is made for redirects.
I haven't used this URL checking in production yet, but I'm hoping it will help me prevent mistyped URLs and params.
Bug Tracking can be Bug Prevention
I've been deploying a couple of Rails applications for a client lately and things have been running surprisingly smoothly and there have hardly been any issues at all. In one of the more complex applications though a few bugs slipped through my testing net and were caught by the excellent Exception Notifier plugin by Jamis Buck. Now, the question is, how do we typically deal with bugs as programmers? If the bug is in production we rush to fix it, push the fix out, and hope not too many people noticed it, and then try to forget about the whole thing. What we should be doing though is ask ourselves why the bug occured in the first place, how we can categorize the bug, and what kind of process and/or tests can we put in place to prevent similar kind of bugs from happening in the future. Bug tracking is a gold mine for figuring out how we can raise the quality of our software and reduce the bug rate in the future. This may be common sense, but how many of us actually use this opportunity?
Rails Gotcha: No ordering of ActiveRecord after_save callbacks
I am setting up tagging and searching for a demo application and I am combining the plugins acts_as_taggable_on_steroids and acts_as_ferret. I was using the following setup for my users:
However, that didn't work. When making a change to my tags I had to save the user twice for the search index to be updated. Why? Because the acts_as_taggable_on_steroid after_save callback (save_tags) runs after the acts_as_ferret after_save callback (ferret_update). Maybe the callbacks are invoked alphabetically, I don't know. Anyway, there is no explicit ordering and no after_after_save callback...
The workaround in this case was easy because of the virtual attribute tag_list that gets set before the save:
Now we are always using the latest tag list when we update the Ferret index. Problem solved.
Rails Extension: ActiveRecord::Base.find(:last)
I'm at the Big Nerd Ranch Ruby on Rails bootcamp in a monastery outside Frankfurt right now having a great time. One of the students asked if you can say ActiveRecord::Base.find(:last), which you can't. The value of the first argument is typically :first, but you can't use :last. Just for educational purposes I decided to change that (it's probably not something I would use in production):
ActiveRecord::Base # Make sure class is loaded, which it probably is by now anyway
if args.first == :last
options = extract_options_from_args!(args)
find_without_last(:first, options.merge(:order => " DESC"))
else
find_without_last(*args)
end
end
end
end
end
I guess most interestingly this code illustrates how to alias a static method.
Abandoning Textilize for Comment Formating
I've been using the textilize helper for comment formating in this blog. The textilize helper uses the RedCloth gem, which supports both Textile and Markdown syntax and it's pretty complex code with lots of hairy regular expressions. I noticed that the textilize helper will let arbitrary HTML tags through so it can mess up the HTML of a page. Seems like a vulnerability, and it's even more a problem for me since I like to be able to HTML validate my pages. I decided to stop using textilize for comments and instead use the following simpler and tighter formating:
Capistrano is not just for deployment
I'm appreciating Capistrano more and more. It has such an elegant and powerful API, and you can use it for pretty much any commands you need to execute on one or more of your servers. Here is a simple example. For the development of this weblog I found myself quite often wanting to copy the production data to my development server. Of course at first I was doing this on the command line. The next natural step would have been to write a bash script, but these days I tend to write most of my scripts in Ruby. Rather than stick a small Ruby script under the Rails script directory I'm starting to create Rake tasks under lib/tasks though. One advantage of having scripts as Rake tasks is that you can cagalog them nicely with "rake --tasks". Here is the Rake task:
Rails Deployment: Running Tests in Production with Capistrano
Murphy teaches us that if something can go wrong it will. Given all the differences there are between my development and production server - operating system, web server, database, gems etc. - I can't really feel comfortable deploying my application unless I have first run my test suite not only in development but also in production. Thanks to the beautiful and powerful API of Capistrano this is a walk in the park to accomplish:
Rails Deployment: Check MySQL Charset on Startup
The UTF-8 support got a lot better with the Rails 1.2 release but since Rails has evolved so fast many of the instructions you'll find on the web are dated. Rails will now default the Content-Type of its responses to UTF-8 so you don't need to set that yourself in an after filter.
When it comes to configuring MySQL to use UTF-8 there are at least three pieces to keep track of - the database encoding, the client encoding, and the connection encoding. For my project it was enough to set "default-character-set=utf8" in the MySQL my.cnf config file (takes care of the database part) and set "encoding: utf8" in database.yml (takes care of the connection and client).
Rails Testing: Making assert_select XML safe
I've been using the new assert_select command extensively in my tests lately and it's a wonderfully powerful tool - a huge improvement over assert_tag. The only annoyance has been the warning messages "ignoring attempt to close form with link" that have been polluting my test output. The other day I decided to track the source of the messages and I discovered that others had already complained about them. I found that the root cause of the problem lies in the HTML::Document and the HTML::Tag classes that ship with Rails. More specifically the problem is in the HTML::Tag#childless? method that auto-closes HTML tags such as img, br, and hr.
Rails Tip: Declare your Gem versions
Many Rails applications have external dependencies in the form of RubyGem libraries. The problem with such external dependencies is that you need to make sure that you have the right gem versions installed across all your servers, i.e. development, staging, and production. The probably easiest way around this is to freeze gems into your source tree. For rails you can use the Rake task "rails:freeze:gems" and you can freeze other gems by using Rick Olsen's Gems plugin.
At the end of the day there may still be external gems that you depend on, maybe because they need to be compiled, or maybe because you want to avoid your application source tree growing too big. You can fix the version of Rails using the RAILS_GEM_VERSION constant in environment.rb. For other gems you can fix the versions by adding statements like the following to your environment.rb:
gem "RedCloth", "3.0.4"
RubyGems: LoadError in nested require no longer causes silent failure with Ruby 1.8.5
I had a Rails application where I was trying to reference a helper that didn't exist. What happens in Rails when you try to use a class that you haven't required is that the Ruby callback method const_missing is invoked. Behind the scenes Rails will then try to require the class for you. What was happening was that Rails was trying to require the helper file, but the helper file didn't exist, and so that failed. The problem wasn't so much that it failed, but that it failed silently - there was no error message to be seen anywhere.
Rails Gotcha: ActiveRecord::Base#update_attribute skips validation
It's something others have been bitten by. Although we should know better (why didn't we read the API docs carefully?) we naivley thought we could safely update a single attribute with the update_attribute method. When you invoke my_precious_object.update_attribute(:important_attribute, erroneous_value) then Rails will happily skip validations and save all attributes to the database. This is unlike the sister method update_attributes which updates several attributes and does use validation. I'm sure validation is skipped for a reason but I'm not sure what that reason is. In particular I'm not sure if that reason is strong enough to warrant the inconsistency and the risk of skipping validations.
Nokia N70 - 11 Clicks Away From Usability
I've had the privilege and misfortune of using a Nokia N70 3g camera phone over the last couple of days. My first impression of the phone has been mostly a series of frustrations but most importantly a lesson in the importance of usability and delivering on the basics. To quote 37Signals:
The basics are the secrets of business. Execute on the basics beautifully and you’ll have a lot of customers knocking at your door. Cool wears off, usefulness never does.
Swedish Rails Article and Course
I was very pleased to see my Ruby on Rails article published yesterday by the IT education company Informator here in Stockholm. The article is in swedish and has a title along the lines of "Rails Brings Back the Joy of Working". It was published in the slick offline paper called "Format" that Informator distributes and is also available online.
I there is enough interest (fingers crossed) I will be giving the Ruby on Rails course for Informator here in Stockholm later this year. Needless to say, I'm very excited about that opportunity!
Rails Tip: Use the Unit Tests as Documentation
If you are looking for authoritative answers on how to use the Rails API, look no further than the Rails unit tests. Those tests provide excellent documentation on what's supported by the API and what isn't, how the API is intended to be used, the kind of use cases and domain specific problems that drove the API, and also what API usage is most likely to work in future versions of Rails.
Let me give you a concrete example. In my code I had the equivalent of a Tag class connected through a Tagging join model with two different classes, say Post and Comment. What I was trying to do was say in the Tag class something like "has_many :posts, :through => :taggings, :source => :taggable" or maybe "has_many :taggables, :through => :taggings". However, Rails refused to let me do this and kept throwing an ActiveRecord::HasManyThroughAssociationPolymorphicError. At this point I wasn't sure if I was misusing the API or if I had come across a Rails bug (although I knew that the former was more likely).
I turned first to the most authoritative documentation such as the API doc, the Agile Web Development with Rails book, and the Rails Recipes book (Recipe 22: "May-to-Many Relationships with Extra Data", and Recipe 23: "Polymorphic Associations - has_many :whatevers"). However, none of that documentation seemed to address my specific problem. Through Google I found the wiki page HowToUsePolymorphicAssociations which did address my problem. However, the solution there seemed clumsy, and I didn't know how authoritative and up-to-date it was.
It's usually a good idea to debug a problem by following the stack trace into the Rails code and try to figure out what's going on right there. However, in this particular case I found that reading the clean but complex ActiveRecord code didn't give me any easy answers. It then hit me that what I really should be doing was read the ActiveRecord unit tests. Luckily for us application developers David and the rest of the core team has provided us with a nice suite of unit tests. Once I looked at the join model test case the whole picture cleared up - the fog lifted and there was bright sunlight. Laid out right there in front of me were excellent and authoritative examples of how to use ActiveRecord associations with classes such as Tag, Tagging, Post, Comment, Category, and Categorization. I also found the particular assertion that said that the ActiveRecord::HasManyThroughAssociationPolymorphicError should be raised in my case...
One could speculate that Rails really should provide a way to do has_many through a polymorphic association, and surely there is a way to hack Rails into doing something like that. What's important though is that now I know that the approach is not officially supported or intended and that's fine with me. I like to stay on the unit tested golden Rails path and I recommend you to do the same.
Good and Bad Programmer Practices
As programmers we have a tendency to take shortcuts that come back and bite us eventually. It is very popular to write overly complex system that seem sophisticated and make us feel smart and special. The irony is that a lot of times we think our code is so simple and obvious that it doesn't need automated tests. Here is a list of programming practices to consider:
- Failing silently / swallowing exceptions. Failing silently can make debugging a nightmare since the source of failure is not revealed. Don't do it.
- Not using version control. This shortcut is suprisingly common. Being able to roll back your code to an earlier version is a great debugging technique.
- Not writing commit comments. Let the commit comments serve as documentation. You may also want to keep a high level change log where your customer can review changes.
- Including several unrelated changes in one commit. This makes debugging and code review more difficult and makes it harder to see each change in isolation.
- Not commenting your code. Are you breaking conventions and being particularly clever? Explain what you are doing in a comment.
- Leaving commented out code or unused code lying around in your code tree. This makes the code more difficult to read and understand. Keep the code clean.



