Work with us 2
If the last post about our Javascript issues didn’t put you off, then you might be interested to know that we’re hiring. If you’re an experienced, test infected Ruby on Rails programmer with some Javascript and a real world consumer website or two under your belt, and you’re happy to work in Newcastle upon Tyne, then we definitely want to hear from you. I’d probably be interested in at least hearing from you if you’re an experienced dynamic language programmer who has only recently made (or is considering making) the switch to Ruby and Rails. It’s only syntax after all.
The money’s decent, the work is interesting, the people (well, apart from me, obviously) are great, and Newcastle’s a fantastic city. Drop me a line, ping me on AIM/gTalk/Twitter or just send your CV to the jobs@amazing-media.com.
The "Yes is No" Problem 12
login: pdcawley
password: *****
Yes is no and no is yes.
Do you want to delete all your files? [y]: _
Rereading What’s wrong with Ruby, I realised that the meat of Matthew Huntbach’s worry about dynamic classes boils down to the ‘yes is no’ problem. Given just the source code of a Ruby program, it can be tricky to work out what the eventual program ‘looks like’.
Ruby source can be thought of as a program that you run on the Ruby kernel which creates a bunch of classes, objects and relationships between them. In static languages, once a class has been created, that’s it, the only way to change it is to recompile and restart the program. In dynamic languages, no class is every really ‘finished’, it can be modified at any time.
Done right, that can be a great thing. It’s instructive to look at the way ActiveRecord’s behaviour gets built up in Ruby. ActiveRecord::Base doesn’t really do all that much, it concerns itself with the nuts and bolts of marshalling data to and from the database and building the basic accessors and support methods needed to do that. Its implementations of basic methods like #create, #update and #save are positively naïve. New behaviours are then layered on by including other modules. Modules like ActiveRecord::Locking::Optimistic, ActiveRecord::Validations, ActiveRecord::Callbacks and others all layer behaviour on top of these methods, usually by renaming the old method, so #update becomes #update_without_lock and ActiveRecord::Locking::Optimistic#update_with_lock becomes the new #update, at least until ActiveRecord::Timestamp changes its name to #update_without_timestamps and installs the behaviour that it needs.
It can make the code harder to understand though. Say you’re looking at ActiveRecord::Timestamp#update_with_timestamps and you want to look at how #update_without_timestamps does its thing. If you simply grep for def update in the source code, you’ll find the implementation in ActiveRecord::Base and miss out on the alterations introduced by the locking module. You can’t simply look at the source code, you have to look at the order in which it was executed, and that can be a headache. Once you understand that you need to look at load order, and you find where that is defined, things get much easier, but things can get seriously scary when plugins come into play too.
What’s needed (or at least desirable) is a way of understanding what the state of the program and its classes will be at runtime. It’s easy in static languages, it’ll look like the source code says it looks. It looks like the source code says it will look in Ruby too, but Ruby source code can get a good deal more twisty. Even attr_accessor has the potential to blow minds…
Would you believe it’s a solved problem? It’s been solved for years too. Heck, the solution is older than David Heinemeier Hansson.
Counsel of Perfection
I’m thinking of Smalltalk, but I’m pretty sure that the Lisp Machine folks had similar tools at their disposal. In a Smalltalk image, you can browse the source code at any time, and it always reflects the current specification of the system. In a Smalltalk implementation of ActiveRecord, as soon as you rename #update to #updateWithouLocking that’s what it’s called. When, later you’re looking at the definition of #update and you want to know what #updateWithoutLocking looks like, you can browse straight too it. The knowledge that #updateWithoutLocking used to be called #update hasn’t been lost, you’ll find it in your changes, but it’s irrelevant to how the system behaves now.
I’ve been admiring Smalltalk from afar for a long time now, but until recently it wasn’t a language I spent any time programming in. I was more likely to think “How does Smalltalk do that?”, then fire up my local Squeak image and do a little fossicking about until I had a grasp on how things worked. Then I’d close it down and do something similar (or curse because I couldn’t) in Perl or Ruby. You can learn a great deal just doing that; if nothing else I’ve learned that I like Smalltalk’s message selector syntax a great deal.
Then, a few weeks ago, I watched a couple of screencasts of experienced Smalltalk developers doing their thing and… wow. I’ve always known that Smalltalkers rave about the environment, but I’d not really seen it in full flow.
So, I’ve been doing a few Code Katas in Squeak and starting to get a feel for working in the environment. It’s really, really lovely. Watch this space for more ‘coo er gosh’ type posts. Who know, maybe even an embarrassing screencast – if nothing else but so I can get some feedback from experienced Smalltalkers on what I’m doing wrong.
What about Ruby?
Short of someone coming up with a Smalltalk style interactive development environment for Ruby in the next half hour, this isn’t an immediately useful solution to the problem in Ruby.
But is it really a problem?
Having taken the time to explain why dynamic modification of classes can be a problem, I should point out that I don’t think it’s really a problem in practice. Sure, you can do Very Bad Things with it, but so what? You can do the same bad things (and more) in Smalltalk, if you couldn’t, it would be (nearly?) impossible to implement the Smalltalk environment in the first place. In the absence of an interactive programming environment, you need to take more care with structuring and organizing your code. Impose some standards on yourself – in Rails for instance, the various ActiveFoo classes are structured similarly: first there’s the ‘active_foo.rb’ file, which is responsible for loading up all the packages that define the class (generally found in the ‘active_foo’ sub directory) and includeing them in ActiveFoo::Base in the correct order. This convention helps comprehension. About the only thing I’d like to see further is some documentation in each module describing the behaviour they expect of any class they’re included in – behaviour like that of ActiveRecord::Validations is just too useful not to be pinched for other classes.
As always with these things, it boils down to the good taste and skill of the programmer. Write code that communicates your intent clearly to your fellow programmers – mere compilation is the least of your worries.
Updates
In the comments, Phil Toland has come up with links to some Smaltalk screencasts and a long presentation from Avi Bryant that includes some interaction with a Squeak image.
NB: For ‘ruby’, you can read ‘pretty much every dynamic OO language ever’. Except Matthew Huntbach didn’t write an article called “What’s wrong with pretty much every dynamic OO language ever”.
My first 'acts_as' plugin 7
So, you’ve upgraded to Rails 1.2.1 and you’re working on a tool to maintain a database of all the tunes you have in your various songbooks and (eventually) your record collection. You start with:
$ ./script/generate rspec_resource MusicBook title:string author_id:integer \
abstract:text
$ ./script/generate rspec_resource Tune title:string composer_id:integer \
abc:text book_id:integer
You decide to come back to composers and authors later, so you set up your models1:
MusicBook.has_many :tunes
Tune.belongs_to :music_book
And your routes:
map.resource :music_books do |book|
book.resource :tunes
end
Problems start here
Being a cautious sort, before you start adding behaviour, you fire up a development server and go and check things with the browser. The /music_books/ stuff works fine, but once you start looking at /music_books/1/tunes things start to get weird; all of a sudden your links aren’t making sense.
The problem is, that your scaffolding is calling named routes with something like: edit_tune_url @tune, when, because of the nesting of your resources, they should really be calling edit_tune_url @tune.book, @tune. But how to fix things?
Well, you could go through all your controllers and views, replacing all the calls to named_routes with the right version. But, if you’re anything like me, you’ll get bored stiff of the repetition after you’ve fixed up the first file. And that’s before you start fixing up your controllers to do
@tune = Book.find(params[:book_id]).tunes.find(params[:id])
So, I’m going to suggest that a better bet is to install the acts_as_resource plugin I’m in the process of writing.
Making the model pull its weight
Once you’ve got acts_as_resource installed, you can just do:
Book.acts_as_resource
Tune.acts_as_resource :parent => :book
And your models will magically acquire a resource_chain which returns exactly the list of objects that your named routes need. I’m currently investigating the innards of named routes, but the plan is that you’ll never have to call resource_chain yourself, the named_route helper will do it for you and use the resulting list to build the url.
Your model class, meanwhile, gets a handy
Tune.find_resource(params)
which will find your resource, verifying that it can be reached through the chain of resources specified in your params hash, which means we can fix up the scaffolding generator to use find_resource (or, more likely, a helper method that will set appropriately named instance variables in the controller).
If I understand what simply_helpful is up to, the named_routes hack should play well with that too, which means that form_for @tune will get its url right without you having to remember to call form_for @tune.book, @tune.
Release date?
I’m not quite ready to release yet, I’m busy wrapping my head around how named routes work so I can fix ‘em up to use resource_chain. find_resource is written though.
It’s amazing how much leverage you can get, simply by adding one class attribute and a support method to your model…
Expect a release some time next week. However, if you’re desperate for a look, drop me a line and I’ll send you my local snapshot.
1 Look, vertical space is precious. Pretend this is in the usual blocky type stuff you usually find in the files you’ll find in app/models/whatever.rb
Continuing Sudoku 7
Your mission, should you choose to accept it, is to explain what the following code does:
class Amb
def initialize
@error = Exception.new("Ran out of possibilities")
@failure_continuation = lambda {|v| @error}
end
def assert(assertion)
if !assertion
self.fail
end
end
def deny(assertion)
assert !assertion
end
def fail
@failure_continuation.call(nil)
end
def maybe
one_of [true, false]
end
def one_of(collection = [])
k_prev = @failure_continuation
callcc do |k_entry|
collection.each do |item|
callcc do |k_next|
@failure_continuation = lambda do |v|
@failure_continuation = k_prev
k_next.call(v)
end
k_entry.call(item)
end
end
result = k_prev.call(nil)
if result == @error
raise @error.message
end
end
end
def all_values(&a_block)
k_prev = @failure_continuation
results = []
callcc do |k_retry|
@failure_continuation = lambda {|v| k_retry.call(false)}
results << a_block.call
k_retry.call(true)
end && fail
@failure_continuation = k_prev
results
end
end
Easy? Now explain how it does it.
Usage
Maybe if I show you how it’s used:
amb = Amb.new
a = amb.one_of(1..20)
b = amb.one_of(1..20)
h = amb.one_of(1..20)
amb.assert((a*a + b*b) == h*h)
puts "A triangle with sides #{a}, #{b} and #{h} has a right angle"
If you run that, you'll get:
@A triangle with sides 3, 4 and 5 has a right angle@
In English: we choose a number between 1 and 20. And then two more. Then we assert that the sum of the squares on two of the sides is equal to the square on the third and print the values. And we get the classic 3, 4, 5 Pythagorean triple. How did that happen? Where's the loops?
One clue might be to add a line like: @puts "#{a}:#{b}:#{h}"@ just before the @amb.assert(...)@ line. You'll see a whole pile of numbers spooling away until @a@, @b@ and @h@ reach the values @3@, @4@ and @5@ respectively. It seems that, between them, @Amb#one_of@ and @Amb#assert@ are hiding a depth first search.
Welcome to the weird and wonderful world of continuations. Let's take a look at a slightly simpler example and see if we can explain what's going on.
<code><pre>
amb = Amb.new
x = amb.one_of(1..10)
amb.assert(x > 5)
puts x # => 6
</pre></code>
Most of the magic happens in the implementation of @one_of@. Here's a heavily commented version of it:
<code><pre>
def one_of(collection)
# Capture the previous failure continuation. If we exhaust the collection we need
# somewhere else to fail to.
k_prev = @failure_continuation
# callcc sets a 'bookmark' that we can return to later. See below
# Essentially calling 'k_entry' resets the call stack to what it was when
# callcc got called, and makes it look as though callcc just returned
# that value.
callcc do |k_entry|
# loop through the collection
collection.each do |item|
# save our place in the loop. Taking the k_next continuation is akin to
# simply doing 'next', but you can do it from anywhere...
callcc do |k_next|
# Push a new failure 'continuation' on the fail stack. If #fail gets called
# this will restore the old failure continuation and resume the loop
@failure_continuation = lambda do |v|
@failure_continuation = k_prev
k_next.call(v)
end
# Instead of carrying on with the loop, we return the current item through
# the entry continuation. The loop will only get resumed if 'fail' gets called
k_entry.call(item)
end
# <== k_next points here
end
# If we get here, the loop has come to an end, we've got no more values, so we'll just
# take the old failure continuation.
result = k_prev.call(nil)
# If we get here, that means k_prev didn't take a continuation at all, which means we've
# completely run out of possibilities. So, if the return value is the @error fence we set up
# in initialise, we'll throw an exception.
if result == @error
raise @error.message
end
end
# <== k_prev points to here
end
</pre></code>
Essentially, @one_of@ iterates over the collection you pass it, but then freezes the iteration and 'proposes' the first value. Later on, an assertion (say @amb.assert(x > 5)@) might be false for the first value proposed. The assertion calls 'fail', which jumps back into @one_of@'s iteration, which proceeds to get the next value. @one_of@ effectively says "Did I say 1? Sorry about that. I meant 2, obviously. No? 3 then? 4? 5? 6!" and the program continues on its merry way.
Admittedly, it's not easy to understand how Amb[1] does its magic, but, frankly, most of the time that doesn't matter. You can use it to set up some remarkably complex searches without having to worry about any of the backtracking book keeping yourself.
It helps to have some idea of what's going on behind the scenes though, or you can end setting up a _huge_ search space when a bit of thought in choosing your conditions etc could pay off big time. For instance, say you wanted to find all the Pythagorean triples whose hypotenuse is <= 100. A simple minded setup would look like:
<code><pre>
amb = Amb.new
triples = amb.all_values do
a = amb.one_of(1..100)
b = amb.one_of(1..100)
h = amb.one_of(1..100)
amb.assert((a*a + b*b) = h*h)
[a,b,h]
end
</pre></code>
And it _would_ find all the pythagorean triples in that range. But it would consider some 1,000,000 cases, are @[3,4,5]@ and @[4,3,5]@ _really_ distinct?
A bit of thought gives:
<code><pre>
amb = Amb.new
triples = amb.all_values do
h = amb.one_of(1..100)
a = amb.one_of(1..(h-1))
b = amb.one_of(([a, h-a+1].max)..(h-1))
amb.assert((a*a + b*b) == h*h)
[a,b,h]
end
</pre></code>
Obviously, @a@ can't be longer than the hypotenuse, since the hypotenuse is, by definition the longest side of the triangle. The lower bound for @b@'s a little less obvious, but it derives from the fact that, in any triangle any side is shorter than the sum of the other two sides. However, @b@ should also be larger than @a@, that way we avoid checking 'mirror' cases.
Now, instead of searching 1,000,000 cases, we search 76,625, which is a substantial improvement don't you think?
I have to admit, finding Pythagorean triples isn't the reason I ported Amb from Squeak to ruby (not that it took that long), I ported it because I wanted to use it to solve Sudokus. I'm not going to post the Sudoku implementation just yet; it's in dire need of refactoring and clarification, but, following a first cut which would probably have kept on running 'til the heat death of the universe I have something which will do:
<code><pre>
s = Sudoku.new(<<EOS)
34.....28
.....19..
...64....
.....24..
51.....97
..61.....
....76...
..32.....
62.....39
EOS
puts "Solving Sudoku:"
puts s.unsolved_state
puts "Solution is:"
puts s.solution
</pre></code>
and get output like:
<code><pre>
Solving Sudoku:
3 4 . . . . . 2 8
. . . . . 1 9 . .
. . . 6 4 . . . .
. . . . . 2 4 . .
5 1 . . . . . 9 7
. . 6 1 . . . . .
. . . . 7 6 . . .
. . 3 2 . . . . .
6 2 . . . . . 3 9
Solution is:
3 4 1 7 9 5 6 2 8
7 6 5 8 2 1 9 4 3
2 8 9 6 4 3 1 7 5
8 3 7 9 5 2 4 1 6
5 1 2 4 6 8 3 9 7
4 9 6 1 3 7 8 5 2
9 5 4 3 7 6 2 8 1
1 7 3 2 8 9 5 6 4
6 2 8 5 1 4 7 3 9
</pre></code>
within a sensible amount of time. It's quite remarkable really; ruby's not the fastest of languages, and its continuations are far from the fastest bit of the language.
The great thing about this approach is that, because Amb handles the bookkeeping, I can concentrate on reducing the search space.
fn1. You will note, I'm sure that at no point do I explain what a continuation _is_. Mostly because, for the life of me, I can't find the words - it's all I can do to show you what they do.
Other viewpoints, and why they are important
Many thanks to Paul Ingles who has just written a fantastic article about writing a Typo sidebar in a test driven way.
For too long we’ve been assuming that Typo sidebars are, in fact, detestable (“If it’s not testable, it’s detestable” – someone at the Sydney XP group, by way of Martin Fowler). Paul neatly demonstrates that sidebars are perfectly testable, we just haven’t been testing them. Bad us.
So, I’m rolling back the changes in my current sidebar rejig and starting again by writing some tests for a few of the core sidebars, and then I can start my refactoring without quite so much of The Fear.
So, once again, thanks Paul. It’s really great to occasionally see the curious outsider’s view of what we’re working on – it makes us question our assumptions and often shows us better ways.
Scratching an Itch
Some time ago, around the time of the Typo themes contest, someone, I think it may have been Scott Laird, wrote a nifty little sparklines textfilter.
It’s a really cunning piece of code, the problem is that, to make it work, Scott had to make some dramatic changes to the way textfilters work which, in turn, meant we had to change the way we got at the htmlized versions of our content. Before sparklines, you could do content.body_html in your view and it would just work, generating the html as required. After the change you had to do content.html(@controller, :body), which I’m sure you’ll agree is rather gruesome. So we wrapped it up in a helper method, letting you do: article_html(@article, :body), which is only slightly less gruesome.
At a stroke, we managed to break almost all of the themes entered in the contest, the eventual winner included. Ho hum.
To be honest, screwing up the winning theme’s almost beside the point, I’ve disliked the new interface for getting at html content ever since we introduced it, but I’ve liked cunning textfilters more, so we’ve bitten the bullet and accepted the fact that Typo 4.0 will have a different theme API from the last release.
Well… I say accepted. I’ve been looking for a way out of it in a desultory fashion ever since we introduced it. And today I cracked it. I knew – despite a few howls of protest – that introducing a Blog class was the right thing to do even if we never take the step to full multiblog capabiliity. Today, it paid off…
It turns out that the blog object and its class is the ideal place to hang both static and dynamic context information. The static context includes the usual blog wide settings (and eventually things like the sidebar configuration).
Dynamic context is currently just the active controller. I had been toying with having the controller set a $current_controller global, but that becomes a massive pain in the bum once you start rendering components. However, if you tell the blog about the current controller it can keep a stack of them. Every time you render a component, the blog pushes the component’s controller onto the stack and pops it off again once the sub request has completed.
So: the blog knows about the current controller; the content knows about the blog, which means that we can write:
class Content
def body_html
html(blog.controller, :body)
end
endAnd all your views can use the same API as they did back in the days of Typo 2.
The real body_html is more sophisticated, of course, and in the absence of a controller it falls back on the cached value.
I am a happy camper this morning.
BTW. If you’ve still got problems porting an old theme, it’s probably because your theme renders sidebars ‘wrong’. Check in your themes/<theme>/layouts/default.rhtml and look for the line that looks like
<%= render_component(:controller => 'sidebars/sidebar', :action => 'display_plugins') %>and replace it with:
<%= render_sidebars %>The other big change in the way themes are rendered (and again, its’ sidebar related) is in the <head> section of the layout. Here’s what the default theme header looks like nowadays:
<head>
<title><%=h page_title %></title>
<%= page_header %>
<%= stylesheet_link_tag "/stylesheets/theme/azure", :media => 'all' %>
<%= stylesheet_link_tag "/stylesheets/user-styles", :media => 'all' %>
<%= stylesheet_link_tag "/stylesheets/theme/print", :media => 'print' %>
</head>You should change yours to look similar (you pretty much must include that <%= page_header %> part.
I hope this helps.
If you test it, I will patch
I’ve been working on Typo this weekend, mostly going through open tickets and deciding whether to apply patches.
There’s plenty of potentially good patches in the queue, but too many of them give me The Fear.
They don’t have tests!
I’m the first to admit that I’ve added untested code to my projects (and I’ve been bitten by it too), but I’m more sanguine about that risk because, when it goes wrong, I can often rack my brains and pull back some memory of what my intent was. Knowing my intent helps me write meaningful tests and fix the issue. However, I can’t remember the intent of your code. The only thing I have to go on is what you wrote. If you’ve written tests, I can tell what you expect your code to do, which is far more important than being able to divine what your code actually does. Given a choice between code without tests and tests without code, I’ll take the tests every time.
So, if you’ve got a typo patch on the trac and you want it to get accepted, you must write tests for it. If you’re implementing something that’s supposed to be conforming to a standard, then please reference those standards in your tests.
Humane interfaces again
A friend just popped up on IRC and pointed me to one of Elliote Rusty Harold’s contributions to the humane interfaces debate that was sparked by Martin Fowler’s post on the subject. I didn’t recognize the URL and ended up rereading it.
As I did so, I suddenly realised that yes, Harold does have a point, but that he’s missing another one that seems rather more important to me.
His complaint stems from mystification at the presence of methods like Array#abbrev, Array#nitems and Array#compact!. Methods that, he argues, have no place on a general List object.
He’s sort of right, maybe the distributed ruby Array class wouldn’t suffer if they were removed. But where else are you going to put them? In a world of closed classes, your only choice is to write a utility function somewhere and to hold your nose as you write a function that never touches self and which monkeys with the state of an object in a different class.
Those of us who use dynamic languages with open classes would see that the method needs to be moved onto the appropriate class where it can manipulate the state of self to its heart’s content. Smalltalk excels here; as well as creating the method, you must decide what protocol to place it in. So you could create a ‘foo support’ protocol for methods you’ve put on a class to support your ‘foo’ application. When you discover that the method is of of more general utility, you would move it into another, more general protocol. All of Smalltalk’s tools are protocol aware, and they are a really useful tool for organizing code.
I’m sure that Ruby’s Array has the methods it does because Matz needed them for particular problem and implemented them on the class because that is where they belong.
If I were redesigning the Ruby Array class, I wouldn’t remove the methods that Elliotte objects to, but I would do the nearest thing possible to moving them into a protocol. I’d move them into one or more Mixin packages that Array mixes in by default. The ‘offensive’ methods would still be there, but the would be, in a sense, set apart from the class’s ‘fundamental’ properties. It should even be possible to make Array a subclass of some stripped down BasicArray, but if you’re going to wear that hair shirt you wouldn’t get the syntactic sugar of the [ ... ] constructor.
It seems to me that the users of static languages (and even some dynamic ones) have never got into the habit of asking “Where does this method belong?” Or maybe they did ask it, but got bored of being told “You can’t put that there!” by the compiler.
One could certainly quibble with the choice of which methods got included in Ruby’s core classes, but you certainly couldn’t put those methods anywhere else without compromising them. One could even make a case for leaving them where they are because they act as pointers along the Ruby Way.
The guiding principle is that you should put behaviour in the Right Place. Ruby’s lovely because it allows me to apply that principle everywhere, so is Smalltalk, and Perl 6 (when it gets here). The fact that Java’s classes are closed in this way is just one more bullet point on my ‘Why I don’t code in Java’ list.
As for the “What’s Ruby’s Array doing with push/pop and shift/unshift?” question. If it bothers you that much, try pronouncing ‘Array’ as ‘Deque’.
Bwah hah ha ha!
This evening, I acquired a commit bit for the Typo project. I’ve been chucking the occasional big patch and ideas at Scott Laird, the most active of the current maintainers, but he’s been busy doing all sorts of stuff, and he just started working at Google (go Scott!) so he doesn’t have quite as much copious free time as before.
So, this evening I asked for, and received, my very own commit bit. Which means I can make changes to Typo without needing to funnel them through a maintainer—I’m now part of the funnel.
Of course, responsible programming means I’ll still run big changes by Scott and the other maintainers, but it’s also useful to be able to make the kind of small changes that sometimes need to be made without it blocking on a busy man.
Wish me luck.
Testing Your Assumptions
One of the joys of writing applications using Ruby on Rails is the way the framework is constantly evolving better ways of doing stuff. It’s one of the dangers too.
Each release of Rails brings new and groovy features to the table, so it’s nice to stay close to the edge. However, when you do that, a change in the framework can bring your whole app crashing down because a key assumption you made turns out not to be true any more.
This is especially common when the assumed behaviour is undocumented, or a surprising consequence of behaviour that is documented.
When this happens, your test suite can have failures everywhere; it’s hard to work out precisely what the underlying problem is.
So, what to do?
Easy. Write tests that express your assumptions. It’s always good to make assumptions explicit. It’s better still to make them executable. Whenever you use something that’s a little bit surprising or hard to find in the docs, or when you do something that works around a bug, write a test.
Then, when the framework changes and tests are failing all over the place, test your assumptions. Hopefully there’ll be one or two failing tests there that point you to a solution to your problem. If there aren’t, then at least you know what’s not causing the problem. And, when you do find the underlying problem, you can add the new assumption to your tests for later.
I’ve just been working on Typo and found (rather usefully as it happens) that, if you add counter caching to a model, the model that holds the cache field will call all its save/update hooks whenever the cache field is updated.
Which is a little odd; it could be argued that simply adding what should be an ‘invisible’ cache field to a model shouldn’t go changing its behaviour in this way. So, I’ve created an assumptions_test.rb file and written the behaviour up as a test. Now, if David Heinemeier Hansson decides that this is undesirable behaviour and changes it, we’ll be able to see what’s going on.
The idea (as is so often the case) comes from Kent Beck. In this case, from Test Driven Development. One more book by Beck that deserves to be on every programmer’s bookshelf.
Older posts: 1 2
