Wrapping Rails: A Work In Progress

[This post is long and meandering... apologies. But such is the state of my mind and reason for posting it. I'm trying to work out some alternative approaches with Rails, so these are not intended to be how-to examples... more background on our evolving process and links to the influences whence they come. All this related junk is spinning 'round in my head, and if I don't get it down in a readable and refutable format, I won't be accomplishing much in the sleep department for several days.]

The Code Smells

As we work on a few new Rails 3.1 projects, I keep rubbing up against things that just feel a tad… off to me. Some of them are things I’m just now realizing have made life less fun. Others have always bugged me, like messy, untestable business logic in the views (any conditionals, loop logic or awareness of model internals… form helpers, for instance).

In the second version of Capocus!, Norbert and I came up with a PagePiece concept where all that logic would take place in objects between the controller and view layer of Rails. It wasn’t very refined, and I don’t think we were even aware of Martin Fowler’s Model-View-Presenter pattern at the time (or at least I wasn’t). But PagePiece was roughly a Presenter concept except it wasn’t bound to a domain model. Basically, our idea was just smart helper objects that delivered partial views based on logic internal to the inheriting PagePiece class.

module PagePiece
  def self.included(base)
    base.delegate :current_user, :logged_in?, :has_permission?, :to=>;'(controller or nil)'
    base.send :include, InstanceMethods
  end

  module InstanceMethods
    def initialize(details={})
    @page = details[:page]
    @controller = details[:controller]
  end

  def page
    @page
  end

  def controller
    @controller
  end

  # then some methods which checked authorization privileges
  end
end
# app/pieces/admin_menu_bar.rb
class AdminMenuBar
include PagePiece
  def render_options
    can_edit? ? {:partial =>; 'layouts/admin'} : {:nothing =>; true}
  end
end

That’s a really simple example, but of course the logic could get as rich as you like… switching to different partials for different user roles, perhaps. Whatever you like. What was cool about it was you could do this in the view:

# some haml template

= display AdminMenuBar

So, our application template would look something like this:

# app/layouts/application.html.haml
!!! 5
%html
  %head
    %title = display Title
    = stylesheet_link_tag "application"
    = javascript_include_tag "application"
  %body
    = display AdminMenuBar
    = display Messages
    = yield
    = display Footer

Which was nice. No logic. Brevity. Clarity. But we still had some logic residing in the partials called by PagePiece objects for things like loops and form helpers. So that was a bit irksome. And things could get messy in that app/pieces directory if you weren’t careful, since the objects didn’t track cleanly to the MVC dealio… sorta like the JavaScript directory before Assets Pipeline came along.

So, now we have Mustache, which I’ve yet to implement, but I really really like. It solves the remaining logic concerns for us (with a little additional help regarding forms with some approach like this, or better yet, this). So that takes care of the logic in view templates, at least. And we get some nice, reusable templates for JavaScript actions to boot. Plus, I still get to use my beloved HAML. I hate writing HTML… so, sue me.

Your Dill, Vinegar and Cucumber In My Pickle

But there are still a couple of irksome things. It’s an old argument, but Rails MVC works great… until it doesn’t. Development is fast… until it isn’t. Once you have a complicated domain entity that doesn’t track cleanly to the ORM approach of ActiveRecord, things get a bit cloudy in your “model”. And in this house, we’re mostly using NoSQL these days anyway (Mongoid currently) so ActiveRecord isn’t something we have to deal with or care about (for better or worse). And that has brought out the code smells for me. What I’m realizing is I need clarification of duty wrappers.

There seems to be a lot of benefits to wrapping. Corey Haines did a wonderful talk on speeding up Rspec unit tests by simply staying in Ruby and ignoring Rails… as mother nature intended. So, he’s basically creating delegate classes, a concept I recently started wrapping my head around as I learn Objective-C for our iOS app development. Nicholas Henry is blogging pretty extensively about very similar wrappers for domain logic and has put a lot of good thought into it in my opinion (more on that in a bit). Details aside, he seems to be heading toward creating services, which is something Pat Maddox gets into in his talk on constraint-driven changes to Rails development approach. And Attila Domoko is kind enough to provide his wrapper approach.

Then there is the whole conversation going on now about framework decoupled (if not agnostic) development spurred by Uncle Bob Martin’s short video on framework prudence. The argument against that idea, expressed by a commenter, being you should either trust your framework or build your own. I don’t buy it. I love Ruby and Rails. But, I have some seriously coupled Rails 1.2.3 apps still running that I’d love to get into Rails 3 (SAME FRAMEWORK, RIGHT??!) and can’t upgrade without considerable redesign (starting over would be faster!). That’s my fault. I didn’t insulate my app from the framework. It has nothing to do with trust. Things change. We find better ways of working. So, I’d rather make my life easier in the long run… even if that means a few extra steps and potential “over-engineering” for me now (I’m good at over-engineering… or bad about it. Not a poster-child for pragmatism in that department. But I am getting counseling and we’re hoping for the best). And anyway, the extra steps to insulate my app will quickly add up to a cost of null (if not turn into a big asset) once I have faster running test and a more agile codebase. Especially once the process is down, I’ve meta-programed away duplication and built whatever generators I need. See, I’m doing a damn good job of selling this to myself.

