Upgrading some Rails apps is as easy as
bundle update rails.
But what if you have one of the other kind of apps? The ones that are still on Rails 4.0, or even 3.2, because you didn’t feel like dragging them kicking and screaming into the future?
Whether you’re facing a quick upgrade or a painful one, these steps will help you get your app to Rails 4.2 as smoothly as possible. And in the process, you’ll learn how to take full advantage of Rails 4.2’s new features.
Read the upgrade guide
You should always start your Rails upgrade by reading the Rails Upgrade Guide.
The upgrade guide will walk you through most of the major changes between Rails versions. It’ll tell you exactly how to change your app to fix the biggest problems you’ll run into. (For example, this change in Rails 4.1 would have caused us hours of pain if we hadn’t known to watch for it.)
But more than that, the upgrade guide will explain why you should make those changes. And that’s important, because it helps you understand which of its suggestions you should follow, and which you can ignore.
And if you want to understand the Rails 4.2 changes more deeply, read the release notes. They’ll introduce you to all the cool new Rails 4.2 features you’ll soon be able to use. If nothing else, it’ll motivate you to get that upgrade done.
Upgrade in steps
When you’re moving to a new Rails version, always upgrade to the newest point version first. And don’t skip minor versions.
So if you want to upgrade from 4.1.6 to 4.2, go 4.1.6 to 4.1.9, and 4.1.9 to 4.2. If you’re starting at 4.0.12, go from 4.0.12 to 4.0.13, then 4.0.13 to 4.1.9, and then from 4.1.9 to 4.2. It might seem like a lot more work, but it’ll save you time in the long run.
The newest point version of a Rails release has the best deprecation warnings.
Don’t underestimate how useful deprecation warnings can be. If you upgrade in steps, they’ll tell you exactly what will break and how to prevent it. If you jump too far, your app will just plain break, and you’ll have no idea why.
Finally, if a point version Rails upgrade (like from 4.0.9 to 4.0.13) breaks a test, it’ll usually also break during a major or minor upgrade. But those problems are much easier to debug and fix when less has changed.
So here’s what to do:
- Upgrade to the newest point release of whichever Rails version your app is on.
- Upgrade any other gems that keep you from
- Fix the tests, committing as needed.
- Fix the deprecation warnings, committing as needed.
- Upgrade to the next minor or major version of Rails, and repeat.
Lean on your tests
When you upgrade Rails, your tests will be your first sign that something’s not right. They should be the first thing you run, and the first thing you fix. Don’t even bother trying to get your Rails app to boot before your tests mostly pass.
Upgrade your gems
When a test fails, and it doesn’t look like it’s your fault, it usually means one of your gems needs to be updated.
A Rails upgrade is just about the worst time to upgrade a gem. Since so much of your app is changing, it’s hard to tell if the Rails upgrade broke something, or a gem upgrade broke something, or if you broke something.
But because some gems rely so heavily on Rails internals, this is something you’ll just have to do. So if something inside sass fails, upgrade the sass-rails gem. And hope it doesn’t make things worse.
Take lots of notes
If you’re having a particularly rough Rails upgrade, you’ll have to experiment. You’ll probably do some debugging, tweak some code, or change some tests around.
As you do this, you will forget the changes you’ve made. Some of the things you tried that got you mostly, but not quite, there. Which tests you were going to look at next. Other places you need to change code. Tips to share with coworkers that are upgrading their own apps to Rails 4.2.
So, take lots of notes. Write everything down. Things you tried, and files you changed. Hypotheses you have about why things were failing, and whether they were correct. Which gems you had to update. Behavior that seems weird, and you’ll want to check out more closely later.
You don’t want to make a change, roll it back, realize it was actually the right thing to do, and not remember what you did. So write it down.
The best possible Rails upgrade
Some Rails upgrades are smooth. Things just work. Others are challenging, multi-day or multi-week projects. It depends on your app, your dependencies, and the specific things that Rails changed. But if you follow these steps, you’ll have the best possible upgrade experience.
Are you on Rails 4.2 yet? How did your upgrade go? Did you just have to update a bunch of gems, or are you still chasing down that one last failing test?
Monkey Patching. When you first try Ruby, it’s amazing. You can add methods right to core classes! You don’t have to call
Time.now.advance(days: -1), you can write
1.day.ago! It makes Ruby a joy to read and write. Until…
You hit weird bugs because a patch changed
You get confused about which code actually ran, so you can’t debug it when it breaks.
And you finally figure out that all your problems were caused six months ago, when you monkey-patched
Enumerable to make one line of code five characters shorter.
But what’s the alternative? No convenience at all? Code that looks like
GroupableArray.new([1, 2, 3, 4]).in_groups_of(2)? Waiting for
blank? to make it into core Ruby before you’re allowed to have nice user input handling?
You don’t want to give up on monkey patches entirely. But how can you write monkey patches that won’t make you want to fire yourself for incompetence the next time you see them?
Put them in a module
When you monkey patch a class, don’t just reopen the class and shove your patch into it:
1 2 3 4 5
If two libraries monkey-patch the same method, you won’t be able to tell.
The first monkey-patch will get overwritten and disappear forever.
If there’s an error, it’ll look like the error happened inside
While technically true, it’s not that helpful.
It’s harder to turn off your monkey patches.
You have to either comment out your entire patch, or skip requiring your monkey patch file if you want to run code without it.
If you, say, forgot to
require 'date'before running this monkey patch, you’ll accidentally redefine
DateTimeinstead of patching it.
Instead, put your monkey patches in a module:
1 2 3 4 5 6 7 8 9
This way, you can organize related monkey patches together. When there’s an error, it’s clear exactly where the problem code came from. And you can include them one group at a time:
If you don’t want the patch anymore, just comment out that line.
Keep them together
When you monkey patch core classes, you add to the core Ruby APIs. Every app with core patches feels a little bit different. So you have to have a way to quickly learn those changes when you jump into a new codebase. You have to know where your monkey patches live.
I mostly follow Rails’ monkey-patching convention. Patches go into
lib/core_extensions/class_name/group.rb. So this patch:
1 2 3 4 5 6 7 8 9
would go into
Any new developer could browse through the Ruby files in
lib/core_extensions and learn what you added to Ruby. And they’ll actually use those convenient new methods you wrote, instead of those methods just getting in the way.
Think through the edge cases
I don’t know why
Enumerable doesn’t have a
sum method. So often, I wish I could write
[1, 2, 3].sum, or
["a", "b", "c"].sum, or
[Article.new, Article.new, Article.new].sum… Oh.
When you monkey patch a class, you’re usually thinking about one thing you want to make easier. You want to calculate a sum of numbers, but forget that Arrays can hold other things.
Right now, it makes sense. You’d never try to calculate the average of a bunch of hashes. But when you attach methods to an object that just plain fail when you call them sometimes, you’ll confuse yourself later on.
You can deal with this in a few ways. From best to worst:
Handle unexpected input reasonably.
This works well if your patch works with strings. You can get something reasonable from almost anything if you call
to_son it first. And Confident Ruby will teach you a ton of ways to deal with different kinds of input.
Handle the error in a clearer way.
This could be as easy as throwing an
ArgumentErrorwith a good message when you see input you’re not expecting. Don’t depend on someone else understanding random
Document the kind of input you expect in a comment.
The other two options are better, if you can use them. But if you can’t check for edge cases inside your patch, at least document them. That way, once your caller figures out it’s your patch that’s causing their problem, they’ll have an idea of what you were trying to do.
My all-time favorite monkey patch
Finally, I want to leave you with my all-time favorite monkey patch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
It makes attaching CSS classes to HTML elements so much nicer.
Sensible monkey patching
Monkey-patching core classes isn’t all bad. When you do it well, it makes your code feel more like Ruby. But just like any of Ruby’s sharp edges, you have to take extra care when you use them.
If you keep your patches together, group them into modules, and handle the unexpected, your monkey patches will be as safe as they can be.
What’s the best monkey patch you’ve ever written (or seen)? Leave a comment and tell me about it!
You found the perfect solution to your crazy testing problem. All you have to do is override the
DEFAULT_HOST constant, and you’ll be in business.
Except that you have to turn off warnings to get that ugly message to go away. But now all your tests pass, and you only had to change a few lines of code!
Except for that one test where you don’t want to override the host. But you could just re-override the constant, turn off the warnings again, and make sure it gets reset at the end of the test. You’re so close to being done, you can almost taste it!
Except… Except… Except…
And a few days later, when you get stuck for the twenty-seventh time and your app has become one giant ball of hacks, you’ll sit back and wonder: Why am I doing all this? Isn’t the solution worse than the problem?
How to solve the too-clever problem
It’s clear your original idea won’t solve your entire problem. So how do you think up a better idea, that solves all the edge cases your original idea couldn’t?
You can’t. You can’t fight too much cleverness with more cleverness. At least, not directly. Instead, go the other way. Go simple. Go straightforward.
What does that mean?
Inline the code you were trying to abstract away. Do repeat yourself. Keep your code explicit.
If you were trying to override a
DEFAULT_HOST constant with a different default host, forget about the whole idea of a default. Just specify it every time.
So, instead of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Do something like:
1 2 3 4 5 6 7 8 9
Whenever my seemingly perfect solutions break down, it’s because I didn’t imagine the edge cases I’d eventually have to handle.
It’s OK. We can’t predict the future. But when you notice it happening, stop digging. Don’t just apply patch after patch after patch. Instead, unwind your original solution and extract a better one.
How to extract a better solution
When you have all your code written out in an explicit, straightforward way, you’ll start to think of ways to reorganize it.
Usually, it’s enough to apply Extract Method or Extract Class in the right place. The trick is deciding what that right place is. But figuring that out is a lot easier when you see lots of repetition right in front of you.
And lean on inheritance and delegation. Those are the simple building blocks that will help you clean up your code without getting too clever.
One more thing
Don’t forget to read the documentation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
The answer won’t always be that obvious. But there’s nothing more humbling than realizing there’s a one-method built-in solution to the exact problem you wrote three classes and a gem to solve.
A better solution, in the end
Your second solution will usually be better in every way than your original one.
Why is that?
You have more experience as a developer.
So you’ll have a better idea of what makes good code.
You know more about the system you’ve built.
So you can make better decisions about how the code you’re writing fits into it.
You know which of your assumptions were wrong
So your solutions can better fit the actual problems that exist, not the ones you imagined might exist.
And in the end, there might be a place for the best of your cleverness to fit back in. This time, without the hacks.
You have to stop digging
Clever code is fun to write. In Ruby, it’s also easy to write. And it’s especially easy to keep going down a path, even when you know it’s taking you to the wrong place.
But once you get that nagging sense that something’s not quite right, stop for a minute. Un-factor your code. Read the documentation. Make your code straightforward and explicit. And find a better way.
Can you remember the last time you kept on digging yourself a hole that just wouldn’t end? How did you get yourself out of it? And what did the code you ended up with look like?
It’s so easy to get lost in the small bits of daily work you do. When you’re that focused, you can totally miss what you actually accomplished with all those small steps.
So here’s a look back at the writing and speaking I did last year, along with the best things I learned along the way.
What went well
I started to write here on January 10, with Estimates Are Not a Goal, They’re a Communication Tool. In 2014, I posted 53 articles, more than one per week! Starting in April, I’ve posted an article every Tuesday. Creating that kind of schedule makes writing and planning so much easier.
I tried to start writing a few times before, and always gave up for two reasons: I ran out of topics, and it didn’t seem like anyone cared. Two things made it different this time:
I started solving problems instead of just writing. I learned this from Amy Hoy and Alex Hillman’s (completely awesome) 30x500 course: If you focus on helping people with the problems they have, you’ll never run out of things to write about.
I started telling people when I posted. I’ve always been nervous about sharing my own stuff. But when you just start out, how are people going to know about what you write?
If you’re posting helpful things and participating in the discussion around it, people are happy to learn from you. So tell them about it!
These were my most popular articles from last year:
- Search and Filter Rails Models Without Bloating Your Controller
- The Lesser-known Features in Rails 4.2
- 4 Simple Memoization Patterns in Ruby (and One Gem)
- Rails 5, Module#prepend, and the End of
- How to Beat Procrastination on Your New Rails Project
Take a look if you missed them the first time around!
A few articles in, I started an email list. Through the end of 2014, it’s grown to 1,670 subscribers! I send emails to the list every Friday, with 41 sent so far. They range from deeper looks on some of the articles I posted, to answering questions, to exclusive articles about the problems I hear about from my subscribers. If you haven’t joined already, sign up here. I’d love to hear from you.
One problem with the email list is that once you miss an email, you miss it. So this year, I’m going to find a way to get the best emails you’ve missed to you.
I also wrote a book about learning Rails without getting overwhelmed, and started to pre-sell it. You can get early access here. It’s 25% off until the final release, and you’ll get the final update once it ships. More than 300 people have received early access, and it’s already helped a lot of them get past the tutorial stage so they could start building their own Rails apps.
Finally, here are a few other things I did in 2014:
- A guest spot on the Ruby on Rails podcast, where I talked about some of the behind-the-scenes of the site and the book.
- A presentation to Cali Ruby on getting the most out of Rails tests.
- A guest episode for RubyTapas, on using the
tsortRuby library to handle trees of dependencies.
What didn’t go so well
I create schedules and due dates for myself that are, in retrospect, totally insane.
Once it’s clear that you’re not going to hit a goal, you start beating yourself up. You start thinking, maybe if you worked a little harder, maybe if you stayed up a little later, maybe if you were just plain better at what you were doing, you wouldn’t be in the mess you’re in. But it’s a self-imposed mess! It makes no sense.
With aggressive goals, I’ve done a lot of stuff. But too-aggressive goals and too-high expectations have hurt more than they’ve helped. I have to find more of a balance.
The best things I’ve learned this year about creating things:
Schedules and habits are better than motivation.
I credit all of the work I’ve done here to setting up the right habits and schedules.
You still need motivation to get those habits started. But that motivation is better spent setting up good systems and habits, instead of spending the motivation on the work itself. Because motivation eventually fades, but a good habit sticks around for a lot longer.
Rough first drafts make the creation process faster.
The first drafts of my articles are unreadable. But rough drafts are easy to improve. I don’t have to keep what I’m thinking about in my head, I can put something down and improve it piece by piece.
Sometimes you have to close your eyes and hit submit.
Some of the articles I’m most proud of are articles I came close to not posting. It’s nerve-wracking to put something you created out in public, especially as you’re learning. When that happens, you just have to
git pushand walk away from the computer for a little while. What you made is never as bad as it seems right before you publish.
What about you? What were your biggest accomplishments last year? What did you learn? And what are your plans for 2015?
You’re excited about the app you’ve made. There’s just one problem — you don’t have any tests. You wanted to write it using Test-Driven Development, but you didn’t quite know where to start. So you’re stuck. Where do you go from here? How do you get from an app with no tests, to writing your apps using TDD?
Test the code you already have
You have a bunch of code with no tests. But that doesn’t mean you can’t write your tests now, against the existing code. So start testing the code you already have, to make sure your code works the way you expect it to work.
It’s not TDD. But testing existing code will help you learn TDD:
You practice thinking about edge cases and error conditions.
To write tests without spending years testing every single possible input, you have to think about where the code is most likely to break. If the method you’re testing takes a string, what happens if you pass it a symbol? What happens if you pass it
nil? And if you’re testing a function that divides numbers, you’d better test it with 0. But you probably don’t need to test it with 1 and 2.
After you write enough tests, you’ll start to predict where your methods are most likely to break. And once you start TDDing, you can use this skill to write robust tests that force your code to better handle your edge cases.
You practice writing well-structured tests.
When you write tests after-the-fact, you can try different patterns for structuring those tests. The code you’re testing is already there, so you can focus on your test, and how it’s written. And once you learn some good patterns, you’ll write better tests when you don’t have the code to lean on.
You discover the things that make code hard to test.
As you write more tests, you’ll begin to sense which areas of your system will be the hardest to test. When you notice those areas, you can highlight them as places that need refactoring. Even better, you’ll start to write more testable code the first time.
Once you know what easy-to-test code looks like, you can TDD easy-to-test APIs with that knowledge. And that will help you build your apps faster.
Ease into TDD
You can use test-after to build skills that help you learn TDD. But you still want to TDD pieces of your app eventually. And there’s a simple way to ease into TDD, and still lean on existing code: write regression tests.
Regression tests keep you from breaking code you’ve already fixed. The idea is pretty simple. Whenever you hear about a bug, instead of clicking around in the browser to reproduce it:
- Write a failing test to reproduce the bug.
- Run the tests, and make sure they fail (because that bug still exists).
- Fix the bug in the simplest way possible.
- Run the tests, and make sure they pass.
- Refactor your fix, if necessary.
This is a lot easier than TDDing a new system from scratch, because you’re just test-driving a change to code that’s already there. You build the habit of “Red, Green, Refactor” that is the essential loop of TDD. And from here, TDD is a shorter step away than trying to go straight to TDD from no tests.
From nothing to TDD
An app without tests isn’t a bad place to start. When you test existing code, you’ll learn a lot of what you need to write good TDD tests. Test-after is easier than TDD at first, because you don’t have to imagine APIs that you don’t know how to design yet. And once you decide to bring TDD into your app, you can ease into it with regression tests.
So, if you don’t know how to TDD the system you’re imagining, keep writing tests. Even if you have to write the code first.
The Rails community is full of conflicting advice. Do you use Minitest or RSpec? Do your plain Ruby objects go in
If more experienced devs constantly argue about these things, what hope do you have of deciding which advice to follow?
What’s the secret?
Here’s the secret: just pick one side, and follow it.
It seems like a cop-out. But these are arguments over small differences in philosophy. The stronger the argument, the less important the choice actually is. Especially in small apps, like the ones you’ll start with.
Experienced devs got that experience somehow. They learned, they wrote code, they shipped apps. Whichever choice they made didn’t sabotage their progress. So following that choice won’t sabotage yours.
I’m a big fan of Minitest, and don’t use RSpec when I can avoid it. I’ve written a ton about why I prefer Minitest over RSpec. But if you decide that RSpec is the framework you should be using, you’re not going to end up with an unusable app. If you end up switching to Minitest later, you won’t be starting all over again. Most of the testing skills you learned will carry right over. So just pick one. Flip a coin if you have to. Because either choice is better than none.
It’s easier to change than you think
The right choice for you depends on your personality, your environment, your team, and your past experiences. These things will change over the course of your career. So if you spent days learning all the background and the nuances of each side and finally made the perfect choice, that choice may eventually change.
You’ll make some bad choices. Some of the best developers I know wrote entire gems based on philosophies that they now argue against. In code, few opinions last forever.
So don’t agonize over picking the right side in a Rails argument that you don’t totally understand yet. If it doesn’t work out, you can always change your mind.
After you finish the Rails tutorials and start your own app, things get confusing. Like where does your non-CRUD, general logic go? How does grabbing followers from Twitter fit into MVC? Ask two people, and you get four answers. Or your thread devolves into a bunch of smart people insulting each other for hours, while you beat your head against the desk. Either way, you’re left with a headache.
You can’t build the app you’ve been dreaming of without some general, non-Rails logic. So where do you put your code, and still keep things simple?
The easy place to start
When I have logic that feels related to an existing ActiveRecord model, I’ll start by putting it into that model. For example, if I had a
Game model and I wanted to import a bunch of games from CSV files, I’d put that method right onto the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
You have all the information your methods need right at hand. It’s easily testable. And it’s probably where a new contributor would look for that logic first.
But if you keep adding and changing that model, it’ll get big and complicated. Different parts of the model will interact in strange ways. The more you change it, the harder it will be to change.
In that case, you’d probably want to refactor that code out to a non-ActiveRecord model.
Just because it’s in a Rails app, doesn’t mean it has to inherit from Active/Action-whatever.
You can write your own Ruby code, in plain Ruby objects, and use them in your Rails app. These objects can still be called models, because they’re modeling part of your problem. They just don’t have an ActiveRecord database storing their data.
The next time I worked on that game CSV parser, the
Game class was getting a little too big. So I moved the parser logic into its own
The whole commit is here, but this is what the non-ActiveRecord class looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
I’ll go right to creating a new plain Ruby object if the logic I’m adding doesn’t feel related to any specific ActiveRecord model. Or if the code seems like it should be a part of a thing that doesn’t exist yet in the app. Otherwise, they mostly pop up through refactoring.
With plain Ruby objects, you can write anything. But knowing you can write anything doesn’t help you with direction. What methods do you need? How will all your new objects interact?
A lot of Rails apps use the same categories of plain Ruby objects. These categories are patterns you can follow to write code that other developers would recognize. You might have heard of a few of them already.
Service objects, presenters, and jobs
There’s nothing special about service objects, presenters, and jobs. They’re just plain Ruby objects that act in a particular recognizable way.
For example, a Resque job is a plain Ruby class that has a
perform method and a
1 2 3 4 5 6 7 8
perform is called when the job is run.
A presenter is a plain Ruby object with code that only makes sense inside a view:
1 2 3 4 5
It might also include Rails’ view helpers, or take a few different objects and treat them as one unified object for the view’s convenience.
A service object is a plain Ruby object that represents a process you want to perform. For example, writing a comment on a post could:
- Leave the comment.
- Send a notification mail to the author of the post.
A service object could do both, and keep that logic out of your controller.
There’s a great take on service objects here. It’s full of examples.
For simple processes, I don’t bother with service objects. But if the controller starts getting too heavy, they’re a good place to put that extra logic.
You can use these patterns to organize your own business logic. They’re just plain Ruby objects, but they’re Ruby objects that share a certain flavor, that have a name, and that you can talk to other developers about.
Where do you start?
There are a lot of different places your non-Rails business logic could go. It might be hard to choose. So here’s what I do:
- If the logic is mostly related to an existing class, even if it’s an ActiveRecord model, I put it in that class.
- If it doesn’t fit an existing class, I create a new plain Ruby class to hold the logic.
- If it feels like the logic should be part of something that doesn’t exist yet, I create a new plain Ruby class for it.
- If I come back to the code later, and the model is getting too complicated, or the code doesn’t make sense in that model anymore, I’ll refactor it into its own plain Ruby class.
- If the code only makes sense in a view, I’ll add it to a helper or create a presenter.
- If the code doesn’t need to run during an HTTP request, or has to run in the background, it goes in a job.
- If I’m juggling several different models or stages of a process, and it’s making the controller too hard to understand, I’ll put it into a service object.
How about you? Where does your code go? And do you have any patterns besides these that you’ve found helpful? Leave a comment and let me know.
And if you don’t have a process yet, try mine. See how it fits you. There’s no perfect way to write code, but when you get stuck, a process like this will help you get started.
(This post is based off of one I sent to my list a few months ago. To read more like it every week, join up here!)
In Using a Little Bit of Convenience to Save Your Programming Flow, I talked about how the simplest abstractions can make your codebase more fun to work with.
But what about when you have a bad abstraction?
You dig out the API reference every time to understand what that method that clearly should have been named
leaderboard is actually called. You try to refactor around it, but one part of the code hands you structured data and the other part gives you raw lines of text. And every time you touch the code, something breaks, and you blow the rest of your day trying to debug it. Because you tweaked
position, but didn’t update
score at the same time.
So how do you build good abstractions, while staying far away from the bad?
Using good metaphors to write clearer code
Good abstractions come from good metaphors.
A metaphor describes something in your system by relating it to a concept that’s already in your head. For example:
Many command lines use the metaphor of a pipe.
You put data in one end of the pipe, and it comes out the other end. Easy to imagine and easy to use.
In concurrent programming, a lock is a good metaphor.
Without knowing anything about how locks work internally, you know that when a resource is locked up, you can’t use it. You have to wait until it’s unlocked.
In Object-Oriented Programming, message passing triggers a specific mental image: An object passes another object a message. The second object can do something with the message or pass it to someone else.
You don’t need to know anything about the implementation in order to get the idea (and it makes things like the proxy pattern much easier to understand).
A good metaphor is critical. If you see an abstraction based on a good metaphor, you’ll rarely have to read the documentation or go source diving to understand the code. The better the metaphors you come up with, the easier it will be to describe your system to someone else, and the easier it’ll be to work on your system later on.
How do you come up with good metaphors?
A good metaphor is specific, self-contained, and creates a vivid mental image of the thing it describes. It doesn’t leave room for misunderstanding, and it matches the code’s behavior as closely as possible. If someone makes assumptions about the code based on their understanding of the metaphor, those assumptions should be valid.
Coming up with good metaphors is hard. You’ll often go through a few leaky metaphors before you can find the right one, and that’s OK! It will get easier with practice. But here’s a process that’s helped me:
Think about what a bit of code does, without thinking about how it does it. What image comes up in your mind? How does other code use it? If you had to describe it in a word or two, which words would you use? Write that down.
Then, think about that image. Does it fit how that code works? Are there edge cases that leak through the metaphor? Is there anything besides the metaphor a new contributor would have to understand before they could use the code? When you think about your metaphor, do you get a clear picture in your mind, or is it fuzzy?
These are indicators that your metaphor doesn’t match your code closely enough. But the closer you are to a good metaphor, the less of a leap you’ll have to make to get there.
Finally, use it in a sentence. “This code is a (something), because…” Could you explain your metaphor to someone without a technical background, and have them understand what it does? The less background someone needs to understand the metaphor, the more helpful your metaphor will be.
If you’re close, but not quite there, take a look in a thesaurus. Subtle differences between similar words can mean the difference between an OK metaphor and a perfect one.
Metaphors and OOP
Object-Oriented Programming is fundamentally based on metaphor. That’s where it gets its power! Well-designed classes don’t just hide their implementations from other classes, they hide their implementations from you.
With a great metaphor, you should be able to tell just from the class name how to use your objects. If your classes are named well, and they cause the right images to appear in your mind, you shouldn’t have to dive into their implementation to understand them.
So now, I’d like you to think about the classes in the last code you worked with. Can you think of any that have particularly clear metaphors? Which classes do you feel like you work with, rather than work against? How often do you go digging into them to figure out how to use them?
I’d love to hear what you find out. Leave a comment, and let me know.
To become a great software developer quickly, you have to consciously think about the code you’re writing and the processes you’re following. And I can help you.
Generally, global variables are bad. But sometimes, a global in the right place can make your code a lot simpler.
In Rails, you might set data once during a request, but use it in every layer of your app. Which user is making the request? What permissions do they have? Which database connection should be used?
This information should be available throughout your code, so passing it everywhere would just be a bunch of noise. But using a Ruby global variable, or setting a class variable, will cause its own problems. Multiple threads could overwrite it, and you’d end up with a huge mess, and maybe unrecoverably bad data.
You need something that’s global, but for just your request.
The plain Ruby way
Usually, you’ll see this handled with
Thread.current. It looks like this:
This is simple enough. It’s a decent solution. But it has two big drawbacks:
Someone could accidentally overwrite your data.
What if two people picked the same key? You’d stomp on each other’s data. And if you’re storing something like users, that would be seriously bad. In your own apps, this probably won’t be a problem. But if you’re writing gems, or your Rails app is big and messy, it’s something you need to think about.
It’s not well-structured.
Thread.currentis just a big bag of data. You can’t easily document it. If you want to know what you can take out of it, you have to search for what you put into it.
Of course, if you’re putting away enough global data that this is a problem, you have more to worry about than
Thread.current’s lack of structure. But it’s still a point to keep in mind.
So, is there a better solution than
Thread.current? As you might imagine, Rails itself manages a lot of request-local data. And the box of goodies that is ActiveSupport has a different pattern to follow.
The Rails way
If you dig through ActiveSupport, you might run into
ActiveSupport::PerThreadRegistry. You can probably tell from the name that this is exactly what we’re looking for.
ActiveSupport::PerThreadRegistry, the earlier example would look like this:
1 2 3 4 5 6 7 8
It’s a tiny bit more work. But with
You have a place to put documentation. Anyone looking at your class will know exactly what global data you’re expecting, and what global data they can use.
You get an API that looks good. In fact, it looks like you’re just setting class variables. That’s probably what you’d be doing if you didn’t have to worry about threads.
Any data you set on the class will be namespaced. You don’t have to worry about
PhoneNumberApiRegistry.current_user, they’re treated totally separately.
ActiveSupport::PerThreadRegistry internally, for things like ActiveRecord connection handling, storing EXPLAIN queries, and ActiveSupport::Notifications. So if you’re looking for more good examples of the power of
PerThreadRegistry, Rails itself is a great place to start.
When threads and requests don’t match
For some Rails servers, a single thread handles multiple requests. This means anything you put into a
PerThreadRegistry will stick around for the next request. This is probably not what you want.
You could do what Rails does, and clean up the things you put into the
PerThreadRegistry after you’re done with them. Depending on what you put in there, you might be doing this anyway.
This leaves the door wide open for someone to create a
PerRequestRegistry, which combines the safety of request_store with the API of
PerThreadRegistry. If someone’s done this already, I’d love to hear about it!
Try it out
Global data can be bad. Too many globals can quickly lead to unmaintainable code.
But if it makes sense to keep data in a central place for an entire request, go beyond
ActiveSupport::PerThreadRegistry or request_store a shot. If nothing else, it’ll make dealing with global data a little less risky.
each, but I have a problem with it. What happens when you have an empty collection?
If you call
.each, nothing will happen and
 will be returned. Sometimes, that’s what you want. But more often, especially when you’re building UI, you’ll want to handle empty lists in a special way. For example, I usually want to show a different message when I don’t have any data.
