
Recently I struggled a bit to find an elegant and working solution to validate an associated model when destroying another model’s instance. The two models were Account and User and I needed to validate the Account whenever I delete a User.
The two models look like this - very much simplified:
class Account < ActiveRecord::Base
has_many :users, :dependent => :destroy
end
class User < ActiveRecord::Base
belongs_to :account
has_many :roles # these contain authorization roles, such as ‘admin’
end
The given rule to validate was:
An account must have at least one user that is an admin
This rule makes sure an account always has one administrator that can access all parts of the application.
When someone deletes a user within the app, and this user happens to be the only administrator, validation should prevent this from happening.
I could have implemented this in the User model, for example like this:
class User < ActiveRecord::Base
belongs_to :account
has_many :roles # these contain authorization roles, such as ‘admin’
before_destroy :make_sure_account_would_still_have_admins
private
def make_sure_account_would_still_have_admins
self.account.users.admins.count > 1
end
end
But I didn’t want to do this. Firstly I wanted to use ActiveRecord’s validations and secondly I wanted to put this logic into the Account model. After all, it’s the account that becomes invalid if there are no more administrators.
I added a custom validation and an association extension to the account model:
class Account < ActiveRecord::Base
has_many :users, :dependent => :destroy do
def admins
self.select { |user| user.has_role?('admin') }
end
end
validate :has_at_least_one_admin_user
private
def has_at_least_one_admin_user
if users.admins.count < 1
errors.add(:users, "must contain at least one user of type 'admin'.")
end
end
end
So, if an account doesn’t have a user with the ‘admin’ role, the account will be invalid.
Now I needed to get this validation triggered whenever someone tries to delete a user. The problem with this was, that the Account model would only be invalid after the user was deleted. If I used validates_associated :account, deleting the last administrator would have passed. That’s because Rails validations are run before the actual operation happens - and at this point the potentially last administrator I was going to delete still existed. E.g. if I ran @last_admin_user.destroy, the associated account would still be valid at validation time, because that user actually has not been deleted, yet.
So I thought, the solution would be to run the validation from an after_destroy callback.
Here, the part I struggled with was that after_ callbacks can only cancel transactions when they raise an error - returning false in an after_ callback is not enough. But of course, you don’t want to end up catching errors whenever you call a simple destroy on the user model.
It turns out, there’s a special error you can raise that doesn’t get passed on. It only causes the transaction to roll back. This error is:
ActiveRecord::Rollback
When I raise this error in the after_destroy callback, the transaction will be rolled back and I do not have to worry about catching errors.
The user model now looked like this:
class User < ActiveRecord::Base
belongs_to :account
has_many :roles
after_destroy :trigger_rollback, :if => "account.invalid?"
private
def trigger_rollback
account.errors.full_messages.each { |message| errors.add :account, message }
raise ActiveRecord::Rollback
end
end
This mechanism works beautifully for this use-case while keeping responsibilities in the models they belong.
You can’t build an office block and then decide you had better put in some concrete foundations afterwards.
You can, however build a large scale app and decide you want a different foundation along the way.
When you start an agile project, you “pitch a tent in dirt”.
E.g. write a simple app that lets users drag a ‘job’ - which only has a name and id - onto a timeline.
You then itterate, turning the the tent ever so slowly into the office block ( e.g. vWorkApp ). You get to revisit every aspect, and you must revisit it.
If you don’t, you’ll have an office block sinking into the mud.
I’m starting out with the “not terribly awesome” and perhaps even “that’s crap!” stuff. That is, you might think I’m losing my mind at this point. Remember these are things that I love about Boiler. You might not.
I promise you, though, that the final two articles in this series will be epic. I’ll show you how to extend Steam using ‘commands’ as an example. I’ll also show you how to integrate some syntactic sugar without making the framework brittle.
It’s sugar that I want to discus in this post.
Mediator#addViewListener makes me think that Mediator is-a view listener.
Mediator#addContextListener makes me think that Mediator has-a context bus AND is-a contextListener.
In Steam, neither is true. Mediator is-a class that expects to be told when a view of a specific type has been added to the stage. That’s all it is, period.
Without sugar, there is no Mediator Interface, only duck typing as I explained in the previous article.
It’s all about SRP
This ones pretty fluffy, purely a matter of taste.
Again with the Mediator sugar from Robotlegs, I would usually start typing:
addContextListener(MyEvent.EVENT_NAME,eventNameHandler);
IntelliJ would highlight the ‘eventNameHandler’ so I could select ‘create method’. Which is great. I can automatically stub out:
public function eventNameHandler():void {
}
That’s not bad… Except if I had used:
myContextBus.addEventListener(MyEvent.EVENT_NAME,eventNameHandler);
IntelliJ would offer to stub out:
public function eventNameHandler(event:MyEvent):void {
}
Boiler want’s us to avoid hiding language conventions. Kthx?
Haha! That’s gunna turn some heads.. I hope.
Herein I set out to explain why I’m building Boiler. Other than the obvious fun of writing a framework, I have some very specific reasons.
I could be wrong of course, Robotlegs is so good (and it’s well past the alpha stage, unlike Boiler) that it’s a tough call. However, why would I bother if I wasn’t trying to improve on something awesome?
Imagine this is a mediator I just wrote:
package views {
public class WidgetMediator {
[Inject]
public var notifier:IEventDispatcher;
private var view:Widget;
public function register(view:Widget):void {
this.view = view;
view.addEventListener(MouseEvent.Click, handleClick);
}
public function deregister():void {
view.removeEventListener(MouseEvent.Click, handleClick);
}
protected function handleClick(event:MouseEvent):void {
notifier.dispatchEvent(event);
}
}
}
I consider this class to have all the markings of a mediator. It’s name ends in mediator, that should be enough… But there’s more. It’s in the views folder where a lot of folk stash their mediators. It expects to be registered against a view:
register(view:Widget)
Despite not inheriting from a Mediator Interface, it’s a mediator. In the land of Ruby, they call it ‘duck-typing’. It looks like a duck, quacks like a duck, it’s a duck.
So in Boiler, we use conventions to invert control. When you start using Steam, you follow convention to describe the intent for a class. Steam, by the way, is the reference desktop framework for Boiler, like MVCS is for Robotlegs.
The above mediator needs only be ‘Mapped’ using SwiftSuspenders and Steam will configure injections and view mappings ‘because it looks like a mediator’.
In Boiler:
lifetime.mapClass(WidgetMediator,WidgetMediator); // A little note on the first param later.
In Robotlegs:
mediatorMap.mapMediator(WidgetMediator);
It’s a tiny difference, really, except now we don’t have to inherit from anything to look like something. Later I’ll show that we also don’t let ourselves get caught by inheriting from sugar classes.
The first thing that Boiler gives you, is no more polymorphic dependencies that only serve to duplicate intent.
When you want to extend the framework behavior, remove behavior, or use certain behavior in only some modules, the benefits of avoiding framework polymorphism become clear. Later on, I’ll demonstrate this with examples that extend or modify the framework.
Why does SwiftSuspenders require an interface separate from the class to instance? This example is a mediator, all of it’s dependencies are injected. It’s never injected into another class. Where it’s built is where it’s stored, in a generic, testable class… So I can’t see a problem with lifetime.mapClass(widgetMediator);
I’ve been away for a while in the land of the Ruby Object model and Rails. Upon my return to some Flex development, I decided that, although Robotlegs MVC ‘Controllers’ are really nice, they require too much boilerplate. One thing I found in RoR, is that although they use static controller classes with many command methods, most folk follow convention and avoid introducing wads of business logic and tightly coupling their code.
RL-MVC Commands enforce loose coupling. It’s really hard to end up with complex, tightly coupled controllers when every ‘action’ is a short lived class.
Commands are dynamic. You can change the existing command set on the fly. I’m not sure where this is useful, I don’t take advantage of it, but that’s not to say that other people do not.
With awesome power comes diminished responsibility. Yeah, that decoupling that Commands give you, removes the need for you to pay as much attention to OO and MVC best practices. Instead, you pay the price by having quite a lot of boilerplate:
One command class per ‘action’ (read: method)
I never did mind this tradeoff. Now It’s wearing thin. Our app is big, and there is a lot of boilerplate. After spending some time learning to write Ruby DSL’s it’s really grating to see that a simple and sensible idea reads so badly in code. If I was in Ruby I would have something like:
# controller_map.rb
map RubbishReminderEvent, PutTheRubbishOutCommand
# put_the_rubbish_out_command.rb
class PutTheRubbishOutCommand
def initialize
house.nature_strip << house.rubbish_bin
end
end
Minimal boilerplate, just intention. Actually, with a decent DSL, you could do away with all of the boilerplate.
However if you look at Rails, events (http requests) are routed by “concept”. In the de-facto Rails convention a “concept” is a resource, but it does not need to be. The ‘routes’ tell Rails how to map incoming events to controllers.
In Rails, the controllers are classes, by “concept”, with action methods. This runs the risk of being abused, and it sometimes is. However, people who care do not make a mess of their controllers. So I decided to assume my developers are good people, and make a compromise to remove some boilerplate.
As a reminder, this is the boilerplate I’m talking about
import .... // boilerplate
public class PutTheRubbishOutCommand extends Command { // Boilerplate
// Boilerplate
[Inject]
public var event:TimerEvent;
// Often repeated, not 'DRY'
[Inject]
public var rubbishModel:RubbishModel;
// The only bit of actual 'intent'
override public function execute():void {
rubbishModel.putRubbishBinOnNatureStrip;
}
}
I propose using a Static Controller. Here, “static” is an antonym of “dynamic”, not the keyword
This is more like a Rails Controller which groups ‘action methods’ and reduces boilerplate:
public class RubbishController extends Controller {
[Inject]
public var rubbishModel:RubbishModel;
public function RubbishController() {
addMethod(TimerEvent.RUBBISH_NIGHT, putTheRubbishOut);
addMethod(HomeOwnerEvent.GO_TO_WORK, bringTheRubbishIn);
}
private function putTheRubbishOut(event:TimerEvent):void {
rubbishModel.putRubbishBinOnNatureStrip()
}
private function bringTheRubbishIn(event:HomeOwnerEvent):void {
if (!rubbishModel.rubishBinIsOnNatureStrip())
return;
rubbishModel.putRubbishBinRoundBack();
}
}
I think this looks good. At this point I should apologise to Jesse Warden for admonishing him when he suggested to a conference room at FATC that it was o.k. to do this.
I still think it’s not “o.k.” because if you don’t grasp the value of seperation of concerns, you will make a mockery of this approach to controllers. If you’re starting out with Robotlegs, do not use this approach!
Extra reading: How I aim to implement the static controller
The heart of a Static controller is Controller which looks a bit like this:
public class Controller extends Actor {
public function dispose():void {
eventMap.unmapListeners();
}
protected function addMethod(eventType:String, callback:Function, eventClass:Class = null):void {
eventMap.mapListener(eventDispatcher, eventType, callback, eventClass);
}
}
All it does is give you the ‘addMethod’ event and allows ControllerMap to tear it down via ‘dispose’ as the context dies.
addMethod is a similar to CommandMap#mapCommand, except it doesn’t map a class, it maps a method.
Also, I need to extend Context to provide/manage ControllerMap.
ControllerMap collects and disposes of Controllers. It won’t do much more than:
public function mapController(controllerClass:Class):void {
injector.mapSingleton(controllerClass);
controller:IController = injector.getInstance(controllerClass);
controllers[controllerClass] = controller; // for teardown later
}
I realise that some people may consider the ‘routing’ and the controller to be different responsibilities. As it stands, I cannot figure out a way of keeping routing seperate, because the Controller#actionMethods have to have a ‘thisObject’ of Controller. It would mean a really dopey looking router method like this:
addMethod(TimerEvent.RUBBISH_NIGHT, RubbishController, RubbishController.putTheRubbishOut);
Which would get worse when you need to enforce the event type:
addMethod(TimerEvent.RUBBISH_NIGHT, RubbishController, RubbishController.putTheRubbishOut, TimerEvent);
yick
We’re seeing a lot of to-ing and thro-ing about Agile, it’s success and failure. There are a lot of blogs, posts and tweets which let anyone who wants to, throw it away and dismiss it out of hand.
From our fledgling point of view, the point is being missed.
To work, Agile must be all about allowing a team to do what it naturally wants to do.
That’s it in a nutshell. You can’t use Agile to get a team to toe the line. You can only use it to nurture the natural instincts of a team.
So be very careful about the team you pick. Cultivate a team that will align itself with the business goals, often something like:
The team’s instincts will likely include pride. So your business needs to add to its goals:
Then really, Agile is just a matter of trusting people. If your ecosystem cannot support trust, you will probably embrace micromanagement.
We’re not being cheeky, Agile is not the only way to success or failure. But Agile is our only way.
Why is it so hard to find ruby conventions? It’s the biggest aspect of the community, isn’t it?
© 2010 - VisFleet Ltd
No prawns were harmed in
the making of this website
Comments