Skip to content

Instantly share code, notes, and snippets.

@gma
Created July 6, 2012 17:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gma/3061489 to your computer and use it in GitHub Desktop.
Save gma/3061489 to your computer and use it in GitHub Desktop.
Exceptions and the observer pattern
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
@gma
Copy link
Author

gma commented Jul 6, 2012

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
  • Rather than passing a listener it uses the observer pattern.

@gma
Copy link
Author

gma commented Jul 6, 2012

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…

@gma
Copy link
Author

gma commented Aug 10, 2012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment