Slides for "Practical Ruby Projects with MongoDB" at Ruby Midwest

07/17/10 12:02 PM

Here are my slides for "Practical Ruby Projects with MongoDB", a talk I gave at Ruby Midwest. Enjoy.

Testing rails view helpers

07/11/10 03:24 PM

Rails view helpers are easy to overlook in your test suite, but they want your testing love just like your models. I generally use view helpers for one of two reasons: 1.) when I'm trying to output something that's a bit too hairy to leave in a view or 2.) when I want to DRY up some view code. Both of these reasons warrant testing this code.

At first, view helpers can be kinda awkard to test, but in this post I'll show that testing view helpers can be quite simple.

Mongoflow == Rubyflow + Mongoid + Rails 3 + Refactoring Love

Recently @theriffer and I have been hacking on mongoflow, a MongoDB link aggregator heavily inspired by rubyflow. In fact, the super-awesome Peter Cooper was gracious enough to open source the original rubyflow codebase. We've decided to go with mongoid as the Mongo ODM (object-document mapper) and rails 3. Refactoring Peter's original version of Rubyflow to rails 3 has been a fun little project. (To view the code for MongoFlow, check out the github project.)

Rails 3 form error helpers

As I was clicking through the app, I noticed some deprecation warnings flying by in the server output. As it turns out, the error_messages_for and f.error_messages methods have been deprecated in rails 3 beta 3 and moved to plugins. I was never really crazy about the default error message style, so I decided to write my own super-simple helper method to show error messages for an object.

Keep in mind that rails view helpers are simply modules that get included into an ActionView template. Thus, the methods you define in a helper are available to you in templates as instance methods. However, we often use helpers to abstract creating HTML markup, and we often do this by leveraging the rails view helper methods (such as content_tag).

So in order to test view helpers that use the rails helper methods, we need to simulate the scope in which we would otherwise be calling our helper methods - a view template instance.

Rails to the rescue!

Luckily, creating an instance of ActionView::Base is absolutely trivial. All we need to do is create a new class that extends ActionView::Base, include our helper module, and instantiate it. That's it. No params, nothing. Boom. Awesome. You can see where I've created the MockView class on line 11.

By extending from ActionView::Base, we get access to all the other helper modules that rails would give you in a view template, in our test suite. If we had just created a plain-vanilla ruby class for MockView, trying to call the content_tag method on it would raise a NoMethodError.

The second class, FakeItem, is an actual valid Mongoid model, with a title field and two validations on that field.

The reason for that I've created a proper Mongoid class (as opposed to stubbing certain methods or creating a mock object) is that Mongoid leverages the new ActiveModel API, which is where validations and validation errors (among other things) are handled in rails 3. The implementation of the form_errors method is dependent upon the ActiveModel api, and I would rather have access to the real errors objects, as opposed to attempting to stub out the portion of the api we're using in the form_errors method, which could get messy. Plus, creating classes in Ruby is so trivial, so why not!?

In the first example, the model instance will get two errors placed on it by ActiveModel. To test the #form_errors method, I'm simply asserting it outputs the correct html markup around the error messages coming from the model. A bit brute force, yes; but it's the best way that I know of to test the entire #form_errors method, rather than just pieces of it.

An alternative might have been to place message expectations on the various calls to the content_tag method, but that's a lot of magic, and not much benefit. And I hate magic ;)

I hope you've found this post useful. I'd love to see how other people are testing view helpers, so please post what you're doing in the comments.

tags: rails, testing

Ruby Will Treat You Like an Adult

05/04/10 07:41 PM

One of the things that I love most about Ruby is that it is an "adult's language". It is a very powerful language; as such, it doesn't put many restrictions on what you can do with your objects and classes.

Most of are familiar with the ability open up and modify any class in Ruby, including what might be referred to in other languages as "primitives". Take the following example:

Ok, we can open up any class and play around and be really destructive. Ok, so maybe we're not going to get ourselves into much trouble trying to do something contrived like override +, but Ruby provides many other ways to subvert object oriented principles. Probably the most well-known of these is the Object#send method.

send if used correctly, allows you to completely subvert encapsulation without even missing a beat.

