Skip to content

Instantly share code, notes, and snippets.

@gmoeck
Created July 11, 2012 06:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save gmoeck/3088352 to your computer and use it in GitHub Desktop.
Save gmoeck/3088352 to your computer and use it in GitHub Desktop.
class CommentsController < ApplicationController
def create
translator.translate params
end
private
def translator
CommentRequestTranslator.new(processor)
end
def processor
comment_processor = CommentProcessor.new
comment_processor.add_listener twitter_poster
comment_processor.add_listener facebook_poster
comment_processor.add_listener comment_persister
comment_processor
end
def twitter_poster
TwitterPoster.new
end
def facebook_poster
FacebookPoster.new
end
def comment_persister
SQLCommentPersister.new
end
end
#example class
class CommentProcessor
def initialize
@announcer = Announcer.new
end
def add_listener(listener)
@announcer.add_listener listener
end
def new_comment(comment)
new_comment = comment.with_language(language_for(comment.body))
.with_spam(is_spam(comment.body))
@announcer.announce('new_comment', new_comment)
end
end
@mattwynne
Copy link

Thoughts:

  • I don't think you need a CommentRequestTranslator, I think you just need a CommentRequest, though if I could see the code for that thing I might think differently
  • I think that a fan-out listener model is the right design in this instance, but there must be a neater, more Rubyish way to wire them up - the #processor method is very noisy.
  • Maybe it's better if the CommentProcessor takes the announcer in its constructor, and then it doesn't have any responsibility for adding listeners.

@solnic
Copy link

solnic commented Jul 11, 2012

I love the idea of having a request translator. Having AR objects dealing with params directly has been always bugging me. It's such an ugly part of Rails IMHO.

@therabidbanana
Copy link

I've been moving to something similar to this in my app, two questions I'd like some input on:

  1. In our app, the processor's job would include persisting the new comment (through a repository) and announce either the success or failure of that persistence. It does introduce a construct I'm not completely happy about:
    if repo.save(comment)
      @announcer.announce('new_comment_success', new_comment)
    else
      @announcer.announce('new_comment_failed', new_comment)
    end

But it seems important to verify persistence before announcing to Facebook and Twitter - having persistence be just another listener that the others are completely unaware of seems weird to me. How would you only announce to Facebook if the comment successfully saved, or should this not be a concern? (I'm thinking of refactoring to pass the announcer on to the repository and let it do the announcing of the save status.)

  1. Our app also has the Processor authorize and announce authorization failures before doing the processing, as well as data validation - seems like these before filters should probably be a separate concern, but should they be left in the controller, or would you add them in somewhere else? I kind of like the idea @mattwynne has in his fork of a facade - maybe I'd put validation/authorization in there before calling the processor?

@gmoeck
Copy link
Author

gmoeck commented Jul 11, 2012

@therabidbanana I think that depends on the actual domain. In my mind all of the validation of the values and such should already have taken place, and the persistance layer should only be responsible for mapping the comment into the database. If that's the case, for something like comments which are strictly additive I see no reason why the actual persistance should block the rerendering of the page. The actual persistance itself can be totally async, as should the "persisting" to Facebook and Twitter. If we were worried about someone clicking the Twitter/Facebook link and not seeing the comment, we could always have the persister announce like your saying, and have FB/Twitter listen to that.

@therabidbanana
Copy link

@gmoeck I can definitely see that, the actual domain definitely changes things - but in the case where persistence is async, where would you put validation, and make sure posts to fb/twitter only happen with valid comments? Would you just let them all call .valid? on the comment, or would you place that logic elsewhere and only announce new_comment when valid?

@gmoeck
Copy link
Author

gmoeck commented Jul 11, 2012

@therabidbanana I would put a service that was responsible for comment validation in front of the processor. The processor would never receive a comment that wasn't valid, and it wouldn't need to worry about that, since validation seems to be a separate responsibility to me.

@therabidbanana
Copy link

@gmoeck That gets to my second question: where would you put those services that should live in front of the processor and keep it from firing? Leave them in the controller, or is there another object living somewhere in there that runs validation/authorization services before calling the processor?

@gmoeck
Copy link
Author

gmoeck commented Jul 11, 2012

@therabidbanana I would do that by composition. I would make the validator implement the processor contract, and have it proxy the request to the processor when it is valid, and do something else when it isn't.

@witoldsz
Copy link

Right now this is the controller calling the processor. After the change, proposed above by Gregory, controller would call the validator which would call the processor, so at the end, the controller would:

  • create a processor (as it is now)
  • create a validator, passing in the processor (so validator would delegate when everything is OK)
  • pass a comment to validator.

But that would introduce a processing logic into controller (or would it not?). What if there are two more controllers using a processor right now? They would still be passing a comment directly to processor instead of validator... :-/

Is that not a problem that the controller is actually creating the processor instance? Making the validator implementing a processor contract won't help here. Shouldn't controllers ask someone (a comment processor factory?) for an instance of processor, so your solution with adding a validator into the processing chain would "kick in" everywhere?

@gmoeck
Copy link
Author

gmoeck commented Jul 19, 2012

I would agree that it can be somewhat of an issue to have the controller creating the instances of the objects. I'd generally move the creation of the objects into an infrastructure layer and then just have the controllers call into the domain. I would often however still create a "translator" or "adapter" within the controller, and pass the domain into its constructor when translation from the network request into several messages to the domain is necessary.

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