Most Rails developers know to get logic out of their views. Good Rails developers know to keep their controllers slim. What happens when your models become unmanageable with all their new responsibilities? Ryan Brunner is going to educate us on how practices like the presenter pattern can help when your models get a little too fat.
Strategies for Landing an Oracle DBA Job as a Fresher
Dealing with Obese Models
1. Dealing with Obese Models A 5 Step Guide to Getting your models in shape Ryan Brunner, Influitive @ryanbrunner
2. The Problem My Models scare me. They’re too big and hard to deal with. Usually, when I run into a structural problem with Rails, there’s one place I can turn to.
34. Look for code that’s Too interested in another model. person.rb
35. Look for code that’s too interested in another model. person.rb person.rb It might make more sense to move it there. feedback.rb
36. Look for code that’s too interested in another model. person.rb person.rb It might make more sense to move it there. feedback.rb Fatness isn’t just about LOC.
42. Watch for Presentation Logic in your Models Consider HTML Harmful. Models shouldn’t format data. If you’re returning something other than models, be careful..
46. Delegates methods to the subjectof the decorator. Provides additional methods for presentation logic. Use them like you’d use the model, but feel free to add presentation logic. Anatomy of a decorator
47. Decorators are a way to extend your models to Add Presentation Logic
48. All the delegation work is done for you. https://github.com/jcasimir/rails_decorators
67. You have lots of instance variables on your controller action.
68.
69. Summary Sweat the easy stuff first. Organization goes a long way. Limit how much your models know about other things. Consider presenters or decorators for complicated scenarios.
Notes de l'éditeur
Hi, I'm Ryan from Influitive. Today I’d like to talk to you about keeping your models in shape.
So what's this all about? I wanted to talk today about a problem that I've been facing more and more often in my own codebase, one that perhaps you're seeing too, that doesn't get enough attention as a problem in Rails development. My models scare me. Each individual method is small and understandable, but the whole model is just a scary amount of code. Usually, when I run into a structural problem with Rails, there’s one place I can turn to.
Conventions.
Rails loves conventions. It’s one of the nicest things about working with Rails. With very few exceptions, when you’re structuring your code, you can find a pithy little statement about the proper way to do things. For example, in Rails:
Your folders should look like this.
Your views should look like this.
Your controllers should look like this.
Your stylesheets should look like this.
Your javascript should apparently look like this.
But there’s no catchy catch-phrase to prevent your models looking like this. We talk about keeping methods small and organizing things better, but models still have a tendency to bloat up over time.
What we do hear nowadays is “Skinny Controllers, Fat Models”. And it’s not a bad catchphrase to follow. Bloated controllers are hard to test, hard to understand, and overall just ugly to deal with.
But is this really a good alternative? Doesn’t just looking at this model feel like a punch in the gut? I actually have two confessions to make – one – that this model is actually from our codebase, and two – that it’s actually a couple pages longer – I couldn’t figure out how to make it fit on one slide.So while I agree with the “skinny controllers” part of the argument, I’m going to propose that we make one modification to our rule.
Skinny controllers are great. Writing a one-liner controller method just feels awesome. That doesn’t mean that we should dump everything onto the models plate. The worst part is, unlike controllers, where bloat usually looks like seriously hairy methods, model bloat can be subtle – a whole lot of small, compact methods slowly bloating the model until you’re left with an incomprehensible mess.
Imagine that you’re working on a person model for your new, shiny app. You’re a pro Ruby developer, so you know all about how your controllers and views need to be as compact as possible. You’re going to make sure that the hard stuff is all done inside the models. Let’s get started.
Our person model starts innocently enough, just a few attributes. If anything, it’s looking a little thin. Let’s make this model do something useful.
We’ll add some associations to related models..
Validations to ensure that we’re not passing in invalid data.
Maybe we create some scopes to clean up some controller search logic in our index action.
Maybe we want to prevent some users from seeing certain people. We can create some scopes to handle that as well.
The feature requests are rolling in now. Let’s wrap our person in a state machine to reflect whether they’ve been invited into the system.
Also, we need to add some support for geotagging.
Bob the sales guy just asked for leaderboard functionality.
We’ve got some hairy logic dealing with relationships between people – let’s add some more methods.
Finally, let’s add some methods to help calculate and format things they way we expect them and get some logic out of our views.
Suddenly, our model isn’t looking so svelte anymore. It’s hard to point to any one particular feature as the thing that drove our poor little Person model over the edge, but bit by bit he’s bloated into something that’s difficult to understand, hard to maintain, and evokes that painful feeling every time we open person.rb.
Our model is now obese. It wasn’t as big of a problem when he was pleasantly plump, but now every developer dreads modifying anything about people.Today, I’m going to go through a 5-step program to get our poor little person back into shape. We’re going to go through some quick wins, as well as some more involved stuff that will help when dealing with complicated models.
Our model is now obese. It wasn’t as big of a problem when he was pleasantly plump, but now every developer dreads modifying anything about people.Today, I’m going to go through a 5-step program to get our poor little person back into shape. We’re going to go through some quick wins, as well as some more involved stuff that will help when dealing with complicated models.
Step 1: OrganizationWhen you want to lose weight, the first thing you do is start logging everything you eat, so you can figure out where making changes would be most effective. That’s a good first step for your models as well.
Start by taking your model, and organizing it into manageable chunks. Keep like things together, and avoid the temptation to group things with names like “methods”. Categorize your code by what it is doing, not what it is.
Don’t be afraid to use whitespace in your model. Fatness isn’t about lines of code, it’s about density. Give your code some room to breathe and it starts to look a lot less daunting. You want your model to be scannable by a developer reading it, so they can find where they need to go without reading through each individual line.
Use comments. I know, ruby is beautiful, and readable, and ought to not need comments, but we’re already dealing with a complicated situation, and a bit of extra description might be worth the extra cruft and maintenance. Focus on why the code is doing what it’s doing, rather than what.
Don’t be afraid to play around with conventions around indenting a bit. If you look at my categories, they technically should be indented to align with the code, but bringing them back one level really goes a long way towards making the categories stand out.Watch out for things that you find difficult to categorize, or don’t seem to fit well with the rest of the model. These are probably your prime candidates for refactoring.So now we have a model that is at least organized, although it’s probably even bigger in terms of line count than when we started. But we’re in a good place to start implementing some real changes in the model.
The second thing we should look at is moving some logic to another model if it’s appropriate.
It’s a pretty common occurrence that methods in models will spend most of their time talking to another model – usually an associated one. This is bad for a few reasons – first – it’s unnecessarily bloating our model – and more importantly, it is implementing business logic that it shouldn’t be responsible for.One telltale sign of this is interacting with ActiveRecord a lot on a particular association – this is usually a sign that you can push some of that logic into the appropriate association class.
And here it is after a quick fix. We’ve reduced the method on contacts to be a single line and have delegated nearly all the business logic to the Feedback model. Most importantly, Feedback is now responsible for it’s own rules – when feedback is completed, and what constitutes “recent” feedback. This will help a lot if we ever need to modify these rules.
One interesting thing to note here is that we’ve actually added lines of code here. Don’t be afraid to do that. Designing an application isn’t the same as playing code golf – you aren’t judged on writing the fewest lines of code. We’ve made everything much more modular and testable, and even though there’s more lines overall, it feels more thin and flexible.
Another common source of model bloat is with functionality that the model uses, but isn’t really core to what the model is about. Let’s look at an example.
So here’s a good example of some code, which, while readable and somewhat compact, really isn’t core to the concept of a person. If you’re curious, this code adds a new attribute that isn’t stored in the database for our person class that stores an external image file. When a person is saved, if it’s set, we set the image property (which is using paperclip), and we can easily support both file uploads and references to external images. Awesome, right?But if we take a look, it hardly depends on the state of the person at all, outside of the attribute we’ve defined here and the image property included on the contact. What’s more, all of this code isn’t really about people. People have an image – the concept of uploading external urls to populate those images isn’t core to personhood. So what’s the solution?
Abstract it away. Move all of that code to your lib directory, and include it into your model. Now the person class is way less involved with the particulars of how to upload files, and just cares about the fact that it has an image, and it’s a file, and it can be remotely downloaded in some way.One key concept to take out of this is that you should be developing the framework around your app. Rails is great, and does many amazing things, but it won’t do everything. Once your application gets to a particular size, you’ve built a framework, whether you like it or not, and the only thing left to decide is whether it’s a clean framework or a messy one.
And don’t worry if no other model in the application remotely downloads images. What’s important is that the model cares about what it needs to care about, and the remote image downloader knows how to download images.
So we’ve made a few pretty big steps in cleaning up our model. Things that don’t rightly belong to the model are more or less taken away now. Let’s focus next on what the model should be concerned with.Presentation logic is almost certainly not one of those things.
Watch out whenever you seem to have logic in your models that looks like it’s ripped out of a view somewhere. Your models should act as if they don’t have any idea what this new-fangled “HTML” is.
Now, there’s a pretty standard way to address this in Rails. There’s this little known folder called app/helpers, which is supposed to store all of this presentation logic stuff. You get access to methods like you would in the views, and everything seems like a clean fit, save for one problem.
Helpers are kinda crappy. Working in helpers feels like you’ve abandoned object oriented programming for procedural programming. Everything needs to take arguments, and dealing with model state is a pain.
But there’s actually a pretty good solution to this problem – decorators
. Basically, the idea behind a decorator is to wrap the model that you’re working on, delegating calls to the model when you don’t need to implement custom logic, but adding new methods or supplying your own implementations when you need presentation specific code. This is similar to, but not quite the same, as the tradition Design Patterns definition of a decorator.Let’s take a look at how a decorator works.
So in the context of a decorator, we can write code just as if we were actually in the person class, but we still maintain a tight seperation between model logic and presentation logic. Our model isn’t cluttered up with unnecessary presentation junk, and we don’t have to switch over to the procedural mindset of helpers. Since I started using decorators, I’ve almost completely abandoned class-based helper files.
Fortunately, all of the messy delegation logic as well as including some helpers from ActionView is packaged up in a neat little gem called RailsDecorators. It’s super simple to use and takes nearly no time to get working, so I’d highly advise using it.
So now we’re on to the ugliest cases of model bloat, the stuff that makes you feel dirty even while you’re writing it.
I think the best way to go over this is with an example. I’m actually going to use a widget that we are currently working on at Influitive to demonstrate how these tricky sources of model bloat can happen.
So we recently worked on an interface that plugs into external systems and gives an overview of how a particular person was related to other people in our database.
Because it needed to fit within a few hundred pixels, and provide a lot of at-a-glance information, this particular interface had a lot of responsibilities. It had to:Check whether the current user is connected.Get counts filtered by customer type.Get detailed lists of relationships by relationship type.See whether the current user has provided feedback before.List how many users are currently in the system.Check for any content linked to those users.
Um, yeah. There’s an awful lot here for one controller action to do.
So let’s try doing this in the “Rails-approved”, fat models approach.
First off, we’re going to need to extend our model to support all of the different types of data that we’re going to need to grab, so we don’t leave it up to the controller to implement what ended up being some pretty hairy activerecord calls. This logic alone, with all the code, added about 50 lines to the model we were working with. And we’re constantly tossing variables like the prospect in question, and what type of relationships we’re working with. Seems a bit brittle.
Our controller really isn’t a much better story. This controller seems to know an awful lot, even if it’s delegating most of it’s responsibility to the model. And passing around that many instance variables seems a bit smelly and un-RESTful. You’ll notice that this is a custom, non-REST action on top of all that.
Enter presenters. Presenters can really be thought of as a way to wrap logic that deals with multiple models in convoluted ways into a neat, organized package. With the old logic, our controller was really talking about 5 or 6 vaguely related things at once. But if you think about it, it really was only talking about one thing – a summary of the relationships of the prospect. The problem is, that’s not a model in our system, nor should it be. With a presenter, your controllers will be returning the presenter itself rather than a bunch of small, unrelated things.
So here’s what our presenter ended up looking like. Let’s go over how the logic behind a presenter works.First off, you initialize presenters based on the information that you’re dependent on. In this case, we need to know what user is accessing the widget and which prospect we’re looking at. From there, define an interface to your presenter to match exactly what you’re doing in the view. It’s actually pretty useful to start writing your view before your presenter, and to just write whatever you would want the presenter to provide you with. From there, you can implement the presenter logic.Notice that the prospect and user variables are completely private to the view – the idea behind a presenter is you are constructing an interface for the view to work against – your view shouldn’t be able to access your models unless negotiated through the presenter.
That complicated controller logic has been pushed down into the presenter itself, and we’re now talking about one thing, rather than 5 tangentially related things.
So the benefits of presenters are pretty obvious – our views, controllers, and models are now far cleaner, and dealing with only their concerns, rather than being tightly tied to each other. The presenter serves as the glue connecting all of these things together, rather than delegating all of the cruft onto the model and controller. And your model isn’t implementing a lot of logic just to support one particular view.
You should use presenters if you detect any of these smells in your app:Your view is talking to more than one model in a non-trivial way. Iterating over associations is probably OK. If you’re doing anything else though, you could probably benefit from a presenter.Your controller is setting a lot of instance variables. This creates brittleness between the controller and the view, and is pretty unREST-ful. Try to make controller actions talk about one thing, even if that thing is a presenter and not a model.Your model is doing a lot of things involving other models it isn’t related to, or doesn’t have a simple relationship to. If you find yourself passing in parameters really often to model methods, that might be a sign that your model is being a little too smart.
Hopefully, by applying our 5-step program, your model is back to it’s old, trim, fit self.
So, in summary, a few hints and tips to keeping your models clean.Start with the easy stuff. In a lot of cases, you don’t need to get into huge refactorings if there’s some obvious quick wins that you can take.Always start with organization. An organized model is way easier to refactor, and even a large organized model is easier to deal with than a small mess of spaghetti code.A good rule of thumb is to ensure that models only know about themselves – specifically what they are and what they do. Models shouldn’t know much about other models, save that they are associated to them.If you have some complicated scenarios, presenters and decorators are powerful tools.