6197 comments
2357 subscribers
Follow me on Twitter (@sachac)
Subscribe! Feed reader E-mail

On this page:

Rails experiences: Things I learned from project O

Rails is awesome. We built a workflow/reporting system for ~120 users using Rails 3. My part of the project came to about 468 hours, or roughly 60 workdays (~ 3 months), and I worked with another developer who also put in around the same number of hours. We worked with a graphic designer, a CSS/HTML developer, a tester, a project manager, and the client, and we put together a surprisingly large set of functionality.

It’s amazing how quickly the site came together. I built a simple prototype to help the other developer get started with Rails, and we fleshed it out with the client’s input while waiting for the creative design. I started with web_app_theme so that we could have a decent-looking interface for starters. When the client approved the graphic design, another developer sliced it up into HTML, CSS, and images for us. I took those, converted them into HAML and ERB, and we were off and running. Every weekly sprint meant a chance to show off useful functionality and get feedback. It was awesome.

We were initially worried that building all the UIs from scratch in Ruby on Rails would mean taking up more time because we couldn’t use CCK or Views to quickly throw everything together. It turned out that HAML, partials, and semantic_form_for made the forms and reports easy to do. Filtering reports was straightforward with ActiveRecord and scopes. Because we built the screens ourselves, we didn’t have to fight with Views or CCK for the last 20% of tweaking, and I didn’t have to kludge any SQL queries (yay, no views_pre_execute!).

I was working on a Drupal project shortly before this, and I spent some time supporting the Drupal project during this one. Rails made my brain much happier. I felt that I could organize my code more cleanly, and I could test it more thoroughly, too. I didn’t have to fight so much with other people’s modules or themes. I like Drupal, and I’m still looking forward to doing more projects with it. But I wouldn’t mind working on more Rails projects, and I’m glad I’ve got Quantified Awesome as a personal project.

Drupal does many things better than Rails. Drupal modules tend to be more mature and better-documented, and it seems like there’s been more work on scaling Drupal. Internationalization is also more straightforward in Drupal, although Rails I18n is easy to use once you’ve gotten the hang of it. Drupal module dependencies seem a little easier to sort out, too. But Rails is fun!

Tests will keep you sane. This was the first project where we invested in developing a large suite of automated tests. We used Cucumber for high-level tests and RSpec for everything else. The tests caught many regression errors we might have otherwise missed. Test-driven development was fun, too, because the tests gave us tangible progress and simplified checking.

There were times when I gave in to the temptation to commit without running the tests, and I almost always regretted them. (Particularly after friendly finger-wagging from the other developer!)

Issue-tracking rocks. We’ve liked using Rational Team Concert in the past. Getting an externally-accessible instance was complicated, so I set up a Redmine issue tracker as soon as we started the project. We used Redmine to plan work, track bugs, and even collect feedback from the client. As of the time of writing, we had created 766 items and closed 683 of them (89%).

We started with story points, but didn’t end up continuing with them for the rest of the tasks. When we needed to prioritize, we estimated the hours required for each task in order to help the client decide. That worked out quite well. I haven’t tracked item-level time spent, but that seemed to be roughly around my estimates.

I now estimate more time than I used to, because I’ve started factoring in both writing and running tests. It’s a little strange being the pessimistic estimator instead of the optimistic one, but it’s good for the project.

Selenium is great for screenshots, too. Not only is Selenium good for automated browser-based testing of web pages, but it’s also a handy way to capture screenshots for documentation or demos.

Lotus Symphony and Microsoft Word don’t get along. We wasted a few hours trying to update the user guide with the new screenshots, only to find out that the PDF still got screwed up because I didn’t have the fonts the client used. Those fonts were part of Microsoft Office and weren’t on my system. The client took care of updating the user guide so that she could format it the way she wanted, and we focused on code.

Plan with the end in mind. Short projects mean that milestones can sneak up on you before you notice. Half-way through the project, we realized that the project end date was coming Really Soon Now, and we scrambled to put together a launch plan. We wanted to launch a few weeks before the end of the project, to give people time for feedback and updates. That meant that we needed to send pre-launch e-mails one week and two weeks before the launch, which meant… that we needed to start sending those e-mails within a week. Fortunately, the client, IBM PR, and everyone involved managed to get it all sorted out, and we launched.

I like launching. I would like to do more of it. We don’t do it nearly enough on short projects, but these projects are much more likely to launch if we’re around to help with the transition than if only the client is there.

