Thoughts on Object-Orientation vs. Ease-of-use

I was reading Giles Bowkett’s “Rails as she is spoke” and it really opened my eyes about how some of the ways that Rails diverges from OO orthodoxy make for a better user experience.

Interestingly, he gives the example of url_for being defined in 5 places that Ernie Miller also discusses in his talk: An Intervention for ActiveRecord. Giles wants to point out how convenient url_for is to use, as opposed to an OO version e.g. Url.new(foo), that it’s more LISPy. And I remember Ernie saying something similar, but that it really should be more OO under the covers.

Giles also notes that the rails controller does an awful job at separation of concerns, talk by by Gary Bernhardt

General Advice

  • Consider an object’s responsibilities.
    • Can you describe what it does without an ‘and’?
    • What would make it need to change?
    • Does any other object do the same thing?
    • Do its name and method names clearly express its intent?
  • Does it have a clear interface on how to use it?
  • Does it know too much about its collaborators?
  • What does it need to know about? What role does it play?
  • Is it tightly coupled with other objects or classes?
  • Is there a low cost to change? Is it open to extension? Can you close it to modification?

General Advice for Rails ActiveRecord Models

Common Objects Patterns in web apps

  • Move constraints such as authorization to your router
  • Use active support hooks e.g. ActiveSupport.on_load,
  • Make your own validations with ActiveModel::EachValidator
  • It’s better to use instance methods than class methods (which resist refactoring)
  • Domain object that often appear
    • ValueObject e.g. Rating, PhoneNumber, that can be used as a hash key (requires implementing :hash, :eql?). virtus is a good gem for this.
    • Service object, e.g. UserAuthenticator.new(user).authenticate(*args) to write data
    • Query object e.g. AbandonedTrialQuery.new(Account.scoped).find_each(&block) to read from db/sql
    • Policy object e.g. ActiveUserPolicy.new(user).active? to read state from objects in memory
    • Decorator e.g. FacebookCommentNotifier.new(comment).save to separate concerns, by layering on functionality to existing operations. e.g. instead of a callback
      • prefer:
        • decorators when
          • The additional behavior only runs sometimes – for example only posting to Facebook for users who signed up with Facebook Connect.
          • The additional behavior makes the objects harder to test. The smell here is that you need to stub out the callback or disable the decorator in wholly unrelated test cases.
        • callbacks if the behavior runs all the time and is simple enough to never feel the urge to stub it. Observers don’t fit in well to that ruleset.
      • see review of decorator facilities in ruby
    • FormObject/ViewModel/Presenter e.g. Signup.new(company_name, user_email, user_name).save to signup a user and add to a company, and also determines whether to show specific content and present notification In brief, ‘is a representation of the state of the view’. used for your rails view/template
    • View object e.g. DonutChart.new(snapshot) that respond to #cache_key
    • WIP: Command
  • Cache managing object
  • Method Object: takes in args and has a method that does the work
  • WIP: How to generate views templates for js or json, and how to serialize models
  • WIP: Logger or instrumentation stuff

References:

Notes from An Intervention for ActiveRecord