Wednesday, January 30, 2008

ActiveScaffold reverse associations to models in modules

I really enjoy using ActiveScaffold, and encourage everyone to use it to generate administration style interfaces. I came across an apparent defect today that I wanted to share.

I have a set of models that are contained in a module, and I'm using AS to do administration for them. Mostly fine. However there's a problem when I click a link on an element of a many relationship that's being displayed in a list. In this image,


ranges is the result of a has_many relationship, and when I click on the range 'Summer Selection', I get this:


and the following stacktrace:

[sourcecode language='ruby']
ActionView::TemplateError (undefined method `reflect_on_all_associations' for Range:Class) on line #19 of vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:
16: # determine what constraints we need
17: if column.through_association?
18: @constraints = {
19: association.source_reflection.reverse => {
20: association.through_reflection.reverse => parent_id
21: }
22: }

vendor/plugins/active_scaffold/lib/extensions/reverse_associations.rb:26:in `reverse_matches_for'
vendor/plugins/active_scaffold/lib/extensions/reverse_associations.rb:12:in `reverse'
vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:19:in `_run_erb_47vendor47plugins47active_scaffold47frontends47default47views47_nested46rhtml'
vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:11:in `each'
vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:11:in `_run_erb_47vendor47plugins47active_scaffold47frontends47default47views47_nested46rhtml'
vendor/rails/actionpack/lib/action_view/base.rb:637:in `send'
vendor/rails/actionpack/lib/action_view/base.rb:637:in `compile_and_render_template'
vendor/rails/actionpack/lib/action_view/base.rb:365:in `render_template'
vendor/rails/actionpack/lib/action_view/base.rb:316:in `render_file'
vendor/rails/actionpack/lib/action_view/base.rb:331:in `render_without_active_scaffold'

The problem is that class should be MyModule::Range, not Range.

AS is looking up the class using code on ActiveRecord::Reflection::AssociationReflection, from the file activescaffold/lib/extensions/reverse_associations.rb. The fix (apparently - I haven't tested this exhaustively yet) is to change the code that sends "class_name.constantize" to the association to send "klass" instead (you need to do this in two places). The completed code is attached below. I hope this helps someone else!

[sourcecode language='ruby']
module ActiveRecord
module Reflection
class AssociationReflection #:nodoc:
def reverse_for?(klass)
reverse_matches_for(klass).empty? ? false : true

attr_writer :reverse
def reverse
unless @reverse
# Following line changed for compatibility with associations on classes in modules
# reverse_matches = reverse_matches_for(self.class_name.constantize)
reverse_matches = reverse_matches_for(self.klass)
# grab first association, or make a wild guess
@reverse = reverse_matches.empty? ? self.active_record.to_s.pluralize.underscore :


def reverse_matches_for(klass)
reverse_matches = []

# stage 1 filter: collect associations that point back to this model and use the same primary_key_name
klass.reflect_on_all_associations.each do |assoc|
# skip over has_many :through associations
next if assoc.options[:through]

## Following line changed for compatibility with associations on classes in modules
# next unless assoc.options[:polymorphic] or assoc.class_name.constantize == self.active_record
next unless assoc.options[:polymorphic] or assoc.klass == self.active_record
case [assoc.macro, self.macro].find_all{|m| m == :has_and_belongs_to_many}.length
# if both are a habtm, then match them based on the join table
when 2
next unless assoc.options[:join_table] == self.options[:join_table]

# if only one is a habtm, they do not match
when 1

# otherwise, match them based on the primary_key_name
when 0
next unless assoc.primary_key_name.to_sym == self.primary_key_name.to_sym

reverse_matches < 1



Uses of OpenID

I'm a user of OpenID, but this video from Google is an excellent introduction to the concept, the uses, and the pros and cons.

Particularly interesting to me as a developer were the ideas of using OpenID as a 'lightweight' authentication mechanism for sites that want a low barrier for registration and access, and recognizing multiple openids so that a site can access id-specific features from each. Highly recommended!

Monday, January 28, 2008

Rspec 1.1.2 and Textmate

A quick warning to everyone - if you upgrade to RSpec 1.1.2 and you use Textmate, you'll probably need to update your textmate bundle as well.

cd ~/Library/Application\ Support/TextMate/Bundles/

svn co svn://

Wednesday, January 23, 2008

Freezing gems with architectures

So I don't ever lose this reference again (hopefully), here's a reminder to myself (and maybe to you) that I want to freeze all my gems, and accommodate different architectures, so I should be using gems_with_architecture. See
usage, and browse the

Beanstalk Dashboard

Since I've failed to get the beanstalk dashboard to display in my comments, here's the "team leader" view that Mark was asking about in the last post:

Cogent Consulting Pty Ltd_ Dashboard — Beanstalk-1

Tuesday, January 22, 2008

Excellent support from Beanstalk

We've started evaluating Beanstalk for our subversion repositories, and I had a problem this morning trying to commit to my second repository (the detail is that I was getting the message "Can't create directory '/var/lib/subversion/': Permission denied"). Apparently this is a recurring problem on some accounts - probably a teething problem with the service.

Of course I'd rather not have any problems at all, but when I got this message I logged into the Beanstalk Campfire conversation, and within 10 minutes (9 of which were me getting a drink while Chris worked) Chris Nagele has fixed my problem and I was up and running again. Great stuff!

Wednesday, January 16, 2008

Interesting behaviour for defined?

I've been working with Pete Yandell's Not-a-mock plugin, which I like a lot, and this morning a failing spec led me to some interesting behaviour for the Ruby defined? operator. Try these two things:

defined? arbitrary_attribute

defined? arbitrary_attribute=

With Ruby 1.8.5 on my Mac, the second one fails! From experimenting, it doesn't seem like you can use any setter. The workaround (which I haven't confirmed with Pete yet) is to use 'self.methods.include?("arbitrary_attribute-").

Sunday, January 13, 2008

Don't be dogmatic about dogma

A colleague of mine recently passed around a reference to "The Way of Testivus" from Agitar, and as promised on the front page I did indeed find that it was filled with "good advice on developer and unit testing" [although personally I haven't found testing my developers to be so useful :-)]. But there was one page that I thought would be dangerous in the wrong hands, and it started like this....

Don’t get stuck on unit testing dogma

Dogma says:
“Do this.
Do only this.
Do it only this way.
And do it because I tell you.”

Dogma is infl exible.
Testing needs fl exibility.

Dogma kills creativity.
Testing needs creativity.

I've got a good grasp of what the author means, and I agree with the sentiment quite strongly. But I'm also confident that out there somewhere is someone new to automated testing who's saying "oh, I've seen how other people do testing, and how could they have gotten it so wrong? I'm going to do things completely differently - I'm going to be creative". The trouble with rejecting dogma is that "dogma" is often a great approach to 80% of the problem, and it often contains a great deal of wisdom that isn't obvious to a newcomer.

I'm all in favour of rejecting dogma, but only after you've understood why those ideas might have become dogma in the first place, and you're confident that you understand the reasons that particular dogma doesn't apply in your specific context. Otherwise you're just being dogmatic about rejecting dogma.

Monday, January 7, 2008

Installing Postgres on Leopard

Excellent article on installing Postgres from binaries on Leopard. Just make sure you have XCode installed first.