About the talk
RailsConf 2019 - Code Spelunking: teach yourself how Rails works by Jordan Raine
Have you ever wondered how something works deep in the guts of Rails or been stuck when a README says one thing but the code acts another way? Guides and docs are often the best way to get started but when they fall short, you may need to get your hands dirty.
Using method introspection, this talk will show you ways to confidently explore Rails code. By looking at common problems inspired by real-world situations, learn how to dive into Rails and teach yourself how any feature works under-the-hood.
Let’s go spelunking!
Good afternoon, everyone. Thank you so much for coming and I hope you're all having a really great couple days. I realize, so far. Just wanted to introduce myself. I'm Jordan rain I from Vancouver Canada. And if you want to reach me, I'm at JN rain on most places. I'm a Staff developer at a company called Cleo and like I said, we're based in Vancouver and since 2008. We've been using rails to try to change and improve how lawyers and law firms practice law. And if you're interested in this come talk to me afterwards and we are remote
friendly. So today's talk is about how getting lost in somebody else's code is sometimes the best thing that you can do and it starts with something that I think a lot of us have experienced pretty often. Now I love to program and if you give me a problem I can go for hours and hours, but sometimes it can feel like half of our time is spent here trying to figure out what to do next. I've been reading Ruby code since the rails to three days. But before that I spent my time writing websites in PHP and that was a time when this
is how you build a website. At the top of the file you opened a connection to your database and you crafted a SQL string sometimes included user input as well. And then once you had the result use that to render the page blow. There were no models here and there were very few classes for the most part. This was the whole thing. And as odd as it seems to look back at this now adding little Snippets of code to a into HTML file to do everything myself seem like a very reasonable thing to do.
It was all up on the screen in front of me. So when I became stuck, it was easy to find my way out of it. But obviously we know now working like this can be limiting the possibilities for what I can do or reduced to my ability to cram things into a single file and to make sure that I always do things correctly. And when it came time to go something more complex A co-worker pointing me at a framework called rails. It really changed what I thought was possible. I didn't have the craft sequel anymore
by hand. I could use active record and I did not to copy and paste my URLs all everywhere. I needed to use them. I can use URL helpers and I could put my code in these nice buckets of behavior using model view controller. And pretty much everything became easier and with generators rails and even write the code for me. But because everything wasn't in front of me anymore when I got stuck it was deeper than ever before. And when a stack Trace was released it often only included one or two lines of my code. I didn't know the
difference between an application trays or framework trays and even if I did I wouldn't know where to look to figure out more. My best defense was to Google the air and if I found a fix that was great and if I didn't I was left to tweak the code until I made the error go away. No everyday as developers you're faced with problems. And one of the beautiful parts of using rails is that it often has a solution for you, but the longer you work in an application no more features. You add the more likely it is they'll come across something unusual. No, one in the code
team has seen it before and you can't find any mention of it in the rails docs or the guides and it puts you back here. During the session I want to show you how to get out of this when you hit a wall and only way out is to go deep within the rails codebase. You'll know how to find the answer. My Hope Is that when you sit down at your desk next week you'll have new techniques that can help you more deeply understand how your app behaves and you will be able to teach yourself any part of rails works for
parts starting with how you spend your time A few years ago a group of researchers wanted to measure how to help her spend their time specifically they were interested in how much time we spend on program comprehension. In other words how much time do we spend it trying to figure out what to do? The way they did this was to install software onto the computers of 78 developers tracking everything that they did these developers work at two companies working on real projects in over two weeks. They are able to gather over 3,000 hours of data.
What they had that data, they categorize it into four groups. The first was comprehension, which they defined as reading code reading bug reports and front understand project requirements is also included any time spent trying things on your browser or running your test. The next with navigation which they defined as browsing through software specifically, this was searching Google for answers. There's an error code base for that right method to call or reading about how something work on stack Overflow or similar websites.
The third was editing which state of mind is adding modifying and deleting code will most of us tend to think of as programming. And then finally other which included news reading news online or shopping or sitting on Twitter. For what we're doing today. It makes a lot of sense to group navigation and comprehension into one bucket. So we're going to do that. And before I show you what they found think back to your last week. How much time did you spend stuck or reading on stack Overflow or searching Google? How much
time did you spend writing code? So here's what they found on average developer spent 82% of their time on program comprehension and navigation only 5% of their time on editing. only 5% writing code There is no question that we need to understand what our code does that time and comprehension and navigation is not wasted but when I look at this, I can't help but think the less time I have to spend here the more time I can spend here writing code in providing value. The study also found a laundry list of things that increase
comprehension time and when I read through these and thought about it it really boils down to two things that we can do to make it better. First one is we can write code that is easier to use and change and we can improve our ability to find answers. Now the first one helps future you the more time you spend today adding tests already documentation or being careful about how you name things the less time you'll need to spend later figuring out how it works. And the second one helps you today understand any understand any
code that you're faced with. So we're going to focus on that. The state wasn't all bad news though. It also found this that the more experienced somebody had the less time they spent on program comprehension and when they looked at the differences between the people with more experience and less experienced they found that the people with more experience approach to problems with techniques that more easily that allowed them to more easily and more quickly come to an understanding wasn't that the more experienced people knew the answer. They just knew how to
find it. So if we want to learn to improve how we understand code we need to understand what we're up against and that brings us back to rails. What makes rails difficult to understand and what can we do to prepare for it? First one is that it's big. How big will a new rail 6 app comes with 77 gems 12 of those jams come from the rails repo and include 75000 lines of Ruby code. And if you include those other 77 gems it brings it it more than a quarter-million lines Abreva code. Note for many rails apps that means that the rails framework and also appendices
is larger than the app itself. The next one is that rails is complex. If you consider how it's structured it's in these nice buckets of behavior. They're all small components all built together and you can look at this. You can see this when you look in the ancestry tree for important classes. Like action controller base, which is 65 and saucers and active record bass which has 67 now. This is not a bad thing because it allows people to more easily add and extend functionality, but it also means that a single path through the code can touch
many different files and sometimes span multiple gems. I'll give an example of this. If you call when you call Save on an active record object. Here's what happens. Really goes out and it finds the same method and then a call and then that method called super the Ruby goes back to the ancestor list and finds the next module that implements. And then that method called super and then that method calls super and so in the end the behavior for save a split across four different files, the order of which is dependent. The
order of these calls is dependent on the order that the modules were included. Until he just went and red Dakota be difficult to understand exactly what would happen. Next rails is dynamic or you might call it magic. It does everything it can to make our lives easier. but that sometimes requires code like this. Which defines a method whose name changes depending on the value of the call that variable or cold like this which innards over list greatest ring, and then evaluate the strengths as Rubicon. I know you're probably not going to see this code and many code bases until
when you come a come up against it when you come to rails to be difficult to understand. I finally rails isn't your codebase think back to when he started your current job. How long did it take you working today in and out with the same code to get comfortable with it? To understand what all the names mentored to get used to the coding Style. Now consider how much time you spent working in the rails internal codebase. If you're anything like me you're not opening for Tequesta rails on a regular basis not part of your job and that means because we're not
living in that code based on a regular basis. It means that we don't gain an understanding of that code base for free just because we're building rails apps at work. So we need some way to navigate around that size and complexity. And that's where codes blinking comes in. Tumi code spelunking is the ability to confidently explore an unfamiliar codebase. and I squeeze into the tight corners and follow the twists and turns the fine, whatever answer you're looking for.
Maybe a sand rail. Maybe that's in a different gym, and maybe it's the code base at your job. When I go spelunking, I like to follow these three guidelines ask a question start with what you know, and only read code relevant to your answer. So let's give this a try with a pretty simple question. What's the difference between try and try Bang? Let's just a quick review of a review of what these do if you call length on a string Returns the number of characters in a string if you call
length on nil, it will raise an exception. If you're not sure if something is nil or not, you can get around it by using try. If you call try length on a strangle get back a number you call try lines on nail you get back now, it won't raise an exception. How much eye bag? Well, it's looks pretty similar trybang includes return the number and try Bang. O'Neill returns nail so they look really similar that they look identical from this perspective the why do they both exist? If your first instinct is to go look this up in the rails Doc's that's good. If there are rails documentation,
if there's rails documentation, I can answer your question you should use it but let's pretend for a moment that this is our view. And the airplane Wi-Fi is not working. So you have no real stocks. And this is a great time to use method introspection. If you're not familiar with nothing intersection it is it is a really really useful feature of Ruby where you can ask objects what methods they respond to and do a few other really interesting. Thanks. So let's go back to work. Let's pause our existing example and go look at some different code to see how this works.
So this is my dog. Foxley and as you can see he is a good boy. And we have a class here that we use for this example. So if we go to the console and we ask we invented a new dog instance and we can ask it for all the methods that it has. Now. When you call this you get this overwhelming list of method names that they don't fit on this fly. And this is the case for almost every object in your system rails and Ruby include a lot of things by default and so it can be a little bit
overwhelming but there's a really simple way to help this take all the methods from object Avenue and subtract them from the dog class and then we just get back the two methods we implemented. And because the method object or method method returns just a normal Ray. We can do other things with it like search through all the methods for anything that includes the word instance. We can go further than that. We can actually ask it for a specific method. Let us sign it to a variable and I must get the name method once you have
the name method. We can ask it where was Define. And this will return the path and line number of that method. But and we can go go to desk and read this or we can just ask Ruby to do it for us now everything I've shown you so far is just standard Ruby, but this is a little bit different. This comes from a gem called message source, and if you use rails, you don't need to worry about it. It's already included. But if you'd like to use it somewhere else just add this Jen. So once we call this method we're going to get back a string
of the source code. But because we're at the console, it looks not great. Are we can get around this by using the. Display method which will print a string to standard out. Let me get back exactly what we had on disc. This works for our code, but it also works for code in gems. If you ever wondered how something works in rails you can just take a peek. And often times when you do this, you learn new things. I didn't realize that created took an array of attributes until I was repairing the stalk. And remember active records save method
we can explore that as well if we yet so we have for implementations and if we get the same method he gives us back one from active record suppressor. That's where it was Define. Then we can ask for the super method no, give us back one from active record transactions and we can keep going get one from validations and one more. from persistence and this will return the exact order of calls as they're going to happen at runtime now, there's no other methods. So if we call it one more time,
we're going to get back now. And there's one got you to keep in mind as you're looking through code and trying to figure out what's going on in the hood. Sometimes you'll see this Source not found. and that's because part of Ruby is written in C and the methods and functions that are in NC are not going to be available to you the source code and there is a way to get at them and I'll touch on that at the end but most for most of the case is not a problem cuz most of rails is not see. Okay, so
back to our existing example will let's get the stores for try. Zoom in on it. But before we read ahead remember the third rule only read relevant code. I started the first line and try to get rid of any code that we don't care about so that the name know and block given. Oh and we called it we gave him at the name and we didn't give a block. So this is Falls which means this code we don't care about the get rid of it. We're left with is something pretty simple if
respond to Method Publix and method. Okay, easy enough. What about Tryon Mill? They go to work. Not all Israel. Is this complicated anything to give it a little turned out? Okay. What about trybang do the same thing get the source code take a closer. Look only read with relevant. This is still faults and we don't care about this code. What we're left with is something pretty similar. We can pull the two up side-by-side and now we can see one important difference between the two. Trybang always calls nothing.
So if you go back to the console and we send a method name that string doesn't know about try will return them and try bang will raise in air. Okay, so I think we have our answer now. Oh, sorry. I can one more thing. Let's go figure out trybang on mail and looks pretty similar to both exactly the same. So I think we now have our answer. What's the difference between try and try bang over until they're the same everything else. Trybang is more strict. Let's just review for quickly. What we did there.
We asked a question. We looked at a couple methods we knew about it. They're right in the question where they're at. And then we read ask in about 20 lines of code and read another 6 to get her answer. But that is for a really simple question that we could have probably answer from the rails dogs. So can we use it for something that's more interesting? Imagine yourself back at work next week your manager comes over and says we need a way to keep track of which requests and food a job. Lately, we've been
having a lot of failed jobs in the background, but we are unable to figure out where they came from what user triggered it and what request was being made at the time. I really like to be able to print out the request ID when the job fails he figured a way to add that. You're active job knowledge. So far has been that you called the form later magic happens and then your job is running the background, but it couldn't hurt to look at the code plus do that here that we're starting with some disco days. We have application controller with a before filter
number for action and it takes a request ID and assigned it to the current request ID. Later on in the controller action. We NQ job when the job runs we want to have that request ID available to us. Some of the groundwork has already been laid in application job. We have this request ID out of you and in that report job that were in queuing we print it out to the console just a test our Behavior. so if we do this now set the request ID and to the job and then over on our sidekick console is hell.
How do I add a request ID every job? Well, let's start with what we know and pull up the perform later source code pretty simple calls Java instantiate and then return value do we need to Diamonds be into method next to go one level down when we need the return value of that the do that weekend. Call David Sanchez on a job and then grab. Thank you message from there. When we did it though, we get an error. It turns out that jobran queue. Java instantiate is a private method and you probably shouldn't ever call it in production
code. But when you're flunking you're allowed to bend the rules a little bit. So let's get around that by sending it and then getting the inkey message from there. We get back is a bit overwhelming more code that we seen so far and I imagine most people can't read that and even when I'm at my computer, this can be overwhelming and so I like that kind of squint a little bit trying to find the natural breaks in the code for this I see three Take them one at a time. So the first junk set the bunch of variables
depending on the options that are given we assign those back to attributes on the job. Okay. Well, we didn't provide any options. So you just ignore that. And look at the next one. Just skimming through this. I see a couple methods that seem interesting. So I'm just going to keep that. move on to the next The last one is just a big conditional looks like depending on if we were successful or not. We'll return something and if we weren't will return something else and we don't really care about the return value here so
we can get rid of that, too. What we're left with is pretty straightforward now that's really read and try to understand so we run some callbacks and then depending on the value of schedule that will Branch since we're not scheduling anything here. We can ignore that too. And we're left with is a single line of code that calls out to Q adapter. Thank you. So, let's follow that. We can talk you adapter on a job. I mean grab the into source code. And here we see something interesting work. We're pushing a job into psychic and as part of that we call job.
Serialize. But spoken the job. She realized next by getting a new job getting to serialize method and taking a look at what it does. It's just one big hash literal. made up of attributes on the job and Global values Now it would be really nice if we could somehow sneak our request ID in there. And because this is an instance method on the job. Did I pretty easily inside of application job? Add to realize it's called super and then let's merge R-value into that hash. Let's
see if that works by send the request ID get a new job calling serialized and then grabbing the request ID and it's that so are we done? Do we add it? Give it a try by 7 to request ID and keeping the job and then checking in sidekick. Still no. So we were missing something here. Something active record active job is doing we haven't found yet. But we've come to the end of our path. We've got all the way to a person a job into psychic. So let's review what we've done so far. We've looked at reports
job. Formed later. Let me call Java instantiate and Then followed that down to NQ. That lettuce too cute after donning to which told us about job. Does he realize what you were able to extend and then psychic client that push? When we think about where to go next week and have a look at the full process that we understand so far. We call perform later active job does calls in queue serialized the job pushes into psychic then in the sidekick process something happens and then perform gets called on a job
and we want to know what's in that box. And when I'm stuck and not sure where to look next I go look at the gym code. Now we can do this by closing our rails console and then calling bundle open. Give it a gem name. It'll open it up and I'll text editor. Now if you run this when you see this message, it's easy to get around just make sure to set the editor environment variable to whatever command command line tool opens. Your editor. This is for Sublime is a 4 vs code them and I believe most will have this and once you do this, make sure to put
that in your bash profile. So it works in every terminal in do want to do that. We can open up active job. But now we're faced with a whole gym full of code. Where do we start? We know something's happening in sidekick. So let's just yes and search for sidekick in the code. We get one piece of code that mentioned psychic adapter. And again, it's a wallet Axel its squint Financial sections. And again, I see three. The first of the NQ we've already seen this. This is the thing that pushes the job into psychic we can ignore that as well. The second is he into
at method and it looks really similar to NQ. The only difference is this time stamp again. We're not scheduling anything at 2. What we're left with is a sidekick job with a one-line before method takes job data, it add something to it. And then it passes that along to base. Execute. Be really nice if we could open a console right there. Let's do that with By Buck by bug is a building to bugger that comes of any new rails app and want to add it restart your psychic console and thank you the job. when that Jabra Angel Caesars a bye
bug prompt behave really similar to a console and we can inspect the job data. When we do that we can see it is the serial attached that we had a propensity to even pull that out. The next let's dive into Bayside X Cube. We look at this code. It does a couple things runs callbacks that calls deserialize on the job data. Diving to deserialize next and we can see that at constant eyes has the job class and create a new one and it calls deserialize on that job passing along the job data.
And we can get that class by just copying this code down door console. And grabbing the decertified method. Want to get that we can see this is where the important work happens. We take values from job data and some Global values. We assign them back to actress on the job. 30 great if we could just a sign request ID in here, so why don't we go back? Application job and do it in the same manner as we did with sterilized. So we keep all the existing Behavior. So
are we done now? LACMA console that request ID call perform later when you go back to console. It works. We have done it with only our terminal and text editor. We have added request ID for every single job and to do that. We had to modify serialization and deserialization. Somebody gave us a question. And to figure out what to do next we looked at a method that we had called many times and understood pretty well. And to find an answer we had to skim about a hundred lines of code and really understand about half of them. So we
have come to the end of our spelunking journey. I hope you've all enjoyed your trip and you've been wonderful companions here. I hope that Did the technician cover today? I've only just scratched the surface. There's so much more to these ideas. And I really encourage you to practice them on your own if you're interested in learning more. It's all so we're taking into by bug which is the debugger. We just touched on briefly. It can do a lot more than we looked at today stepping through your code stepping into methods. and pry which can replace your default rails
console and even that doesn't a booger and it has a lot of features to Ruby Splunk, like short cuts for meth and introspection index highlighting command history and it can even They can seek out if you really want to or actually just found this out recently. You can ask an object where it's a monkey patch, which is really mean. so other people's code can be confusing can be frustrating give me scary and I'm sorry to say that it's not going anywhere.
But here's a good news. There are things you can learn and practice I can help you more confidently explore any unfamiliar code that comes your way. And if you invest in your ability to understand code. A small Improvement here can have a really big impact on how much code you can write. Remember, you don't have to know the answer. You just need to know where to find it. Thank you.
Buy this talk
Access to all the recordings of the event
Buy this video
With ConferenceCast.tv, you get access to our library of the world's best conference talks.