Whoa! Aren't private methods supposed to be private!? Well, if we had tried to simply call Person.new.dirty_secrets our secrets would have been safe, and Ruby raises an error in this case. However, when we use send, we don't get a warning, no slap on the wrist, nothing. Nope, we just move happily long, and our secrets are out.

Ruby truly is an adult's language. While Ruby is extremely object-oriented (at least in the sense that nearly everything in Ruby is an object), we're given the power to completely subvert and override encapsulation, a basic principle of object-oriented programming. Kinda cool, but as I found out the hard way, this can be dangerous if you're not careful.

At this point, there's a decent chance you're thinking to yourself, "Listen GUY, I'm a good developer, I would never do something so arrogant as to use send on a private method. Well...at least not in production code, but I suppose I've done it in my tests before. But that's not a big deal. They're just tests."

So let's drop the contrived examples and look at some real code. Indeed, I got a bit arrogant, and it came back to bite me, and by "bite me" I mean that I introduced a bug into what my tests told me were a stable code-base.

Bunyan

About a month or so ago, I released a project called bunyan. Bunyan is a very simple project that provides a thin wrapper around a MongoDB capped collection. I created it because capped collections are really powerful, and we had a need to begin using them to log additional data on every request. Bunyan sits on top of the mongo ruby driver to keep the API clean, simple and familiar.

Shortly after we began using it, it quickly became a bit of a nuisance for other developers who didn't have Mongo installed on their local dev machines to start up a copy of our app. Essentially, I had created another external dependency on another piece of software that was impeding our development process. This is not good. So I decided that Bunyan should fail silently, output a message to $stderr that it couldn't connect to Mongo. Cool.

At this point I need to explain a bit about how Bunyan works. Basically, Bunyan defines very few methods of it's own. In order to keep it as lightweight as possible, Bunyan uses method_missing to pass nearly all method calls through to a Mongo capped collection. In addition to keeping things simple, this also means that all calls to Mongo other than initializing the connection happen right there in the method_missing method.

So, in implementing this silent failure feature, I need to ensure that if there is a problem connecting to Mongo, then method calls do not get passed on to a Mongo collection that doesn't exist. The way I chose to accomplish this was to use a @disabled configuration option I introduced in the initial release.

This is a configuration option that the user can set from the Bunyan configuration block as a way to turn Bunyan off without commenting out the whole configuration block. Naturally, I figured I would set @disabled = true in a rescue block if an error occurs while connecting to Mongo (In retrospect, I'm not too crazy with the idea of "over-loading" a configuration attribute like that, as a user-disabled Bunyan is somewhat different than Bunyan needing to be turned due to connection problems).

Configuration refactor

So here's where things get tricky. Initially, I was handling all the configuration stuff and the logging in one big class. It soon became apparent that I needed a separate configuration class to handle all this logic. Without going into too much detail, it's safe to say this was a fairly major refactor of the configuration logic (commit).

So, when I went about to actually do this, I forgot that I had moved the @disabled variable and more importantly, the disabled accessor method, to the configuration class. In other words, doing what I did on line 12 below had absolutely no effect on anything.

Even worse, my tests told me so, because they were failing. I should've known better right then. But no, I was arrogant. Here's a re-enactment of the conversation my test suite and I had that day:

Ruby: No go bro! Yer doin it wrong.

Alex: No way man! I'm setting the @disabled variable right there.

Ruby: I'm telling you bro, that mess is not disabled!

Alex: Pfft! It's probably just some shared-state crap between my tests because I'm using the Singleton module to do all of this. Yea, that has to be it.

instance_variable_get should come with a poison label on the box

At this point, I made the biggest mistake of the day -- I changed my tests to make my failing code pass. If you find yourself feeling the need to do this, before you type another stroke, take a five minute break and re-think your inks, because you're doing it wrong ;)

The second example below is the one to focus on, and specifically line 20 where I felt the need to use instance_variable_get to subvert encapsulation and pull the instance variable out of the class directly.

Again, I cannot stress enough how bad of an idea this really is.

Of course, the reason the tests were failing is because I should've been setting @config.disabled = true. @disabled was a deprecated property.

Listen to your tests, don't subvert encapsulation, and don't be a jerk

The moral of this story is that you should listen to your test suite when it's trying to tell you something.

tags: ruby, adulthood