This is the HTML version of the handout from my acts_as_conference presentation. If and when they post the audio of the talk, I’ll upload my slides; they wouldn’t make much sense without it. —Luke
Software is hard. Why? Fred Brooks separated software development into essence (the conceptual modeling) and accident (actually building it). As building software has gotten easier due to better tools, the essence has remained difficult. One of the best things about Rails is that it lets fewer people get more done. This helps because adding more people to a project doesn’t scale. But Rails teams still run into problems. What are they and how can we make Rails development easier?
Migrations
Having a way to evolve your database schema baked into the framework is a huge advantage. But they are also a source of pain for Rails teams.
Migration conflicts happen when two people check in a migration with the same number, and then everyone on the team has to manually fix their database. You can fix this with a pre-commit hook or use a plugin that allows duplicate migrations.
I call the seemingly inevitable tendency of migrations to stop working migration decay. You can fight it, if you try hard enough (continuous integration is your friend here). Or you can give up:
Note that this schema.rb definition is the authoritative source for your database schema. If you need to create the application database on another system, you should be using db:schema:load, not running all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations you’ll amass, the slower it’ll run and the greater likelihood for issues). —db/schema.rb
Seed data
Migrations seem great for loading data, because they run automatically. However, touching your models in your migrations increases the chances that they’ll break. Whatever you do, don’t use db:fixtures:load for this—that’s for your tests.
Solution: use a rake task that loads seed data. Jeffrey Allan Hardy wrote a nice task to do this using fixtures. I don’t like fixtures for loading seed data because they don’t validate data, so I use db-populate by Josh Knowles , along with ActiveRecord::Base.create_or_update:
1 2 3 4 5 6 7 8 9 |
def self.create_or_update(options = {}) id = options.delete(:id) record = find_by_id(id) || new record.id = id record.attributes = options record.save! record end |
Managing third-party code
The best way to reduce the difficulty of writing code is to not write it. Ruby’s great library of gems and the hundreds of Rails plugins help us write less code. However, managing third-party code is a pain. If a developer installs a gem, everyone else needs it. We used to have problems with this, but now we vendor everything. Dr. Nic’s gemsonrails makes vendoring gems easy—except for gems that must be natively compiled.
Don’t install plugins with svn:externals unless you want to rely on Joe Bob’s Random Subversion Server to be up when you’re deploying. Don’t edit plugins, unless you’ve got a good SCM or use piston. Even then, it may be better to keep your changes as monkey patches in the lib directory.
Security should be on by default
In some areas, you have to screw up to get insecure code. For example, it’s easier to use secure SQL queries than not. And cross-site request forgery protection is baked in.
Not so with cross-site scripting (XSS). You have to remember to use h() in your views. If you forget once, your site could get hacked. My xss_terminate plugin solves this by stripping HTML from all strings when the model is saved (you can override this for attributes that need HTML). We also use Erubis to auto-escape HTML in views.
(Other XSS plugins include: Cross Site Sniper sanitize_params, SafeERB, and xss-shield. If you don’t like xss_terminate, try one of the others!)
Mass assignment code like LineItem.new(params[:line_item]) may set attributes (like total_price) you don’t anticipate—and a malicious user could end up getting charged $0.01 for a MacBook Pro. Protect your attributes with attr_protected. Better yet, use attr_accessible to create a white list of explicitly allowed attributes. Best yet, do this for all models by default. We added this code in an initializer to protect all the attributes:
ActiveRecord::Base.send(:write_inheritable_attribute, "attr_accessible", [])
Then we set attr_accessible to override the protection. This will wreak havoc on an existing code base, but I think it’s a good policy for new sites.
Source control management: the heart of your project
Code is communication, and the way code has changed over time is communication, too. I rely on annotate, log, and diff extensively when figuring out why code works the way it does. To help your team members, you must write informative log messages: what changed, why, and the bug number (if applicable). Commit atomic changes; don’t patch bomb a bunch of unrelated code.
Bug tracking
The bigger your team, the more important a good bug tracking system is. What makes a good bug tracker? The features I look for are: workflow with open, closed, and resolved states; e-mail integration; SCM integration; and shared saved searches. The most important feature is ease of use, because if it’s not easy, people won’t use it.
Continuous integration
Continuous integration (CI) builds your software every time someone makes a change. CI runs tests, but more than that it ties everything together. It simulates a deployment: checks out your code, creates the database and loads the seed data, and ensures all the necessary libraries are there. If you broke something, CI will let you know right away. CI takes awhile to set up and may be overkill for small teams, but on larger teams, CI is especially helpful.
Does it matter?
The practices above smooth over rough patches, automate processes, and manage communication. For the most part, they attack the accident of Rails development. But the majority of what makes software hard is figuring out what to build: the essence. Fred Brooks argues that to make real improvements in software productivity, we must attack the essence. To get at the essence, do what you can to limit complexity: use existing open source or packaged software; do less (a la 37Signals); split complicated projects into smaller pieces.
Iterative development
Iterative development is the most important software development technique. You can write tests all day long, but if you’re building the wrong thing, they won’t help you. With iterative development, your understanding of what you are trying to build grows with time and feedback from the customer (if you aren’t getting regular feedback, it’s not iterative). There is a difference between incremental and iterative. Iteration is the process of continuous refinement; incremental is building in stages. We try to do both: build smaller pieces, and iteratively refine the software.


Great stuff Luke! I think you hit this on the head, and much of it is equally as important in a non-Rails project.
Thanks, Jamie! I made that point more explicitly in my talk (the first part was about Rails annoyances, and the second part was about more general stuff) but this is just the handout. The conference organizers promise they’re going to post the audio and slides (and maybe video) soon.
I’m looking forward to the audio and video – this is a topic that doesn’t get enough discussion (except, perhaps, the migration issues). Thanks for summarizing your talk!