nil, you’re stuck writing something like this:
1 2 3 4 5 6 7
That works, but it feels awkward. I’d rather say, “Do this thing to each item in this collection, unless there’s nothing in it, in which case do this other thing entirely.” I want something like
each, with an “or else.”
Rendering Rails Collections
Inside your views, Rails can help. It’s great at rendering collections of things:
render is passed
@users, it renders
_user.html.erb once for each user inside
@users. You can skip the
As a bonus, if
@users is empty,
nil, just like we want! So you can write this, and get the same output as the original version:
It’s a lot more direct, once you understand Rails’ conventions.
What about outside of a view?
If you follow Rails,
render with a collection is a fast, powerful way to render collections of objects.
But sometimes you won’t want to deal with an extra partial to render each item. Or
render won’t support your design. Or you won’t even be inside a view.
The best solution I’ve found so far is
list.presence is the same as:
1 2 3 4 5
That is, you’ll get the list back if it has anything in it, and
nil if it’s empty.
presence, you could write:
1 2 3
This prints each name if there are users in
You don't have any users otherwise.
Still, I could do without the
presence. It feels like a hack, because it is one. If it was supported, something like this might be better:
1 2 3
or the Smalltalk-ish:
1 2 3
or even (gasp!):
1 2 3 4 5
For now, though, I usually just go with
presence or the basic
if blank?; else each pattern.
Which do you think is better? How do you handle empty lists? Have you found a better way? If so, tell me about it!