So, let me define what I want…

I Wanna Be Free

From a broad perspective, these are the goals:

  • Insulation – Separation of Concerns which are agnostic of the framework and data structure
  • Domain Clarity – A clear, view-layer-agnostic API for the domain with no required knowledge of internals.
  • Reasonable Test Coverage – Full stack Cucumber acceptance test of the domain, but fast, pure Ruby Rspecs to drive development.

The Stepping Stones

So, here’s the development cycle I’m moving toward (based largely off pains discovered by NOT doing it this way)

  1. Start with declarative Cucumber scenarios to help discover the domain (focusing on API concerns, not view or business logic!)
  2. Quickly jump into Rspec to test-drive behavior behind the API, handle failures and deal with edge cases
  3. Once the API is stable, refine the view templates (if you need http) and format JSON, XML, RSS, PDF, etc. output as needed.
  4. Extract all framework or database specific code into isolation wrappers, point the tests at them, get the tests fast (no Rails)
  5. Repeat for all domain concerns

Peeling The Onion

Cool. Process is down. Now what am I dealing with? I have a gut feeling for how my concerns are separated to achieve both the goals and process I desire, but I can’t be sure until it’s put into practice. I reserve the right to change my mind (and you are welcome to set me straight if I’m talking crazy here), but it falls something like this:

The Data Model

So, the Rails Model? Not really a model, is it? I mean, it can be… if you want to be bound to both the framework (or its current version) and the database you chose at the project start. I have a feeling Rails will change, and I’m almost certain that I’m uncertain MongoDB will be my preference for this or every other app down the road (and hell, I may well want to use multiple databases for different purposes).

So, I’ll instead call this layer the data model. That means, only framework and database specific things go in here. It should not contain any model behavior which isn’t for some reason necessary for the framework or database. So, probably to be kept rather lean. After all, it’s the part I’m going to have to change down the road if I migrate frameworks, databases or even the mapper. I want the least work there possible. Some will be unavoidable as different databases use different find and relational strategies (those nasty custom SQL queries, yep… they go here), but the goal is to extract any of that beyond model specific scopes to a mixin… most mappers cover this pretty well as is.

The Domain Model

This is the juicy bit. Fat model, sure… but only for the stuff it needs to know about. NO VIEW RELATED LOGIC. The view has nothing to do with the domain entity. NO KNOWLEDGE OF OTHER ENTITIES. Person does not need to have inherent knowledge of AccountBalance. And there shouldn’t have to be a direct correlation between the domain model and a single data model.

For example, we’ve learned over the years that Users and People are very different things. The User model is about access (authentication and authorization) and that’s it. A Person is just that… it describes what a person is within that domain. You may have people in the domain who have no access to the domain (buyer contacts, perhaps). So, it’s silly to deal with access conditions when you don’t need them and it’s equally silly to treat users as some different kind of person. So, we delegate or use whatever method we desire in our data models to unify User and Person when we need both. But Rails’ MVC makes this sorta cloudy from a domain perspective. The domain model should only care about me (THE User) and people (all other people I am not). So, My domain model’s API should structure things likewise. And that will include a bunch of different Value Objects (as Iain Hecker described a year or so back in his article on Domain Driven Building Blocks), which are things like Address, PhoneNumber and of course AntidisestablishmentarianismStance. I suppose User is actually a Value Object of Person which deals with access… but I digress. Again.

Point is, how I organize data behind the scenes has zero value for the domain. It wants what it wants… it’s like the heart that way. *swoon*

Services

There’s been lots of talk in the last year or so about the concept of services. Their purpose, to my mind, is to deal with any task that is beyond the scope of a single domain model or involves procedures the domain model shouldn’t care about. Some examples might be RegistrationMailService, PayrollService, MessageNotificationService, etc. where the service may be interacting with a single domain model, several domain models or none. They are really models on the same level as an entity — at least, looking outside-in they should be — with their own API that can be called by a controller. Making these type of procedures stand-alone makes a lot of sense. It simplifies my domain models, it decouples domain models which otherwise might have had to interact within the “fat” model and it makes isolated testing of the service easy.

As I mentioned above, Nicholas Henry has talked of his approach to domain modeling which he splits into Facade and Use Case, which I rather like. Facade would be what I’m calling “Domain Model” and Use Case what I call “Service”… I like my terminology better. Seems more explicit and easier to follow… but a horse is a horse, of course.

Note: Likewise, some have used the “Presenter” term in this realm too… which is equally confusing to me as the name implies — though does not mean — a view context the domain/service layer should not have. More on that below.

The Controller

So this is the Sergeant Schultz of our onion… “I hear nothing, I see nothing, I know nothing!”. Okay, it knows how to route and what domain model method to call. Maybe that was a bad analogy. Regardless, It’s just a dumb traffic cop (no offense to smart traffic cops). This has become the Rails Way™ more or less, and Corey Haines has talked on numerous occasions about his rule that controllers cannot interact with ActiveRecord. Totally agree. So, somewhat conveniently, we can just call to our new domain model layer.

