Hacker News new | ask | show | jobs
by burlesona 2582 days ago
I've found that a pretty simple technique along the lines of what's shared in the article makes complex Rails apps much more maintainable. Most of this applies to any MVC style app/framework.

I follow these rules of thumb:

1. Controllers should only handle converting HTTP to ruby calls. That includes logic that is specific to the request flow, like parsing params or authenticating cookies, but nothing else.

2. Models should only handle read/write on their own table. You can use associations, but no referencing another class name inside of a model. No after_* callbacks (and try not to use callbacks at all).

3. What Rails calls "views" should be though of as simple html templates with loops and simple if/else, but no complex logic.

4. Don't use Rails Helper Methods. Just don't.

All by itself this works for toy apps, but now you've got holes where complex presentational and procedural logic has no place to go. So you plug those gaps with two kinds of domain objects: view objects (for complex reads) and action objects (for complex writes).

View Objects (more often called Presenters in Rails land to avoid the conflict with the templates, which rails calls "views"):

These are used to wrap up any kind of complex, multi-model view. So for example, when you have something like an "account settings" page, you probably need to fetch the user and some associated models, maybe billing info, etc. You can make a simple object that takes in URL params in its constructor, efficiently queries whatever is needed to present this page, then freezes. Now you can put whatever data and logic is needed for the template here, and it's very easy to unit test the queries and the individual bits of logic to ensure they're correct.

Action Objects (sometimes called Mutations, Commands, Interactions, Services, or Procedures):

These are used to wrap up any kind of mutative procedure. They should take in a set of inputs, and when called, perform some kind of action (for example, running through all the steps of user registration). These should be written functionally, and should be idempotent whenever possible. Again, wrapping the code up this way makes it very easy to unit test, and to stub in external dependencies when relevant.

These patterns make it really easy to follow what's going on in your app - easy to add new behavior and easy to walk through complex business processes step by step since everything happens in one control flow. And of course you can compose these objects together for the most complex flows. The simple and stable interfaces help keep your program easy to reason about and allow you to work on individual parts in isolation with confidence.

I've used those patterns over the last ten years or so with great success, and more recently have been helping my team at Atlassian gradually convert what was a somewhat messy older Rails app. Happy to answer questions if anyone has any :)

— Edit —

Just to add, there’s one more big benefit, which is that if you code this way it becomes trivial to replicate any of the behavior in your app from the Rails console. Of course this is the same reason it’s easy to test when you build this way, and writing tests is more important than poking around in the console. But when I’ve worked with people who aren’t as in love with testing as I am, I’ve found that they get more excited when I show them how this puts all your apps behavior into an interface that’s very easy to drive from the console. :)

3 comments

Great write up, seasoned devs who pick up rails do this right away, junior devs who pick up rails rewrite their app using the principles after the ratio of "reading existing spaghetti code:writing new code" starts pushing deadlines.
> 3. What Rails calls "views" should be though of as simple html templates with loops and simple if/else, but no complex logic.

I rather like how Phoenix makes a clear separation between views and templates. The templates have no logic, but the views can have quite a bit of it as long as it is directly linked to the templates (presentational).

When I first looked at Phoenix ~18 months ago, I was a fairly fresh developer and couldn't figure out the separation of Views and Templates (coming from Rails).

I'm just now going through the Programming Phoenix 1.4 book and it's just been one 'aha' moment after the other, with the separation of logic and views being one of the most significant.

Really interesting comment, thanks for sharing your experience. Can you expand on why Rails helper methods are bad?

I'm just starting out my Rails journey so I haven't encountered those issues yet.

Welcome to Rails world! It’s a fun place to work :)

Why no helpers:

1. The scoping is weird and encourages bad patterns.

- You have access to the instance variables from whatever controller action happened to call the particular helper, but you can’t see that when writing the helper or in the view, so it’s very opaque. God forbid you actually USE the instance variables and you’ve now got these weird fragments of tightly coupled code between your controller and your template that quickly become hard to reason about.

- It’s better if you require that all helper methods be strictly functional, but in that case it’s still unintuitive because your methods are mixed into every view. This can lead to surprising name collisions and all kinds of other weirdness.

2. They’re not easy to test due to the issues above.

3. They’re weird to include elsewhere - for example if you want to reference in a model.

3. Most important: the principal value of common utility functions is to have the sort of “general purpose operations” defined in exactly one place, well tested, and then utilized everywhere else.

For all of these reasons you’re better off defining modules and using ‘module_function’ to make the methods directly accessible. Then you can just call the methods like ‘Utils.foo_bar()’ wherever you need them.

I always had exactly one usecase for an application helper when using bootstrap: „<%= glyphicon(:cloud) %>“ It‘s way faster than rendering templates, should be available anywhere and is truly convenient.