 My name is Joel Turnbull. I'm a developer for Gaslight in Cincinnati, Ohio. I also head up and coordinate a blog over there in case anybody ever wants to talk about that. But today, I'm here to talk about debugger-driven development with pry. Most Rubyists I know don't use debuggers. Rubyists I know, when faced with a problem, would prefer to ponder code to pop open a debugger and poke around. This is crazy to me. I've always thought it was. Because to me, trying to solve a problem by pondering code is like trying to find your keys in the dark when you're holding a flashlight, but you're consciously deciding not to use it. Um... So why is this? I used to think it might be about egos or culture or something like that, but really, it's pretty simple. I think we've had a lack of really good tools up to this point. But ultimately, my talk isn't about using debuggers in a traditional sense to fix software, but using debuggers as a tool in your workflow to build software. And so why do I think we can do this right now? I feel like we finally have a tool that we can use to explore this debugger-driven workflow. And that tool is Pry. Can I get a show of hands of who uses Pry? Awesome, like everybody. All right, cool. So I'm talking about debugger-driven development with Pry. Conrad Irwin gave a talk not even like six months ago called REPL-driven development with Pry. I swear to God, I had no idea. And both are accurate, I think. But I think both terms kind of undersell what the power of Pry is. So here's my favorite definition. Pry is an IRB alternative and runtime developer console. So if we take the first part of that and we think about Pry as an IRB alternative, anything that you can do with both are REPLs, right? And a debugger is a REPL too. Anything you can do in Ruby, you can do in IRB, anything you can do in IRB, you can do in Pry. What makes both of them powerful is they both leverage this idea of runtime. To me, runtime is all about immersion. It's about being immersed in a live system where you can play with code and you can look at your objects and all that kind of thing. You can validate your implementations, everything you need to do. And it's like looking for your keys with a flashlight. So given that Pry and IRB are both REPLs and they both have this idea of runtime, why should you use Pry instead of IRB? It's got a couple vital workflow features right out of the box. Syntax highlighting and tab completion, both super handy. But what I want to talk about today is some of the bigger game-changing features of Pry. The first one is enhanced introspection. Here's our friend again. Introspection is the ability of a language where you can ask a language questions about itself. And it's built into Ruby, and that's awesome. If you've ever asked a class what method you can call on it or you've asked an instance what class it is, you're doing introspection. What if you want to go deeper? What if you want to know what the class methods are versus the instance methods? What if you want to know what methods are inherited and from where? What if you want to know what state an instance holds onto during its life cycle? You can answer all these questions with plain Ruby and IRB, but the problem is that the amount of effort involved is non-trivial. I would classify it as daunting. So, given that, you know, I would classify this as DRTFM. This is what I point to. I mean, this is the workflow that I like. I like to take the things out of the box. I like to get a feel for what the pieces are. I like to play around with them. I like to try to put it together without reading the manual, and if I get stuck, then I read the manual, right? The second really game-changing feature of Pry to me is extendability through plugins. And the best way that I can show this is to just demo some of my favorites for you. So, I'm going to show you a Rails app. Instead of calling it in a normal way like Rails S, I'm going to call it like this, under the umbrella of Pry Rescue. And I'll show you why in a minute. But here we are. This is an app I've been working on late nights. Please don't steal this. This is a bowling-score tracker, right? You can push any number after you bowl, and it will record how many pins you knock down, right? So, the first thing I'm going to show you about Pry, for those who aren't necessarily familiar, is how you invoke a runtime at any point in your application where you're running Ruby, right? Here, I've inserted a couple of binding.prize, one into my controller action, and one into my template. On the lower left, we have our model, okay? So, let's rerun it with that in mind. Let's go back to our running server, and we'll see that we've halted our execution here, and we've been dropped into a runtime. We can do things in here like look around. We can play lines of code. Let's play the line that sets the bowling game. And then we can look again. When we exit from this, we've returned from our controller. We're starting to render our template, and we've hit our next binding.prize. You can put binding.prize inside your ERB tags, right? Same drill. We can look at bowling games. We can step into our implementations of our methods. Here, we've stepped into the frames method of our bowling game model. Same drill. We can look around here. We can look at ourself. We can go to the next, or we can continue. So, you can see how handy that will be if things aren't necessarily blowing up, but something's not quite right either. So, what if things do blow up though, right? So, here I am. I'm going to spark an exception here by trying to bowl the next ball of my next frame. I'm going to bowl a big nine. I'm going to come back here, and we've been dropped into a runtime because an exception was thrown. That's what being under the watchful eye of prirescue is going to do for us within this context here. So, some interesting things we can do here. We can call the show stack command of the price stack explorer plugin, and we're seeing the whole stack here. This is like caller, but it's alive, right? We can move up the stack. We can move up the stack nine frames. We can move down the stack. We can take a look at the state of any object here at any level of our stack trace. We can look at our stack again just to see where we're at. We're on frame 10. But this is a big hairy kind of rail stack trace, and we're not getting a whole lot of value out of that right now. So, let's call the CD cause command of prirescue and see if it can track down what caused this problem in the first place. There we go. This looks more familiar, right? Here we are in our template, and I will guess that on line 13, we have a problem with frame one, and that's true. So, we just need to add a little bit of a guard in here and say, you know, if we have pins on frame one, let's render that. Otherwise, let's just render a dash. And we get feedback that that's what's going to happen, right? We know that this is a working implementation. So, let's copy our history. This is another plugin called prieclipboard. That's going to copy that implementation into my clipboard. I'm going to edit a file where the last exception was raised. It drops me right where I need to be. I'm going to paste in that implementation, drop back in, and I'm going to ask prirescue to try it again. And we're back, and we've got our dash. So, and we can continue to bowl, right? That's legit, right? A five and a six? Okay, cool. So, what do we do here? We use binding.pri to invoke a runtime anywhere in our app where we're doing Ruby code. We use the pri debugger gem to give us our step next and continue functionality that we expect out of our debugging tools. We use prirescue and ran our Rails app under the umbrella of prirescue to drop us into a runtime when things go wrong so that we can poke around. We use the pri stack explorer gem to navigate the stack and explore state at any level. A few commands we saw were cdcause and prirescue that took us to the root of our problem. We used play and we used copy history to not mess around with copying things with our mouse and pasting them into our REPL, which can be a pain. We used the edit command with the eflag to take us to the file where the exception occurred so that we could fix it. And then we used prirescue try again, which under the context of Rails just replayed our request. We didn't have to reload our page or do any of that nonsense, reload the whole environment or anything like that, right? So it was fast. So that's great. And having demoed pry in a debugger context, I can show some of those things. But really what I find interesting is this idea of pry as a runtime developer console, right? So there's some really awesome aspects of Ruby, right? It's introspective, which we've talked about a little bit and we'll talk about more. And it's dynamic and it's reflective, which means we don't have to compile it and we can change things on the fly in its runtime. And I think we take advantage of these things a lot in the code that we write, whether we're doing metaprogramming or monkey patching or anything like that. But we haven't really taken advantage of these things in the tools and in our workflow yet. So when I talk about workflow and our problems with our workflow, I see big problems with the traditional workflow in Ruby development that I see, right? I mean, by that I mean we write some code in an editor, we save it, we pop over to a terminal, or we pop over to a web page and reload it and run it to see if it worked. And if it worked, we go back and continue on. If it didn't, we fix it, we save, we go back, we reload it, we run it, rinse, repeat, right? What problems do I see with this? First of all, it's disruptive and it's distracting to keep switching back and forth, right? Some amount of context switching is always gonna be eminent, right? But any effort you can make to reduce that is just gonna be a huge win for your flow and your focus. The second problem I see with that is it's just guesswork. We just write some code. Yeah, I think it's gonna work, we save it, we go over, we run it to see if it works. We're just taking shots in the dark, you know? You can ask this guy. Taking shots with a flashlight is much more accurate and recommended apparently, so do that. And to me, it just seems backwards, right? We're like solidifying and codifying our code into our code base just in an attempt to see if it works. It should be the opposite. Our code base should be the record of code that we've already validated, right? And I mean, these things are just kind of things that we accept and we take for granted, right? But I mean, I think other languages have been more effective and intentional about integrating this idea of a runtime into their workflow, you know? And blurring this line between static and running code. Closure comes to mind, right? But we've talked about the awesome aspects of Ruby and there's really nothing that restricts or limits us from doing the same, right? I mean, I think the Ruby language enables it, you know? It's just that up to this point, we really haven't had the tools to do so. So we talked about these workflow problems. How does Pry solve these workflow problems, right? I see that, you know, the introspection and the documentation and the source code browsing, which we'll see in a minute, that's baked into Pry, gives us 90% of the information that we need to write code right now, you know, in most cases. It has a runtime that you can throw your code against and validate it and get feedback on whether it works or not immediately. And it's smart about editing. You don't have to think about what file you need to open. Pry usually knows what file you want to edit and usually knows exactly where you want to edit it, which is really nice. So when I talk about this idea of runtime development, I'm really talking about being immersed in this environment that, you know, loves us and can give us feedback and is alive, right? So we want to spend the majority of our development time there and we want our editor to just be an afterthought. And that's just a place where we just push working code when it's done, right? So let me demo to you a little bit about how I see that working, right? So our mission is to write an empty class definition, okay? This is just a script, no brails involved here, anything. But given a class like name like bowling game, we want to create a file, bowlinggame.rb, and write a class definition to it, like class, bowling, game, empty space, end, right? And I've created a little skeleton here of how I think that might work, right? We're going to read in a string from the command line. We're going to pass that string into a method called file name for class and get a file name. We're going to pass that string into a method called class definition for class and get a class definition. And then we're going to create that class by writing that class definition to that file, right? So let's step out here and let's just run that. So we get an expected error, right? We haven't implemented anything called file name for class yet. So we could jump into our editor and start coding and all that, but why don't we see what this is like? Why don't we use prirescue and leverage that exception to drop us into a runtime and start this runtime development process? So we do that. We get the same error, but the difference is we're inside of a runtime now, okay? So the first problem is pretty easy. We know that we need to define file name for class and we do so, right? The difference is in here, I'm going to raise in the implementation of this class and drop back out and I'll show you why. When we ask Pry to try again, we get in here, we're right where we need to be to define the working implementation of this class. Although I already know that I have forgotten to pass in the bowling game string like I always do. Okay, now we have everything we need. So we have something like bowling game, right? And we're looking for something like bowlinggame.rb, okay? So I'm just going to preview my favorite Pry command of all time here, ls. When I call ls and give it the class or the object I'm working with, I immediately know I'm working with the string and I'm seeing all of the methods that are available to me on that string. And when I say all of the methods, I don't mean all of them. Notice that the object methods are not in here. And you know that if you've ever used IRB to get the method, you always do thing.methods-object.methods, blah, blah. That's just noise, right, in most cases. So the default is just to leave that out. The developer's a prior thought of that, right? So we can try to class down case, which gets us so close but yet so far away. We don't really know where to put that underscore. But as Ruby devs, Rails devs, we know we have things like active support that can help us out with that. So we include that. And now when we ls our class, we've got some extra stuff in here, right? Demodulize, deconstantize, that kind of stuff. And if that's hard to see, we can just ls active support and flector itself and see what's available to us there, right? So we needed an underscore. I see a method called underscore there. Tab completion is nice. All right, so now we're getting a lot closer. All we need to do is stick on our file extension and I think we're done, right? So there's our file name. So let's copy that. Let's drop back into our file. Right here exactly where we need to put the implementation. Let's put it in there. And let's save it and continue on. Okay, I'm not going to continue through this whole thing in the interest of time, but I do want to implement this last method inside of the create class so I can talk a little bit more about what I love about introspection, right? Let's just reflect on what we did there, though, with the workflow, okay? So we used pry rescue to leverage the power, you know, to leverage that exception to pop us into a runtime and we used the runtime of the debugger not to fix code but to drive our development process, right? So we're in here. We've raised inside of our create class method. We try again. We've got our file name and we've got our class definition. And we have this file class, right? And I know about this file class and I know this is exactly what I need to get the job done but I mean, this is exactly what happened to me. I don't really remember exactly what I need to do here, okay? So I can pop out Stack Overflow or I can pop out to Ruby docs or something and take a look or I can just ls file right here and just take a minute to look at this and think about all of the low level Ruby introspection acrobatics you would have to go through to get this brained up of information right here. We've got all of the methods that you can call on instances of file. We've got all the methods you can call on the file class. We've got all of the methods that file inherits from its superclass and we know what that superclass is, I.O. We also know all the constants involved and if there were state involved in class or instance variables we would see that here as well. So all of a sudden I'm informed about everything I need to know about this class and I have a pretty good idea just from a moment's notice of how I can use it or what I can do to play around with it. So let's... I see the right method and I don't really want to write anything or mess with anything right now so I'm just going to look at the documentation for it. Okay, it opens the file it optionally seeks to the given offset writes a string then returns the length written. Right ensures that the file is closed before returning this sounds like what I want I page down, I see some examples there Great, I'm ready to go. Alternatively, I can do the same and call show source, okay which isn't doing a lot for me here but when I'm in my own code base this is what I lean on more often, right? So let's get our bearings again and let's give it a try, right File, write File, name Class, definition Okay, something happened, it seemed to work, right? How do I know? Really easy to shell out in pry you get really nice output exactly what you would expect which is I can't say the same for IRB in that case I have this thing called bowling game in there and let's take a look at it that looks correct to me so let's just do that again I'm going to copy the history again and then I'm going to remove bowling game just to make sure that my script is doing what it promises, right? Oops, and it's not there anymore so let's go back let's put in that implementation and drop back in let's ask pry to try again no more exceptions, do we have our bowling game? We do, okay so what happened there, right? We used the debugger and the runtime as a tool for driving our development process and build our implementation not just to fix software, right? We validated our implementation before we codifying it and we reversed this traditional workflow that I've got so many problems with we explored and we informed ourselves about how to use our classes without context switching, right? We stayed focused in one tool and then we didn't have to reload our libraries and our environment every time we wanted to run code so it was fast and you know we're in the testing track here so I should probably talk about testing a little bit and that might have felt a little bit like TDD to you I mean it does to me, right? As practitioners of TDD I mean I love TDD I think most many Rubyists do but I mean what I love about it most is it keeps me focused and having a test suite allows me to aggressively refactor my code and have the confidence to do that and I'd rather do that up front than after the fact, right? But as practitioners of TDD we learned to love failure TDD is all about making something fail than writing the code to make it pass when we have bugs we want to exercise that exact piece of code that's giving us the error and get that error, you know before we then go because we know we're about to do something cool and fix it, right? So we love to see red and so a practice that's centered around failure is naturally suited to a debugging tool, right? I mean that's what a debugging tool does it's meant to handle failures catch failures and then give you the tools you need and enable you to fix them as quickly as you can and we've seen how awesome a debugger of Pry can be and so the promise of this talk is that I'm going to deliver a Pry-enabled TDD workflow but before I do that I mean I mentioned that I've had a kind of a specific experience that made me a believer that this workflow even happens I mean I've actually done this before I'm not creating this, right? and that specific experience was this Does anybody know what this is? Smalltalk, yeah this is a screenshot of a Faro Smalltalk IDE and I spent about a year or so in the 2000s writing a web application in the seaside framework in Smalltalk for a company in New York City with a team of guys and really delving into object-oriented programming and learning so much about it I really learned to love this, right? but what first struck me when I saw Pry and I saw the LS command was it reminded me of this, right? Right here we are looking at a ZN Client class in a Zinc HTTP package we see the class definition right there at the bottom with all of the instance variables that this class makes use of listed out on the right-hand side we see all the methods that we can call on an instance of this class and we can click on any of those to see the source code of that method that's all you need as far as I'm concerned that gets you 90% of the way there almost every time right? so I wanted to demo a Smalltalk TDD workflow because that's the other really cool thing about Smalltalk and different thing about Smalltalk is that it really enables this awesome TDD workflow and I don't think I'm going to have the time to do that unfortunately but the TLDR is this basically okay so if I write a test and I try to exercise a class that doesn't exist like bowling game here Smalltalk's, when I save this Smalltalk's going to say that doesn't exist what do you want to do? and the first option there is to find new class you just click it and it does it when I try to call a method on that class that doesn't exist Smalltalk asks me if I want to implement that method and when I say yes it drops me into a debugger a runtime where I have everything available I need to create the implementation to correct all the things I need to do so I'm not in the static code editor here this is like living system so I can do so I just want to return 0 when I save my test pass that's the idea and it's small but small changes in user interface can really turn like a daunting experience into a really motivating flow it doesn't take much when I talk about this power of pry and we've talked about some of the plugins and how powerful they are and how pry is so easy to extend even I can do it so I've kind of gotten the ball rolling I've pushed a little code up to github it defines a new command called define it in pry right now it's just a pry RC and you can define pry commands in your pry RC or you can do gems or whatever and that's where I want to move it but this is my attempt to kind of get this workflow going in Ruby right? so alright so here it is here's my pry RC I'm creating a command called define it and then I'm implementing every pry command implements this method called process and that is what is going to happen when you call the command from within the pry repel right and so I'm kind of using this idea that we demoed in the last demo of when I hit a name error I just want to automatically generate an empty class definition and move on what it will also do is when I hit a no method error it's going to generate an empty method definition and put you right in there where you need to be to implement it right? so let's take a look so once again real quick though so here is a spec and a set of tests that exercise this bowling game class right? the first test just verifies that bowling game is a thing the second test says if I ask a new bowling game for its score it should return zero the third test says if I bowl a four then the game score should reflect that and the last test says the price the score should reflect that sum so when I run our spec under pry rescue pry rescue it does different things in different contexts so just like before pry rescue is going to break on unhandled exceptions just like it always has done but in the context of the spec it's also going to break on assertion failures so you can poke around and get those fixed so here we are we we've got our first exception it's the name error an initialized constant bowling game so let's define it alright didn't see it but it did it next believe me alright so next we've got our next exception here undefined method score for bowling game are no method error so let's define it you guys didn't believe me but there it is alright there we go we're in our empty method let's TDD we just need to make the test pass so we'll return zero out of there ask pry rescue to try again another exception here no method error undefined bowl for bowling game so let's define it now I feel like we're going to need to keep track of a little state so let's we want to make that the fix num we want to return score out of here now instead and let's initialize score to be zero when you create a new bowling game let's save that let's try again alright that worked we're in our last test here and we've hit our first assertion error right and expected nine and it got five because we're not doing any of the the totaling yet so let's this is another way you can call edit you can just ask it to edit the class and it'll know what file to drop into and I feel like we're going to want to keep track of our scores now so let's just push those fix nums on a scores array let's get our score by reducing those values that's how you keep scoring bowling right just add everything up alright cool and let's let's initialize it with an array with an initial value of zero as well let's try again what was our own there alright for example zero failures our spec thought we didn't fail at all you know it's pretty cool so what happened there alright I mean basically we again we use pry to drive our development process we used our own pry plug in there to kind of make that a little bit easier and within one runtime we fixed all of our assertions exercised our class and implemented the whole solution right no reloading, no nothing like that so in conclusion embrace your runtime and all things don't read the effing manual use a kickass flashlight if you're fixing software use a debugger and when you use a debugger use pry thank you very much