-
-
Save gma/3061489 to your computer and use it in GitHub Desktop.
class CardCreator | |
include Publisher | |
def create(iteration, attributes) | |
card = iteration.cards.build(attributes) | |
card.save! | |
rescue ActiveRecord::RecordInvalid | |
notify_subscribers(:create_failed, card) | |
else | |
notify_subscribers(:created, card) | |
end | |
end |
class CardPersistenceResponder < Struct.new(:controller) | |
def created(card) | |
controller.instance_eval do | |
redirect_to planning_path(card.project) | |
end | |
end | |
def create_failed(card) | |
controller.instance_eval do | |
@title = 'New card' | |
@card = card | |
flash.now[:alert] = 'Your card needs a title' | |
render :action => 'new' | |
end | |
end | |
end |
class CardsController < ApplicationController | |
def create | |
creator = CardCreator.new | |
creator.add_subscriber(CardPersistenceResponder.new(self)) | |
creator.create(project.backlog, card_params) | |
end | |
end |
module Publisher | |
def add_subscriber(object) | |
@subscribers ||= [] | |
@subscribers << object | |
end | |
def notify_subscribers(message, *args) | |
return if @subscribers.blank? | |
@subscribers.each do |subscriber| | |
subscriber.send(message, *args) if subscriber.respond_to?(message) | |
end | |
end | |
end |
Having just applied this refactoring to my app, I've had to deal with where to put the code for CardUpdater
. Do you merge CardCreator
and CardUpdater
, and what about the responder? Make separate ones for create/update?
Keeping CardCreator
and CardUpdater
separate makes a lot of sense, as my update logic is actually quite complicated. The responder stuff is pretty simple, so I've added an updated
callback to the existing responder. It feels nice.
I couldn't help feeling that the observer pattern is overkill given that I've only got one object that wants to observe it. But then I got to updating my unit tests, and enjoyed the fact that if I didn't add an observer to the creator/updater (in the test code) that it didn't mind at all. I didn't need to stub it; the callbacks just didn't run. I do like the observer pattern…
Since I made this gist I've written the process up: http://theagileplanner.com/blog/building-agile-planner/refactoring-with-hexagonal-rails
The two differences between this gist and the previous one (https://gist.github.com/3061327) are:
CardCreator
uses the EAFP idiom; try and save the card and handle the error, rather than ask if everything is okay, and