Sustainable Development in Ruby, Part 2: Method Injection

Sometimes you have a need for an object method which the class author did not foresee. For instance, in our “previous installment”:http://avdi.org/devblog/2008/03/27/sustainable-development-in-ruby-part-1-good-old-fashioned-inheritance/, we used the following code to accumulate packets until an ending packet was found:

  class BufferedConnection < FMTP::Connection
    def receive
      buffer = ""
      begin
        message = super
        buffer << message.data
      end until(message.data.include?("ENDENDEND"))
      Message.new(buffer)
    end
  end

We test whether the packet denotes the end of a message by searching for the token @”ENDENDEND”@. This is a little messy. It would be cleaner if we could call a predicate method on @message@ to determine whether it indicates the end of a multi-packet message.

It’s easy enough to re-open the @Messsage@ class and add such a method:

  class FMTP::Message
    def end?
      data.include?("ENDENDEND")
    end
  end

Let’s take a step back, however. While adding a previously undefined method is one of the more benign forms of runtime class modification, it is not without its risks. And in this case, we only need the @#end?@ method in one place in our own code, which hardly justifies modifying the @Message@ class globally. Instead, we could localize the extension by injecting the method just in time:

  class BufferedConnection < FMTP::Connection
    def receive
      buffer = ""
      begin
        message = extend_message(super)
        buffer << message.data
      end until(message.end?)
      Message.new(buffer)
    end

    private

    def extend_message(message)
      def message.end?; data.include?("ENDENDEND"); end
      message
    end
  end

In the new method @#extend_message@, we are using Ruby’s dynamic nature to add a new method to the message object at runtime. Now our extension is scoped only to the code that needs it.

There is one more small benefit to using this technique over re-opening the class: our extension is not bound to a particular class in the @FMTP@ library. We don’t have to worry about which class to patch, or even if @Connection#receive@ might return more than one type of @message@. So long as the object returned by @#receive@ contains a @#data@ method, our extension will continue to work.

h3. Applicability

Consider using dynamic method injection when:
* Vendor code controls instantiation of the target
* Your code is the primary client of the target
* The extension is only needed in a small subset of the code.

Stay tuned for our next episode, in which we’ll talk about delegation.

Sustainable Development in Ruby, Part 1: Good Old-Fashioned Inheritance

The first technique we’ll look at in this series is something so basic it may not even seem worth spelling out. But sometimes old-school techniques are overlooked in the excitement of a young language.

Let’s use as our example a hypothetical communications protocol, Flying Monkey Transport Protocol (FMTP). Flying Monkey Transport Protocol is a packet-based peer-to-peer networking protocol in which messages are transported from one peer to another by means of flying monkeys carrying satchels full of data.

As developers in the inter-kingdom IT department, it’s our job to make sure that communications between e.g. the Wicked Witch of the East and the Lollipop Guild flow unimpeded. Where wicked witches are concerned it’s important that no one get mixed messages.

The interface for the Ruby FMTP implementation looks something like this:

module FMTP
  class Connection
    def initialize(address)
      # ...
    end    

    def send(message)
      # ...
    end

    def receive
      # ...
    end
  end

  class Message
    # ...
    attr_reader :data
  end
end

Once a connection is initialized, we can receive messages from the opposite peer by calling receive, which returns a @Message@ object:

  connection = FMTP::Connection.new("witch.east")
  message = connection.receive

Lately the Wicked Witch of the East has gotten rather chatty in her old age, and her messages have been exceeding maximum monkey capacity. As a result, we’ve been forced to start dividing her messages up across multiple monkeys. Unfortunately, the writers of the FMTP library did not plan for this possibility, so recipients of the Witch’s communiques have been getting truncated messages. We’ve been tasked with making the necessary changes in order to support multi-monkey messages.

As good Ruby programmers, we like to exploit the language’s dyanamic features to the max. And at first, this might seem like the perfect opportunity to use Ruby’s capacity for runtime class modification. We’ll just re-open the class and patch it to do what we need:

module FMTP
  class Connection
    alias_method :receive_without_buffer, :receive
    def receive
      buffer = ""
      begin
        message = receive_without_buffer
        buffer < < message.data
      end until(message.data.include?("ENDENDEND"))
      Message.new(buffer)
    end
  end
end

But there’s another way to accomplish the same ends. A simpler, low-tech way: inheritance.