On a related note, I get antsy about adding new functionality before the end of a project. This makes sense, of course. I don’t mind adding new reports and other reading-related functionality, but workflow tweaks are scary. When we’re planning future projects, we can consider similar risks for late-project tasks.

Things I want to improve for future projects:

  • More Rails! More!
  • More automated tests
  • Track time estimates and actual time in order to improve estimation accuracy
  • More launch planning
  • More blog posts about what I’m learning
  • Optimization

Things I want to reduce on future projects:

  • Avoid documentation formatting problems – I guess that means Microsoft Office when working with clients who use it
  • Must not give in to temptation to skip tests
Short URL: http://sachachua.com/blog/p/23076

Geek tidbits: Postfix configuration for development and testing

From November:

We got the mail information for our Rails site, so I reconfigured the mail server. We’re doing a lot of development, and testing mail is much easier when you can send example mail addresses to one bucket of mail. Here’s how I set up the server to redirect everything addressed to @example.org to a test mail address.

First, I set the mail server up for local delivery only, and I confirmed that I could send mail from a local user to another local user account. Then I experimented with rewriting, using virtual_alias_maps to rewrite addresses to a specific account. I confirmed that worked. Then I checked the database to make sure we didn’t have live e-mail addresses, reconfigured the mail server to deliver Internet mail, and crossed my fingers. A few quick tests showed that mail was being delivered as planned – example.org mail routed to our temporary address, everything else delivered normally. We’ll see how this works!

Here’s our /etc/postfix/main.cf

smtpd_banner = $myhostname ESMTP $mail_name
biff = no
append_dot_mydomain = no
readme_directory = no
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
myhostname = domain.goes.here
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = domains.go.here, example.org
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
default_transport = smtp
relay_transport = smtp
virtual_alias_maps = hash:/etc/postfix/virtual
inet_protocols = ipv4

And /etc/postfix/virtual:

@example.org change_to_real_email@example.com

Don’t forget to run postmap /etc/postfix/virtual; /etc/init.d/postfix reload after changing /etc/postfix/virtual and your configuration.

Short URL: http://sachachua.com/blog/p/22601

Rails experiences: Building an interactive tutorial

One of the risks for this Rails project that I’m working on is that new users won’t have enough ramp-up time before we finish the project. We’re planning to wrap up in December, which is the end-users’ busiest time of year. The project also highly depends on external factors we can’t control, so it might be weeks or even a few months before people get a chance to try the most important parts of the application.

To make it easier for people to get started, we decided to build an interactive tutorial into the system. When people log in, the tutorial should create an offer that they can respond to, walk them through the process of working with it, and then tell them about the next steps they can take. People should be able to stop the tutorial at any time, and they should be able to start the tutorial from the beginning whenever they want a refresher.

People will be on a system that other people use and that generates reports, so all the tutorial information needs to be hidden from reporting and from people who are not in tutorial mode.

I started off by writing a Cucumber test that described how things should work: what people should see, what they could do, and so on.

To keep all the tutorial-related methods in one place, I put them in a file called tutorial_methods.rb and I included these methods in my controller. I added a conditional div to my application.html.erb that displayed the tutorial in a consistent spot if a tutorial was specified for the current page. Then I defined a function that took the current page and figured out what needed to be done for a tutorial. This function created a sample offer at the beginning of a tutorial, performed the behind-the-scenes work to approve the offer once people finished the first step, and loaded the tutorial text from the localization file into an instance variable.

I decided to use Rails’ built-in internationalization support instead of putting the tutorial in the database so that it could easily support multiple languages, although I might use a gem to support internationalization of database values if we need to.

To make things easier on the reporting side, I extended ActiveRecord::Base with my own association methods that filtered the queries depending on whether or not the user was in tutorial mode. These custom association methods made it much easier to make sure all the relevant queries were filtered.

I really liked adding an interactive tutorial to this project, and I think I’ll use that technique for Quantified Awesome as well. Online help is good, but it’s even better if people can practice on something and know it won’t mess up anything else.

Short URL: http://sachachua.com/blog/p/22642

Ruby on Rails: Extending ActiveRecord::Base to define your own ActiveRecord association methods

One of the things I really like about Rails is the ability to add to existing classes so that your code can be cleaner. For example, in the app we’re working on, I need to be able to display a list of offers associated with an organization. I also need to filter that list of offers by different criteria. If the user is not in tutorial mode, I need to filter out any tutorial-related offers. I want to show offers with different workflow states, too.

