More tinkering with BetterNestedSet, and I think I’ve got the solution nailed:
First of all, use redundant columns to store the parent id. One is for your application to access, the other is for BetterNestedSet to handle – I called mine :parent_id and :bns_parent_id. You can tell BNS to use a nonstandard parent column like this:
acts_as_nested_set :parent_column_name => :bns_parent_id
Why two columns to store one piece of data? Well, as I discovered earlier, BetterNestedSet really doesn’t like you playing with its toys, so we’ll just give it a toy of its own. Then use an after_save callback to call BNS’s #move_to_child_of method; this will synchronize :bns_parent_id with the :parent_id that you’ve been setting in your controller, as well as letting BNS do its voodoo nested set magic.
So far, this is pretty much the same solution I had before, except using an extra database column instead of a temporary instance variable. So why is this solution easier than the old one?
Well, for one thing, you get the more natural :parent_id, and all the readability-enhancing niceties that come with it: #parent, #parent=, and so forth (although you’ll have to define those methods in your model yourself). Sure beats coming up with some new variable name (I’d been using :par), especially if your app was already using :parent_id before, e.g. with acts_as_tree.
The second reason is that it makes validation easier. I can now use :parent_id in my validations without any fuss:
validates_uniqueness_of :name, :scope => :parent_id, :message => "is already taken under this parent"
Some caveats: You can’t use certain BNS methods (e.g. ancestors, children, full_set) on new records. The reason for this is that BNS uses its nested set voodoo to find ancestors and children/indirect children, and new records won’t have that special mojo until after they are saved and move_to_child_of is run. Likewise for testing, you will have to save records, and reload their corresponding ActiveRecord objects, when testing relationships.
Now that I have BetterNestedSet tamed, tied up, and gagged, it isn’t so hard to use. Hopefully this post will spare someone out there the frustration that I had to go through!