Inheritance has gone somewhat out of fashion in recent years. And not without reason. In the old days inheritance was seen as almost synonymous with object-orientation, and as a result it was frequently abused. Programs would consist of elaborate, many-leveled inheritance heirarchies that resembled an inbred royal family tree. These programs were hard to understand and hard to maintain.

Ruby programmers have, for the most part, learned their lesson well in this regard. I rarely see a Ruby application with more than two layers of inheritance. For the most part this is a good thing. But occasionally the avoidance of inheritance leads to implementing more complex solutions in places where inheritance is a perfectly legitimate technique.

This is one of those cases. Here is how the code would look using inheritance:

  class BufferedConnection < Connection
    def receive
      buffer = ""
      begin
        message = super
        buffer << message.data
      end until(message.data.include?("ENDENDEND"))
      Message.new(buffer)
    end

And here’s how it’s used:

  connection = BufferedConnection.new("witch.east")
  message = connection.receive

The difference is small, to be sure. But the inheritance version has a number of advantages. It’s slightly shorter. It’s simpler, because there is no need to alias the original method to a new name; we can just use @super@. The name @BufferedConnection@ makes it obvious that we are using a buffered variant of a @Connection@. There’s no chance of our becoming confused by the disparity between what the original @#receive@ method says, and how it actually behaves. And we know that since we have to explicitly ask for the buffered version, there’s no chance of our inadvertantly breaking code somewhere else in the program by changing the semantics of @Connection@.

It might seem like too obvious a technique to even mention. But it’s easy to forget about the prosaic solutions in a language that gives us so many possibilities. You should still put some thought into whether inheritance isappropriate in any given situation. So long as it is a legitimate IS-A relationship and the “Liskov Substitution Principle”:http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple is satisfied, though, there’s nothing wrong with a little good old-fashioned inheritance.

h3. Applicability

Consider using inheritance when:
* You control object creation.

The Pipe Cleaner Gang

My stepson is a budding artist, and pipe cleaners are his medium of choice these days.  I recently changed seats at work, and the new location is more conducive to displaying the creations he has entrusted to me.  So I give you my new silent audience:

the pipecleaner gang

From left to right: Godzilla; Trogdor the Burninator, Strong Bad, Pom Pom from Homestar Runner; Tom Servo and Crow from Mystery Science Theater 3000.

Details shots below the fold:

Read More

Sustainable Development in Ruby: Introduction

This is the beginning of a series of posts on sustainable development in Ruby. No, I’m not talking about writing code on wind-powered laptops while sipping fair-trade coffee. But the sustainable development movement has a fairly direct analog in software development. As programmers, we work within a code ecosystem. The ease with which we write new programs is impacted by the choices of other coders before us, and likewise the decisions we make while coding affect other programmers down the line.

As with industrial development, in the early years of a particular software ecosystem it’s not always obvious that the choices we are making might be detrimental to our successors.

The first generation of programmers is usually enthusiastic; any failure is a personal failure, so you can gloss over those things. It’s the second generation that’s going to be less enthused, that’s going to stare in bafflement at these classes that mysteriously spawn methods, and trying to figure out what’s going when there’s an exception in dynamically generated code.

Ian Bicking

When a programming language is relatively young, unsustainable practices often go unnoticed, or are dismissed as easily avoidable.

A mere flesh wound, says our programming primate: I usually don’t get conflicts, so I’ll pretend they won’t happen. The thing is, as thing scale up, rare occurrences get more frequent, and the costs can be very high.

Gilad Bracha

When a language is only a few years old, the amount of code written in it is necessarily small. Legacy code is measured in the thousands of lines, rather than hundreds of thousands. If a section of code, or a third-party library, is causing problems–well, you can always rewrite it. As codebases grow, however, the rewrite option becomes less and less viable. Coping with legacy code is an everyday fact of life for most professional programmers.

Parameters

If we take it to mean any and all techniques for making software more robust and easy to maintain, sustainable software development is a very broad subject. Indeed, one could argue that most of the major advances in the software field in the last 30+ years have been made with sustainability in mind – OO, refactoring, TDD, to name a few.

In this and the following essays I’m only going to be addressing one specific aspect of sustainability in the Ruby language. I’m going to be talking about the practice of dynamic class modification, colloquially “monkeypatching”. I’m addressing this subject because I believe injudicious use of dynamic class modification to be one of the greatest threats to long-term sustainability currently facing Ruby.

Definitions

What are we talking about when we say monkeypatching? Some divide dynamic class modification into two categories:

  • Runtime addition of methods
  • Runtime redefinition (overwriting) of methods