If I had to do this in straight SQL, I would need to write many queries to cover the different cases, or write my own query-building engine that takes conditions into account. In the Drupal world, I might try to build a View with lots of arguments, and then use a views_pre_execute hook to monkey around with the generated SQL.

In the Rails world, things are much simpler. I started off by chaining queries, because you can add conditions to the end of an ActiveRelation and go from there. That gave me code that looked like this:

base = Offer.includes(:donation).where("organization_id = ? AND (donations.deadline IS null OR donations.deadline >= ?) AND (NOT (offers.workflow_state IN (?, ?, ?)))", @organization.id, Time.now, Offer::DRAFT, Offer::ALLOCATED, Offer::CONFIRMED).order('offers.deadline')
@direct_offers = base.where("offers.workflow = ?", Donation::DIRECT)
@open_offers = base.where("offers.workflow = ?", Donation::OPEN)

Then I asked myself: How can I make this code even cleaner? I thought about adding instance methods. For example, in my Organization class, I could define the following:

class Organization
  # Other stuff goes here
  def current_offers
    self.offers.includes(:donation).where("(donations.deadline IS null OR donations.deadline >= ?) AND (NOT (offers.workflow_state IN (?, ?, ?)))", Time.now, Offer::DRAFT, Offer::ALLOCATED, Offer::CONFIRMED).order('offers.deadline')
  end
  def current_offers_by_workflow(workflow)
    self.current_offers.where("offers.workflow = ?", Donation::OPEN)
  end
end

That would allow me to replace the code above with something like this:

@direct_offers = @organization.current_offers_by_workflow(Donation::DIRECT)
@open_offers = @organization.current_offers_by_workflow(Donation::OPEN)

… so if I wanted to filter out tutorial entries, I could do that in def current_offers by adding a where clause for the tutorial column.

But it seemed clunky to have to specify all these instance methods in order to filter by different ways. What I really wanted was to be able to chain my custom filters together, so that I could write code like this:

@direct_offers = @organization.offers.filter(current_user).direct
@open_offers = @organization.offers.filter(current_user).open

and then eventually be able to do things like:

list = @organization.offers.filter(current_user).current.direct.allocated

(If I really wanted to.)

I couldn’t figure out where to add the methods so that they’d be defined in the right place. If I added the methods to the Organization class, they couldn’t be called on the ActiveRecord relations. A little bit of searching, and I figured out how to do it in Rails. It turns out that you can extend ActiveRecord relations with your own methods! Here’s how.

You’ll need to extend ActiveRecord::Base with your own methods. I put this in config/initializers/activerecord_extensions.rb.

module ProjectNameActiveRecordExtensions
  def filter(control)
    exclude_tutorial = true
    # Include the tutorial offers for users in tutorial mode
    if control.is_a? User and control.tutorial
      exclude_tutorial = false
    # You can also pass filter(false) to turn off these filters for testing
    elsif !control 
      exclude_tutorial = false
    end    
    if exclude_tutorial
      scoped.joins(:donation).where('donations.tutorial=?', false)
    else
      scoped
    end
  end
  # other methods go here...
end
ActiveRecord::Base.extend ProjectNameActiveRecordExtensions

The trickiest part was figuring out how to do a conditional filter, and that’s what scoped is for. I wanted to include the tutorials if the user was in tutorial mode, so my function should be a pass-through in that case. I couldn’t return self or nil, because that broke the associations. scoped turned out to be the magic keyword that refers to the current scope of the query.

What if you want to use the same words in different contexts? For example, “pending” might need to result in two different queries depending on whether you’re asking for pending offers or pending requests. ActiveRecord::Base is used for all classes, but you can use self to find out what class is being used for scoping. For example:

def pending
  if self == StandingRequest
    scoped.where("standing_requests.workflow_state=?", StandingRequest::PENDING)
  else
    # Replace with other cases as I find the need for them
    raise "Undefined behaviour"
  end
end

I love the fact that Rails lets you modify so much in order to make building sites easier. It’s like Emacs for the Web, and it makes my brain happy.

Short URL: http://sachachua.com/blog/p/22641

Tracking and organizing my clothes: substituting mathematics for fashion sense

Thumbnails of clothes

Inspired by my sister’s photo-assisted organization of her shoes, I decided to tackle my wardrobe. Taking an inventory would make it easier to simplify, replace, or supplement my clothes. Analyzing colour would help me substitute mathematics for a sense of style. Combining the images with the clothes log I’ve been keeping would make it easier to see patterns and maybe do some interesting visualizations. Geek time!