The View Template

I’m going to skip over a layer and jump to the end for a second: The View Template. As I discussed waaaaaay up at the top of this opus of mind-spew, view templates should be dumb dumb dumb. No logic. It spits out either formatted json or html (or rss, xml, etc.). And preferably with the same template for all (which, again, I hope to find Mustache an aid in this effort). What’s important is that, just as I want the domain model framework and database agnostic, I want the view UI agnostic. iOS, Android or other mobile device app hitting an API? Web browser hitting http? ExtJS, jQuery or whatever other layer you want to build a http web/mobile GUI with? A reader client pulling an RSS feed? Great. All the same to me. Heck, Gem up several UI approaches and give developers options.

Note, above I said view templates should be dumb. I’m not saying the view object should be dumb. While I know Rails technically has a view object, its kind of a view object by convention. We have little control over it and end up mixing logic in the view templates in favor of a little convenience. And that’s fine… until it’s not. I want to be able to quickly unit test view logic and make sweeping UI changes without having to worry about the logic therein AND without needing to know anything about the application internals. So… not having a view controller of that nature, I suppose we need one.

The View Object(s)

As I alluded at the onset with our old PagePiece thingy, there needs to be something that deals with view logic that is itself a testable object. People are using Decorators and View Presenters for this purpose. There seems to be a real lack of agreement on what one or the other is. But here is how I’m currently thinking of them (you may disagree):

  1. Decorators remove view formatting from the domain model or service
  2. View Presenters control the presentation of elements on your page for any given context

They are very different things. And then we may even have a third category of view object: View Resources. I know… freakin’ onions and their layers! I’ll explain…

Decorator Usage

As I understand the decorator concept, the idea is to take any view related methods out of the model… like time formatting, “fullname” methods which combine several fields, etc. Excellent… that stuff always bugged me sitting in the model. Draper is a pretty cool looking gem which uses the decorator approach. I’m not sure it will work with my abstracted domain models, but I’m going to give it a shot.

View Presenter Usage

The view presenters, how define them, are very much like our old PagePiece concept above. Given a context (my role, the current url’s section or subsection, my preference settings, etc.) should I show this bit, that bit, or no bit?

View Resource Usage

I don’t like Rails helper methods. I rarely use them… and feel dirty when I do. Just seems like — Ruby being an object oriented language — we should be working with smarty-pants objects. So View Resources are the smarty-pants alternative to Helpers. Really, you could consider Decorators and Presenters as I define them as View Resources as well, but I separate them into their own categories due to their specific behaviors. View Resources is essentially a catch-all.

This is where things like Form Backing Objects fit to me (see Jeff Dean’s article on form backers). Jeff suggests these fit in the other (non view) Presenter. Which, may make sense in their apps, but for me that would mean it is a service, which it clearly is not. Services are revealed through the domain. The user is blissfully unaware of any behind-the-scenes nonsense that makes a form work, as they should be.

It’s possible that View Resources could be mixins for Decorators or Presenters. I’m not sure how I’ll approach that yet. But in the Form Backing Object instance, it makes sense that the basic behavior could be extracted to a mixin and then the specifics of the form supplied by the domain model’s decorator, since that is a view relationship directly tied to the domain model.

Structure

Nobody likes a bunch of junk randomly stuck in lib/ worse than I. So we need to pull this all into conventional app sub-directories. We’ll leave the rails default be, of course. No need to reinvent the wagon wheel:

app/assets
app/controllers
app/helpers #bleh
app/models #remember, these are my framework specific data models
app/views #should be templates, but whatevs

and now my additions:

app/domains
app/services
app/decorators # likely mapping conventionally to domain models and services
app/presenters # mapping conventions the same as controller-to-view
app/resources #our various view object mixins and whatnot

Excellent. I think that will work.

Conclusion

You made it this far. Dang. Thanks for indulging me. I’m certainly interested in any feedback you may have (aghast, snickering or otherwise). This is a work in progress (alpha thoughts, you might say), and I’m sure I’ll look back in a few months and groan at the moronicality. But I’ll certainly try to share my experience putting this all together and my thoughts overall as they shift.

  • J Ryan Williams

    I am principal of Websuasion LLC, based out of Fayetteville, GA. We Develop Web and Mobile Applications, Produce Video and provide tools and methodologies for Responsible Brand Marketing.

    This blog tends to focus on the technical and conceptional aspects of our work with Ruby on Rails, iOS, DSLR video, the business process and a little branding discussion at times. I welcome your relevant comments, and if you have questions, feel free to speak up. For info on rates and service packages for Websuasion, please visit our Service Packages page.

    Connect with Ryan...

  • Signup for Special Offers

    This newsletter is designed to help commercial business, nonprofit organizations and government agencies get the most out of their websites and web marketing campaigns. We often will have special offers specifically for our newsletter members.





    * = required field

    powered by MailChimp!
  • Topics

  • Archives