Some maintain that only the latter definition is true monkeypatching. The line is blurrier than it might first appear, however. If two separate libraries both add a #to_xml method to the Object class, then individually they are only adding a method, but when both libraries are required by the same program one will be overwriting the other–whichever one is loaded last. For this reason I will use “monkeypatching” to mean both the dynamic addition and redefinition of methods, albeit with an emphasis on redefinition.

Specifically, I will be referring to dynamic non-local addition and redefinition of methods. By non-local, I mean that the dynamic modification occurs outside of the original class definition. The following is not a monkeypatch:

  class Foo
    attr_accessor :bar # defines bar, bar=

    # redefines bar
    def bar
      # ...
    end
  end

Whereas this version demonstrates monkeypatch:

  class Foo
    attr_accessor :bar # defines bar, bar=
  end

  # re-open the class
  class Foo
    # redefines bar
    def bar
      # ...
    end
  end

No easy answers

this “monkey patching” thing is seriously powerful

Chad Fowler

languages—like Ruby—that include dangerous features give the fringe a broader latitude to invent new things. Of course, they also break things and they invent stupid things and they get excited and write entire applications by patching core classes instead of writing new classes and commit all sorts of sin.

Reginald Braithwaite

One of the things that’s really great about agile languages is they give you the power to do anything. One of the most horrible things about agile languages is they give every other idiot the same power to stab you in the back with a rusty pitchfork.

Zed Shaw

This series will not tell you when to monkeypatch and when not to. It is not my intent to set myself up as the arbiter of when monkeypatching is justified. These posts make the assumption that you already understand that Ruby dynamism is both tremendously powerful and potentially dangerous. I want to present some alternatives to monkeypatching, so that you can make an informed decision when deciding whether to use monkeypatching to solve a particular problem.

Conventions

In order to help you make that judgement, I’ll be characterizing the techniques presented by their applicability. Some of the aspects affecting applicability are introduced below. In this context, the term “vendor” is used to refer to any code you don’t have the ability to change upstream – whether from a third-party library, or written by another team down the hall. The term “target” is used to mean a vendor-defined object whose methods you wish to modify.

  • Who controls creation of the target? Does your code call the class constructor, or is the object given to you already created by vendor code?
  • Are you the only client? Is the target used by third-party code, or is your code the only code that touches it after it is created?
  • Can you intercept the target? If the target is produced and consumed by vendor code, is there still a point at which your code has access to it? Or is it’s use completely internal to vendor code?

These factors and others will affect which techniques are right for any given case.

Doctor, it hurts when you say that

bq. In your response, for instance, you say “I prefer thinking before writing code.” Well, so do I, but the fact of the matter is some people don’t and some people make mistakes based on inexperience or ignorance even when they do prefer to think before they code.

— “Michael Feathers”:http://reddit.com/info/69sq5/comments/c039uce

A standard response in our office, whenever someone says “I get an error when ever I try to X” is “Well, don’t do that!”  It’s always said with a wry grin, though, because we know that this is just a joke, not an actual solution to the problem.  You can’t just keep sidestepping the problem by avoiding the code that doesn’t work as expected.  You can’t always start from scratch. At some point you have to draw the line, dig in, and make it work.  No matter how old or messy the code involved.  And this is why it’s a funny joke, not a serious philosophy.

Some people in the software industry don’t seem to get the joke.

In any given online discussion of software practices of sufficient length, there’s always a few  whose pat response is, “Well, don’t do that!”.  And when you reply that it’s not your code, it’s in legacy code, or in a third-party library, they smugly retort that you shouldn’t use other people’s code if it isn’t up to your standards.

Meanwhile, back in the real world, there are deadlines to be met, and we don’t always have time to re-write our entire software ecosystem from the bare metal up. Lousy code exists, and we have to learn to coexist with it; to gradually improve it; and to do our best to prevent the same mistakes from being repeated.

Thankfully, others have gone before us.  Michael Feathers wrote -a- “+the+ book on the subject”:http://www.amazon.com/Working-Effectively-Legacy-Robert-Martin/dp/0131177052/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1204774848&sr=8-1, and I can’t recommend it highly enough.  In fact, I’m going to go read some more of it now.

Full Disclosure; or, What’s in your toolbox?