I took pictures of all my clothes against a convenient white wall. I corrected the images using Bibble 5 Pro and renamed the files to match my clothes-tracking database, creating new records as needed. AutoHotkey and Colorette made the task of choosing representative colours much less tedious than it would’ve been otherwise. After I created a spreadsheet of IDs, representative colours, and tags, I imported the data into my Rails-based personal dashboard, programming in new functionality along the way. (Emacs keyboard macros + Rails console = quick and easy data munging.) I used Acts as Taggable On for additional structure.

It turns out that the math for complementary and triadic colour schemes is easy when you convert RGB to HSL (hue, saturation, lightness). I used the Color gem for my RGB-HSL conversions, then calculated the complementary and triadic colours by adding or subtracting degrees as needed (180 for complementary, +/- 120 for triadic).

Here’s what the detailed view looks like now:

image

And the clothing log:

image

Clothing summary, sorted by frequency (30 days of data as of writing)

image

Thoughts:

  • White balance and exposure are a little off in some shots. I tweaked some representative colours to account for that. It would be neat to get that all sorted out, and maybe drop out the background too. It’s fine the way it is. =)
  • Matches are suggested based on tags, and are not yet sorted by colour. Sorting by colour or some kind of relevance factor would be extra cool.
  • Sorting by hue can be tricky. Maybe there’s a better way to do this…
  • My colour combinations don’t quite agree with other color scheme calculators I’ve tried. They’re in the right neighbourhood, at least. Rounding errors?
  • I’ll keep an eye out for accessories that match triadic colours for the clothes I most frequently wear.
  • Quick stats: 28 casual tops, 15 skirts, 12 office-type tops, 8 pairs of pants, 5 pairs of slacks – yes, there’s definitely room to trim. It would be interesting to visualize this further. Graph theory can help me figure out if there are clothing combinations that will help me simplify my wardrobe, and it might be fun to plot colours and perhaps usage. Hmm…

Other resources:

Short URL: http://sachachua.com/blog/p/22539

Context-switching and a four-project day

Context-switching among multiple projects can be tough. I’m currently:

  • working full-time on one project (a Drupal 6 non-profit website)
  • consulting on another (helping an educational institution with Drupal 7 questions)
  • supporting a third (Ruby on Rails site I built for a local nonprofit, almost done), and
  • trying to wrap up on a fourth (PHP/AJAX dashboard for a call center in the US).

I’m doing the Drupal 6 development in a virtual machine on my system, with an integration server set up externally. Consulting for the second project is done on-site or through e-mail. The Rails site is on a virtual server. The dashboard project is now on the company’s servers (IIS6/Microsoft SQL Server), which I can VPN into and use Remote Desktop to access. I’m glad I have two computers and a standing desk (read: kitchen counter) that makes it easy to use both!

Today was one of those days. I helped my new team member set up his system so that he could start working on our project. He’s on Mac OS X. It took us some time to figure out some of the quirky behaviour, such as MySQL sockets not being where PHP expected them to be. Still, we got his system sorted out, so now he can explore the code while I’m on vacation tomorrow.

In between answering his questions, I replied to the consulting client’s questions about Drupal and the virtual image we set up yesterday. That mainly required remembering what we did and how we set it up. Fortunately, that part was fairly recent, so it was easy to answer her questions.

Then I got an instant message from the person I worked with on the fourth project, the call-center dashboard. He asked me to join a conference call. They were having big problems: the dashboard wasn’t refreshing, so users couldn’t mark their calls as completed. It was a little nerve-wracking trying to identify and resolve the problem on the phone. There were two parts to the problem: IIS was unresponsive, and Microsoft SQL Server had stopped replicating. The team told me that there had been some kind of resource-related problem that morning, too, so the lack of system resources might’ve cascaded into this. After some hurried searching and educated guesses about where to nudge the servers, I got the database replication working again, and I set IIS back to using the shared application pool. I hope that did the trick. I can do a lot of things, but I’m not as familiar with Microsoft server administration as I am with the Linux/Apache/MySQL or Linux/Apache/PostgreSQL combinations.

I felt myself starting to stress out, so I deliberately slowed down while I was making the changes, and I took a short nap afterwards to reset myself. (Coding or administering systems while stressed is a great way to give yourself even more work and stress.)

