The problem
Within my 10 years of developer experience, over and over again I see an unnecessarily complex code: methods span tens of li...
For further actions, you may consider blocking this person and/or reporting abuse
Thanks for sharing this, Dimitrii, popped up in my google feed. I'll read through on Monday, but talking about how to factor Rails code beyond MVC directories is sorely needed.
OK, here are my thoughts after having read the post. Once again, thanks for sharing, Dimitrii.
initialize(.., start_date: Date
orinitialize(.., start_date: String
. This may vary from situation to situation. There are no solutions, only trade-offs.Thank you very much for such a detailed feedback! Let me try to address all your points one by one
On the data model issues
Agree, the data model doesn't seem good to me as well, but I'm not the one who modeled it and I don't know all the details. Maybe they had their reasons, maybe that's only a part of the picture, who knows?
On a presentation/application boundary
I'm glad we're on the same here! I usually also split the UI responsibilities a bit further,into user-facing part (the controller action) and application-facing part (Action class). You can find an example of such a class here
On where to place authorization
Agree, authorization is a corner case. I'm still not 100% sure here, but my current thinking is that the permissions/roles are a part of the application logic and thus should stay within it, not outside. Rails ecosystem provides gems with simple mechanisms to authorize right within controllers, but having it there forces controllers to know about your application logic and system state, which creates a coupling between your presentation and application layers, which I'm not a fan of. This might be good at the start, but bad later when it comes to organizing the code into subdomains, cause controllers will know too much.
But once again, I'm not 100% sure. I've never developed a really complex authorization system, and as for the case you've mentioned, it'll be pretty easy to implement it right within the operation:
Sorry, I need to board the plane, will continue later
The date param.
I think you're right, it is a side effect and it should be parsed in the controller. God, such a minor thing for this example and such a lot of considerations! I've already thought about controller's responsibility to set request context (timezone), parsing webhook data being a responsibility of an infrastructure and not the application layer, complex inputs that doesn't map to ruby data types and should be parsed within the application layer, benefits of unification, benefits of simplification and delaying the decisions...
On passing params through an initializer
I understand why it seems cleaner to move the arguments to an initializer, but my decision to pass arguments through the #call method is deliberate. The main reason for this is that I reserve the initializer for dependency injection and configs. If eventually the logic in
get_slots
will become so complex that I'll decide to extract it into a separate class, or if the slot size will become a part of the application configuration, they'll become external dependencies of this class and I'll inject them as following:or
(I never use instance variables directly cause they are read-only and there's no mechanism in Ruby to protect the instance variable from being changed when addressed with @)
The two benefits of this approach are:
#call
acts exactly as a proc:On passing params through an initializer, cont.d
To be frank, I think you're complexing things by drawing a distinction between "dep-injections and config" VS "real params". All that underscore-prefixing.. Embrace KISS, Power of One etc. etc. Kwargs give you all the flexibility you need.
And I don't follow your point about tests, init params are just as easy to specify or omit. In my example, you could even define an ad-hoc querier class to inject:
Update: Having watched Tim's presentation, I see why the desire to have init-call param split. I cooked up a response why it's a bad idea.
This is a great refactoring story, thanks for sharing!
I do, however, similar to @epigene, disagree with parsing the date from string inside this business class. It's a detail coming from infrastructure. The object should receive already parsed
Date
object.To illustrate the point: let's assume this get called from a webhook from some service that passes the date as a UNIX timestamp. Now you need to change your business logic modelling class only because of that, but the actual logic does not change.
I like it when the object could operate on it's data, rather than being acted upon. From that point of view the end result is an anemic biz object
Thank you for your feedback. The approach I'm using is definitely not common, that's one of the reasons I'm sharing it. Same topic was popped up by a person under my announcement of that post on reddit, I'll leave the link here, feel free to join the discussion.
reddit.com/r/ruby/comments/1cd3i1l...
I think it's more common than you think ;) I have seen similar approaches in pretty much every more mature Rails codebase I worked on.
Talking about anemic business object is a big misunderstanding what's going on in this code. It's not a business object, rather a business transaction wrapped in OOP code. Nothing wrong with that and DDD books describe this approach too.
I'm glad to hear that ^_^ In 80% of my past jobs the legacy code I've inherited was anything but readable
Thanks, I usually do also this
I find this usefull