The one objection I haven’t heard yet to my monkey patching rant is: you’re a “hypocrite”:[http://utilitybelt.rubyforge.org/svn/lib/utility_belt/convertable_to_file.rb]!

Gasp, yes, I have written code that exploits Ruby’s open classes. It even extends @Object@, the core-est of the core! And then shamelessly contributed it to a publicly available gem!

As anyone who read past the title of my article should know, I don’t think monkey patches and open classes are unmitigated evil.  I even use them myself.  In this case, I happen to think it was justified. UtilityBelt is essentially a collection of MonkeyPatches. It’s entire usefulness lies in providing convenience methods that require as few keystrokes as possible, since they will be used from the command line. And it is intended for a very special environment – IRB. Many Ruby hackers have hacks in their .irbrc that they would never include in a production application.

So does this mean I think that I should have power of approval over when monkey patching is justified?

No. Ruby is a powerful language. That’s why I use it. That’s one of the reasons that when I set out to find a new job, I looked for one where I could use Ruby every day. And with power comes “danger”:http://www.jamesbritt.com/2008/2/25/monkey-poaching-is-distracting-ruby.

But because I work with Ruby every day, on large, real-world projects that use a lot of third-party libraries, I also come in daily contact with the annoyances and hangups that are caused by the misuse of that power. And lately I’ve been noticing that the majority of those irritations stem from misuse of open classes, and that the majority of those misuses are completely avoidable.

I’m not going to tell people what features they can and can’t use. But I’m also not going to hold back from commenting when I see a clear trend emerging, a trend with a completely predictable outcome. Pile all of your extensions from a myriad sources into a few pitifully overburdened classes, and there will be collisions, which will be a pain to track down when they start causing inexplicable bugs. And all of this will have been, once again… completely avoidable.

I’m not out to stifle anyone’s “creativity”:http://weblog.raganwald.com/2008/02/1100inject.html. I don’t believe, however, that there’s necessarily any tension between promoting creativity and promoting wise practices.

All I’m saying is this: before re-opening a class, did you go through the rest of your toolbox first?

Followup to “Monkeypatching is Destroying Ruby”

My last article was intentionally provocative, and my expectations of response were exceeded. The “ruby-talk thread”:http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-talk/292269?292062-292516 was full of good comments. I’m appreciative of everyone who has taken the time to respond either there or here on the blog.

I thought I’d write a quick followup and just respond to a few of the points that came up repeatedly in the ensuing discussion.

h5. “This is a Rails problem, not a Ruby problem.”

I’ve been coding in Ruby for something like seven years now. When I started ruby-talk was the only game in town, and Rails was years away. Now I work at a Rails shop now, and I’m the self-appointed “Ruby geezer” – I’m the guy who corrects people when they complain about some “Ruby” feature which is really a Rails extension.

I know the difference between the Rails community and the Ruby community. And the fact of the matter is, these days most of the payed work being done in Ruby is Rails work. For better or for worse, the coders of tomorrow’s Ruby community are coming to it from Rails. Rails cultural problems will increasingly be Ruby cultural problems.

h5. “Monkey patching is a powerful and useful technique”

No argument there.  What I am calling for is that it be used sparingly, judiciously, and only when there are no other practical options.  In particular, I’m discouraging the use of monkey patching as a standard mechanism for extending core or third-party libraries – especially when those extensions will be packaged and distributed.

h5. “Bad code has always been with us.  There’s nothing special about monkey patching”

What is special about monkey patching is that right now it is being used as a de-facto standard mechanism for class extension in gems and plugins,  to the exclusion of less invasive techniques.  Numerous gems add functionality to core classes like @Object@.  In some cases it’s warranted, but in many it seems to be more because we can than because it’s called for.  And in Rails, it is simply assumed that if you’re going to extend, say, ActiveRecord with a plugin, you’re going to do it by monkey patching @ActiveRecord::Base@ or some other core Rails class.

As an thoroughly modern OO language, Ruby offers almost an embarrassment of riches when it comes to  methods for extending functionality while maintaining encapsulation.  Inheritance, mixins, rediculously easy delegation.  Using monkey patching to extend classes throws all of that away, and takes us back to the C days of working all in one big undifferentiated namespace.  Except in C if you were lucky the compiler would complain when you redefined an existing symbol.  Ruby will happily let you override methods and overwrite instance variables without a peep.  This, of course, is part of the power of Ruby.  Power that should be reserved for prototyping and for special cases, not for everyday plugins and libraries.

h5. “Where’s the code?”

This is a legitimate criticism. So far I’ve talked a lot about the evils of rampant monkey patching, and I haven’t shown any code, either to demonstrate what not to do, or to demonstrate alternatives.  This is almost unforgivable on a programming blog.  I’m planning on remedying that ASAP.  I’m working on a series of posts covering alternatives to monkey patching.  Stay tuned.

Monkeypatching is Destroying Ruby

(The title of this post is intended to be deliberately provocative, as well as being a nod to Steven Colbert’s “The People Destroying America” segments. It’s provocative because I want to get people talking about this issue.  I don’t actually think that monkey patching is “destroying” Ruby, but I do think the proliferation of the technique has real and troubling implications for Ruby’s future.)

“Monkey patching”, for anyone who doesn’t know, refers to the practice of extending or modifying existing code by changing classes at run-time. It is a powerful technique that has become popular in the Ruby community at least in part because the Ruby language makes it so easy. Any class can be re-opened at any time and amended in any way.

I believe the term first arose in the Python community, as a derogatory term for a practice which that community tended to frown on. The Ruby community, on the other hand, has embraced the term and the practice with enthusiasm. I’m starting to think that the Pythonistas’ attitude may have been justified.

Here’s what crystalized it for me. The other day I wrote a small Rails plugin (“NullDB”:nulldb). It was inspired largely by another plugin, “UnitRecord”:unitrecord. UnitRecord is by “Dan Manges”:[http://www.dcmanges.com/], a talented Rails developer whom I have a lot of respect for.

UnitRecord is implemented almost entirely as a set of monkey patches. When invoked, it dynamically modifies several standard Ruby and Rails classes, including @ActiveRecord::Base@, @Test::Unit::TestCase@.  As a result of this implementation, it is tightly coupled to the inner workings of ActiveRecord.  A small change to Rails and it could cease to work, and such a failure would be difficult to debug. Indeed, one of the reasons I decided to write “NullDB”:nulldb was because of just such a failure.

In writing “NullDB”:nulldb, I discovered that I could achieve the same functionality without resorting to monkey patching.  Instead of modifying existing classes, it implements the Rails “Database Adapter API”:[http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/AbstractAdapter.html].  The finished library is shorter than “UnitRecord”:unitrecord, is composed entirely of implemetations of public APIs, and contains zero monkey patches.  The code is also easier to understand, in my opinion, because of the lack of metaprogramming.

Now, I did not write this post to gloat about how my library is better than Dan’s.  As I said before, I have a lot of respect for Dan.  He’s a smart guy; I’ve learned a great deal from “his blog”:[http://www.dcmanges.com/]; and it’s safe to say I would be a poorer Rails programmer if it weren’t for hm.

And this is really the point.  Monkey patching is the new black.  it’s what all the hip kids are doing.  To the point that smart, experienced hackers reach for a monkey patch as their tool of first resort, even when a simpler, more traditional solution is possible.

I don’t believe this situation to be sustainable.  Where I work, we are already seeing subtle, difficult-to-debug problems crop up as the result of monkey patching in plugins.  Patches interact in unpredictable, combinatoric ways.  And by their nature, bugs caused by monkey patches  are more difficult to track down than those introduced by more traditional classes and methods.  As just one example: on one project, it was a known caveat that we could not rely on class inheritable attributes as provided by ActiveSupport.  No one knew why.  Every Model we wrote had to use awkward workarounds.  Eventually we tracked it down in a plugin that generated admin consoles.  It was overwriting @Class.inherited()@.  It took us months to find this out.

This is just going to get worse if we don’t do something about it.  And the “something” is going to have to be a cultural shift, not a technical fix.  I believe it is time for experienced Ruby programmers to wean ourselves off of monkey patching, and start demonstrating more robust techniques.

I see the problem as one of convenience: sure, Ruby provides us with an immense toolbox, bigger than that of most other languages. But we’re like lazy carpenters: sure, we have a whole garage full of tools, but there’s a hammer laying right there on the floor next to us, and it’s easier to reach for the hammer instead of getting up and fetching the right tool for the job.  But those tools do exist.  Indeed, most of the patterns in the “software pattern literature”:[http://c2.com/ppr/index.html] are inspired at least partly by the need to manage extension in robust and maintainable ways.

I look at the Emacs community as an inspiration.  Emacs, as many of you probably know, is essentially just a lisp machine.  Emacs Lisp, the language it is written in, is every bit as dynamic as Ruby.  Functions can be replaced at any time, by code in any package.  And because all Emacs Lisp code is intended to extend the Emacs editor, it is not uncommon for hundreds of different Emacs Lisp packages to be running in the same process, all coexisting and interacting. And yet, for the most part this melting pot of extensions all function together smoothly and without breakage.

Why?  I think the biggest reason is community conventions.  I’ve read a lot of ELisp code, and I have very rarely seen the equivalent of Ruby-style monkey patching.  Even the @advice@ mechanism, an AOP(Aspect Oriented Programming)-like feature which enables functions to be dynamically wrapped and chained, somewhat like Rails’ @alias_method_chain@, is used sparingly.  Instead, every mature Emacs extension exposes a plethora of “hooks”, extension points that other packages can attach their own handlers to.  Other packages add their handlers to these hooks, and to hooks that the core Emacs code provides, and thus they cooperate largely without collisions.

Hooks are just one of the techniques available to us for robustly handling extension in a language as dynamic and powerful as Ruby.   Another is using the dependency injection style[1] to decouple classes from each other and allow third parties to substitute their own classes without monkey patching.  Another un-sexy but important practice is simply writing clear and comprehensive documentation of our classes’ APIs and extension points.

But the most important thing we can do is set an example.  This is my call to action, my line in the sand: as experienced Ruby programmers, let us demonstrate solid, sustainable practices in our own code.  Let us use monkey patching sparingly, as a tool for exploration and experiment, and only in production code as a last resort.  Let’s produce APIs that are amenable to extension without having to be patched.  And let us begin to develop some community-wide conventions and tools for writing extensible classes.

fn1. UPDATE A commenter on ruby-talk felt that my use of “Dependency Injection” revealed an “Enterprisey” bias.  Let me be clear that when I say “dependency injection style”, I’m not talking about using elaborate DI frameworks.  I’m just talking about writing classes that allow their collaborators to be passed in, either via constructors or setters, rather than having hard-coded collaborators.

[nulldb]http://avdi.org/projects/nulldb/

[unitrecord]http://unit-test-ar.rubyforge.org/

Announcing NullDB 0.0.1

I spent the afternoon coding an alternative to the ARBS and UnitRecord database-elimination plugins.  Definitely scratching a personal itch, as I had a project where I wanted to use one of the above-mentioned libraries but i just couldn’t seem to get them to work.  I’m releasing it in hopes others will find it useful too.

Here’s the text of the README:

What

NullDB is a Rails database connection adapter that interprets common database operations as no-ops. It is the Null Object pattern as applied to database adapters.

How

Once installed, NullDB can be used much like any other ActiveRecord database adapter:

  ActiveRecord::Base.establish_connection :adapter => nulldb

NullDB needs to know where you keep your schema file in order to reflect table metadata. By default it looks in RAILS_ROOT/db/schema.rb. You can override that by setting the schema option:

  ActiveRecord::Base.establish_connection :adapter => nulldb,

                                          :schema  => foo/myschema.rb

There is a helper method included for configuring RSpec sessions to use NullDB. Just put the following in your spec/spec_helper.rb:

  Spec::Runner.configure do |config|

    ::NullDB.insinuate_into_spec(config)

  end

You can also experiment with putting NullDB in your database.yml:

  unit_test:

    adapter: nulldb

However, due to the way Rails hard-codes specific database adapters into its standard Rake tasks, you may find that this generates unexpected and difficult-to-debug behavior. Workarounds for this are under development.

Why

NullDB is intended to assist in writing fast database-independant unit tests for ActiveRecord classes. For why you would want to test your models without the database, see: www.dcmanges.com/blog/rails-unit-record-test-without-the-database.

NullDB was inspired by the ARBS and UnitRecord libraries. It differs from them in a couple of ways:

  1. It works. At the time of writing both ARBS and UnitRecord were not working for me out of the box with Rails 2.0.
  2. It avoids monkey-patching as much as possible. Rather than re-wiring the secret inner workings of ActiveRecord (and thus being tightly coupled to those inner workings), NullDB implements the same [semi-]well-documented public interface that the other standard database adapters, like MySQL and SQLServer, implement.
  3. UnitRecord takes the approach of eliminating database interaction in tests by turning almost every database interaction into an exception. NullDB recognizes that ActiveRecord objects typically can‘t take two steps without consulting the database, so instead it turns database interactions into no-ops.

One concrete advantage of this null-object pattern design is that it is possible with NullDB to test after_save hooks. With NullDB, you can call +save+ and all of the usual callbacks will be called – but nothing will be saved.

Limitations

  • It is not an in-memory database. Finds will not work. Neither will reload, currently.
  • It has only the most rudimentery schema/migration support. Complex migrations will probably break it.
  • Lots of other things probably don‘t work. Patches welcome!

Who

NullDB was written by Avdi Grimm <avdi@avdi.org>

Where