After the nap, I was ready to take on the rest. The client for the Rails project e-mailed me a request to add a column of output to the report. I’d archived my project-related virtual machine already, so I (very carefully) coded it into the site in a not-completely-flexible manner. I found and fixed two bugs along the way, so it was a good thing I checked.

Context-switching between Drupal 6, Drupal 7, and Rails projects is weird. Even Drupal 6 and Drupal 7 differ significantly in terms of API, and Rails is a whole ‘nother kettle of fish. I often look things up, because it’s faster to do that than to rely on my assumptions and debug them when I’m wrong. Clients and team members watching me might think I don’t actually know anything by myself and I’m looking everything up as I go along. Depending on how scrambled my brain is, I’d probably suck in one of those trying-to-be-tough job interviews where you have to write working code without the Internet. But it is what it is, and this helps me build things quickly.

On the bright side, it’s pretty fun working with multiple paradigms. Rails uses one way of thinking, Drupal uses another, and so on. I’ve even mixed in Java before. There were a few weeks I was switching between enterprise Java, Drupal, Rails, and straight PHP. It’s not something I regularly do, but when the company needs it, well… it’s good exercise. Mental gymnastics. (And scheduling gymnastics, too.)

I like having one-project days. Two-project days are kinda okay too. Four-project days – particularly ones that involve solving a problem in an unfamiliar area while people are watching! – are tough, but apparently survivable as long as I remember to breathe. =)

Here are tips that help me deal with all that context-switching. Maybe they’ll help you!

Look things up. It’s okay. I find myself looking up even basic things all the time. For example, did you know that Ruby doesn’t have a straightforward min/max function the way PHP does? The canonical way to do it is to create an array (or other enumerable) and call the min or max member function, like this: [x,y].max. Dealing with little API/language quirks like that is part of the context-switching cost. Likewise, I sometimes find myself wishing I could just use something like rails console in my Drupal sites… =)

Take extensive notes. Even if you’re fully focused on one project and have no problems remembering it now, you might need to go back to something you thought you already finished.

Slow down and take breaks. Don’t let stress drag you into making bad decisions. I felt much more refreshed after a quick nap, and I’m glad I did that instead of trying to force my way through the afternoon. This is one of the benefits of working at home – it’s easy to nap in an ergonomic and non-embarrassing way, while still getting tons of stuff done the rest of the day.

Clear your brain and focus on the top priority. It’s hard to juggle multiple projects. I made sure my new team member had things to work on while I focused on the call center dashboard project so that I wouldn’t be tempted to switch back and forth. Likewise, I wrote the documentation I promised for that project before moving on to the Rails project.

Breathe. No sense in stressing out and getting overwhelmed. Make one good decision at a time. Work step by step, and you’ll find that you’ll get through everything you need to do. Avoid multi-tasking. Single-task and finish as much as you can of your top priority first.

I prefer having one main project, maybe two projects during the transition periods. This isn’t always possible. Programming competitions helped me learn how to deal with multiple chunks of work under time pressure, and I’m getting better at it the more that work throws at me.

What are your tips for dealing with simultaneous projects?

2011-06-30 Thu 16:19

Short URL: http://sachachua.com/blog/p/22333

Negative optimization

Checking on one of my projects (a Ruby on Rails survey site), I realized that it was running painfully slowly, taking 30 seconds to render a page.

The first thing I checked was memory. I was on a 256MB slice at Rackspace Cloud. Was the server running out of memory and swapping to disk? I put in the recommended settings for Apache+Passenger+Rails on 256MB:

RailsSpawnMethod smart
PassengerUseGlobalQueue on
PassengerMaxPoolSize 2
PassengerPoolIdleTime 0
PassengerMaxRequests 1000
RailsAppSpawnerIdleTime 0
PassengerStatThrottleRate 5

The website was still crawling. I reviewed the logs and found that ActiveRecord was taking a while. The Internet had a few performance optimization tips, so I checked out the survey controller to see if I could improve performance by preloading information.

As it turned out, I was already preloading information. So I tried turning off preloading by removing the :include directives for my queries.

The system went back to a decent speed.

You see, I’d been working with lots of associations, and eager loading had probably resulted in a gazillion rows in my result set.

Moral lesson: Test your system before and after you put in something to improve the performance, because you just might be making your performance worse. ;)

Oh well. Live and learn!

2011-06-07 Tue 16:19

Short URL: http://sachachua.com/blog/p/22288