 Jack Danger, it's a silly, silly name, but it is mine. And I work at a company called Square. We're in San Francisco. And a few other places, we have New York office, Atlanta, St. Louis, Waterloo Canada, and Tokyo. It's a pretty great place to work. Because Square sent me here, I feel like it's obligatory that I tell you a little bit about how we do engineering. I'll just sum it up by saying that we do billions of dollars in financial work with our own manufactured hardware that we create, code the firmware for manufacturer, and then send out to all of our people, and all the iOS, Android, and JavaScript applications that all the customers that we care about use. And on the other side of the internet, we rack our own hardware in least data center cabinets and manage all the networking all the way up and all the services that run on those completely. And we do it all with just about 300, 400 engineers. And we'd love for you to join us, because we do it all in a really secure, perfectly secure, and really, really pretty way, very well-designed way. And it's a lot of difficulty. It's really hard. So please come join us. We'd love to show you how we do it and have you help us do it better. All right. This talk is about the Ruby Environment Variable API, which in my description of the talk, I actually said, I'm not going to call it that. And then Avdi was like, no, that's great. You should totally call it that. So I'm calling it that. And I actually kind of like it, too, because what we're going to talk about is the things that you can do with environment variables, which I'll explain in a second if you're new to them, to tell Ruby how to behave. And this is the core, actually, of how all of the tools that we use that are not Ruby itself operate, how Bundler and RBM and RBM and Truby, and I'm missing some of them, how they all actually work. They work through environment variables. And we're going to talk about how to debug your machine when they go wrong. Another name for this talk could be require cannot load such file. Because when you get this error, something in the chain of stuff I'm about to show you has been misconfigured. To begin with, let's talk about the ideal world. So the perfect world is this. There's one version of Ruby. It's at, let's say, bin slash Ruby. And you run it as, let's say, root, because you don't have to worry about users and permissions and stuff. And there's one argument, one argument only, and it's one file that contains all of the code that there is. You've just copied out of your favorite gems, just copy out of the Rails source, just paste it in the top of this file at the top. And then, as you go down, you paste in other dependencies until the very bottom, there's your app. And you run this, and it runs, and it never has to call a require. And that's perfect. And that's what we're gonna do. That's not what we're gonna do. That's ridiculous. It's also just not the real world. The real world has a lot of rubies. Like a lot of rubies. And your application requires a lot of files. This is the reality of it. And your application, you don't say require slash full path to file because that would be extraordinarily tedious, error prone, two tied to specific versions, and it would be impossible to manage. Instead, you just say, I want this thing, give me this thing. In fact, give me the right version of this thing, and the right version of this thing considering these other things that I also want. And it should just work. Except when it doesn't, you know, you can just require no kogiri. I cannot load such file, no kogiri. Oh, also, please forgive me the syntax highlighting. It's hard to do it on slides. So sometimes I get it wrong. Also, I'm kind of blind. So you're gonna see like seven shades of yellow that I can't distinguish. I tried to make them all the same. I really did. Now, can we make this make more sense? Leslie don't think so and so do I. And we've done that mostly through these projects. We have a bunch of tools that wrap up the complexity of working with different versions of Ruby and different Ruby dependencies and make it really easy and intuitive to just run your code and not have to think about things. Also, I would like it very much if you would just pretend there are logos on the slide because some of these projects don't have logos and I didn't want them to look bad next to the projects I did. Now, in spite of the heroes who have built these projects and the incredible amount of work that has gone into making them work across many different kinds of systems and with many different dependencies, this is how most people solve problems with their environment. And this is like kind of written up as a joke but then I've been asking people during the conference. So if you can't find the right file or if Bundler doesn't seem to be working the way you want, what do you do? It's almost exactly this list. Like nobody actually said farmer, right? But if you can't read it or if you'd like to hear my voice say it, it goes rake or whatever command, then oh, whoops, I'll bundle exact rake, all right. Oh, wait, no, RVM use the right thing and then bundle exact rake or actually you don't close your terminal, start over or ask a co-worker and then if Danielle can't figure it out, she'll tell you close your terminal, start over. And then after that, you're like, well, whatever. RVM implode, just start over completely, reinstall RVM and then like maybe install some Ruby so you're compiling them from scratch. Sure, that'll fix it. Or rerun the bespoke setup script that you and your team or your company or your client use. In fact, reboot your machine, actually I probably should have been up there higher, or just buy a new computer but then definitely become a farmer. This is blindly trying things to stem the bleeding from the wound that was caused because our tools were so sharp they cut us. And what we need is not, there are people who blame their tools, right? And we all blame our tools sometimes. But the people who built the tools that I mentioned in the previous slide, they've done incredible work. They continue to do amazing work and all those tools are getting better. What we need are not for those people to stop doing it. What we need are just more heroes like them to do it. And that's what this talk is about. This is indoctrination. You are all about to be equipped with all the tools necessary to join the folks on projects like RubyGems and Bundler and RVM and CHRuby. By the end of this, if you don't want to do it, it'll be because you choose not to do it, not because you're not capable. Now, how to become a superhero parts number one through infinity. There's one step. You just learn the tools. You want to be somebody who everybody else calls a wizard. If you've ever talked to someone who's not a programmer after you started programming, you'll realize that they think that there's something about your brain that just works differently. And that's not true. You just sat down and got really frustrated for a long time until you got a little bit less frustrated. And we call that understanding. And now you know how a thing kind of works. And that's all there is. And then they're like, wow, how'd you do that? And you're like, I mean, stack overflow mostly, right? So you learn the tools. We're gonna talk about a couple tools here. And the main thing, because it's in the title of my talk, I'm gonna start with is environment variables. Now, environment variables I've seen trip up even really, really brilliant people. The people who seem to work with them really easily are just people who've worked with them a lot. But what they are is it's a global hash of strings that or hash map or dictionary, however you want to call it, that your program inherits from its parent program. So if you are in a shell session, you've inherited it from something earlier on in the boot cycle that created your cell session. Any program you execute will inherit your environment variables. Now, if you want a better image, I think of it like a really big purse, like a silly, large purse that you carry around with you and that your parent gave to you and then you can give to your kid. That is how, well, a copy of it. You don't take the actual purse. You don't make a copy of the purse. You give your kid their own gigantic purse. That's how the environment works. And the environment looks like a bunch of individual environment variables, but think of it as just one thing. The environment variables are all just the keys and a hash and their strings and the values are also strings. And you can have some things that are just for yourself, but anything that you export gets passed on to your child processes. One other note about this presentation. Please dink around on your computer and follow along and type in whatever you want here. I'd actually love that. I would love if you got a little bit of muscle memory and some of the things we're gonna do. I won't be offended. In fact, now that I've said that, I will give myself permission to pretend that everyone on their computer is following along closely. You can also just watch me do it in the not live coded because I'm a scaredy pants, but in slides code. So if you were to type nv, you would get a printout of all of your environments. But some of those might be things like back, like control characters and like funnily escaped codes that make colors show up funny. So if you just wanna look for something specific, especially if a giant nv, just grab through it for something interesting like home. And you can find the home variable will point to your home directory. It won't be particularly surprising. You can also play around with setting them. They're usually at all caps, the keys. That's just a convention, they don't have to be. Here I'm setting odere equals uh-oh and then I type nv and I grep for odere. And well, that's weird. If you follow along, where did it go? The output is blank. And that is because of this other interesting property of environment variables, which is that when you inherit them from the process that created you, you get what looks like all of your parents' environment variables. But not really, you got all of the exported ones. So anything that is at any point in the hierarchy of an exported, you'll get and your children will get. But you can have some just for you, just for your own internal bookkeeping. Odere belongs to my shell session. And whatever process ID it has, that environment variable is tied to in the kernel. Now, if I were to echo it, I can see it, but it's not gonna show up when I type nv. Well, that's funny. Now, if I export it, and there's two ways to do this, you can either export it at the same time you set it, export x equals y, or export x and x equals y on different lines. Either way works. So here I'm kind of like doubly setting it, but you can play around. You'll see that it's actually pretty intuitive. And then when I type nv, I grep it. Okay, now I see it, it'll show up in the output. Here's a visual way to imagine that. When your computer starts, there's one program, or one process. That process has a process ID of one. Process ID is just integers. And if you're on Linux, this is called init. That's a process name. If you're on OS 10, it's called launchd. If you're on Windows, the whole of this talk, I can't help you with, I'm sorry. And process one starts all the other processes. All the processes in your computer exist in a big hierarchy. There's no other parent process. There's one that starts first and it starts the others. It's actually pretty simple. And the first one might set some values, one of which it exports, one it doesn't. Happy equals yes, excellent. All the children will then be happy. And then food equals pizza, just for itself. And that was just like, muscle memory might get food equals pizza. I must have been like from a third grade exercise or something, I don't know why it just came up, but I made it to the slides. And then later on, some processes start, then you click on something and your shell starts. And happy equals yes. And you actually, that used inherited that. Also you said, oh dear equals uh-oh. And then when you type env, you are not printing your variables. You are running a new process called env that prints its variables. And it did not inherit oh dear, but it inherited happy. However, if you had typed export oh dear equals uh-oh, then env would in fact identify that because it would have inherited it. So far so good? Awesome, I got some nods. I'm gonna pretend you all nodded. That'll make me feel great. Okay, let's talk about the most important variable of all. This one absolutely dominates things. This is the path. Now, the path is slightly misnamed because it's actually a list of paths. It is a list of places that the operating system looks for commands to be run. Now, you can type env, pipe, grep, path, and you can also just echo path. One note, environment variables, you set them without the dollar sign. X equals Y, but you read them with the dollar sign. Echo dollar sign X. That's just the syntax. Now, I wanna show you something. I did a little animation here before I knew the slide. The projectors would be so big. I'm gonna show you a little something that you can type that will make this more readable, where you can echo path or anything and you can run it through the translate tool which turns colons into new lines, which for the path is pretty handy because then you get it printed like this. So this is slightly edited version of my path. And what the computer will do when I type a command is it will look in each one of those things, those paths to the command. We're gonna see exactly how it does it. It is not very sophisticated. Apologies that you can't run this if you're on a Mac. S-trace or system call trace is something on Linux. It's really, really powerful. It really demystifies some stuff. It makes you feel smarter because it makes the computer seem dumber. And you can't run it on a Mac. There's a thing called D-trace and a script around that called D-truss, which is kind of like this, but usually requires pseudo and an El Capitan. Pseudo is kind of broken. So you're not gonna have as much fun running that on a Mac, but take my word for it, the slightly edited output of this on Linux when I'm running this command. And to explain this, what I'm doing is I'm typing S-trace then some command. That some command happens to be the simplest shell, SH, followed by C, which is just a string to run. It's the same as if I typed SH, then got a prompt and then typed that command. So I'm saying run this program, which is just a shell running one command that doesn't exist, and show me all the system calls it makes. And now we're gonna step back a second because the system call needs a little bit of explaining for someone who doesn't know what it is. A program at any point in time can be doing one of two things. This is true on all computers. It can either be reading and writing to memory, basically just doing math and keeping track of its own internal state, or it can be asking the computer, the kernel, to do something. And every kernel, every operating system has a different set of functions that you can call and different ways to do it. But what you do is you're either doing math with your own memory, variables and stuff, or you're asking a computer to do something. You're opening something, you're writing to something, you're reading something, you're getting the time of day, that sort of stuff. Now, when I tell Strace, hey, run this command and tell me all the things it asks the computer to do, the kernel to do, and show it to me, it tells me this. It first, well, it exact this thing, let's just ignore that, it ran a command. And then it called stat on a bunch of files. And stat just means status or file status. You can just run stat in your terminal right now, stat space dot, and you can like stat the current directory and you'll see some information of the current directory. Who owns it, is it writable, that sort of stuff. When was it created? So it's trying to stat all these files to basically find like, is this a file and can I run it, is this the thing, is this the thing? And it gets this error on the end, which enoent is a C error code. It expands to no such file or directory. But it's looking for, if you notice, some long path and then the exact thing I typed, missing command. Some other thing than the exact thing I typed. Over and over and over. Let's line these up. Path, just print it out on the left. And then the S trace, what is it doing? What is it asking the computer to do on the right? And you can see that the computer is not all that sophisticated. What we're doing is we're just iterating over, we split on the column, we iterate over the entries in the path and you just jab on to the end, whatever the person typed. You type missing command or git. It says, all right, is there a brr-brr-brr-git? Oh no, okay, well is there a brr-brr-brr-git? All the way through until it finds one that's executable and it runs it. That's how commands work. You can see it a little bit closer up, it's just iterating through. How excited are you? How excited are you? This is great because that concept of a path, I went a little bit slow there, I know a lot of people in here actually probably are very familiar with paths. That concept is gonna be really portable to the world of Ruby in a second. Because there are two other paths we're gonna work with. The next one is called the gem path. This is the one that mystifies most people, myself included, until I had to write this talk, because we don't do things like activate gems very often. If that phrase doesn't sound familiar, it's probably because you don't have to do it, because you're not hacking inside Ruby gems or something. But there's a gem path. And you can see it by running some of these commands. You don't have to, it's probably not set on your machine unless you use a tool like RVM or something that sets it by custom. We can always get what the default one is with that top commands. And you'll notice that it's just a colon separated list of paths on your machine. And let's talk about how it's used. You can do this on your computer. This is something that I tested a few times. Definitely works. If you set gem path equal to itself but with a new directory propended to it. And let me walk through this line by line. Gem home means where gems get installed. Gem install, whatever, will place that gem in gem path. And if gem path isn't set, there's like a default one inside Ruby that's like inside the Ruby install directory. But if you set it, it'll definitely install the gem right there. And if that directory doesn't exist, it will create tempo what on your machine and put the gem there. And then because you want to be able to find the gems in the place where you're installing the gems, you make sure that that, we're gonna make sure that that gem path we set or that gem home is at the beginning of our gem path. So we can find it. And notice there's no like double quotes around this setting of gem path. That's just because everything in bash defaults to a string. So it just sort of assumes that strings, you don't have to use quotes that much. Gem path equals something colon gem path, just concatenates. Now when I gem install eally dumb, which is a gem I'm sometimes proud of and sometimes not, you could look at the source to find out why. And then gem list, you'll see that it's there. It's in gem list. And if you ls to the directory, you'll see not only has the directory been created and not only is the gem there, but it created the whole tree structure of gem folders. There's things like a specifications directory and a cache and a gems directory. And they're all there. So it's now a place where lots of gems can be put. Oh, I had an animation to make that bigger. That's nice. So we have two paths so far, path and gem path. There's one more. And this is the one that dominates our problems in Ruby require... Was it command I found? No, yeah. The require problems, where you try to require something and it just blows up. And that is the load path. Now you'll see this dash e and some other things. I'll try to explain them just once whenever I count them. I apologize if I missed one, but dash e means execute the string that follows as if that string were in a file and you just had Ruby that file. It's just a shorthand for one lining some Ruby code. So I'm printing the load path. Now my machine, it looks like that. And also that dash r means require. Dash rpp dash e something. Is the exact same as saying Ruby run a script where the first line is require pp and the second line is pp load path. Identical. R stuff means require. You can do it a bunch of times. E means execute this. I think you can do it just once. So here is my load path. Now this load path might look suspiciously similar to what we saw on the operating system path. The s trace of this simple Ruby program, which I had to do on Linux, which had a 1.8 install, but it's still true. This is what Ruby asks the kernel to do when it tries to require a missing file. If I line this up with the other, and there's spaces on the left I had to add because you'll notice that Ruby will try to require two things for each load path. And that's because .rb files it looks for, but then if you can't find it, it will try to find a .so file, which is a compiled Linux shared object. It's basically just compiled C code, not an executable, but a thing that can be used by an executable. And if you run this on a Mac, you'll find that there's also a .bundle it looks for, which is a mock OS executable format. But anyway, it is the exact same thing as we saw before. Is that visible? Yeah. All it does is it iterates over the load path when you require missing file, and it just sticks forward slash and missing file.rb on the end over and over and over until it finds one. You can imagine how this is written, right? It's basically load path dot each do string or do dir, dir plus thing you're acquiring, and then return if you find it, or actually eval file.read the file, otherwise raising error. That's basically what it is. Slightly more complex, but not really. And if we were to try it out, you could see I'm printing some very simple code into a new Ruby file. And then I'm requiring my file, which I put in the current working directory, which is not actually in the load path, for the same reason the current working directory is not in the operating systems load path by default. For security reasons, if you get check out my project and I have the top level and executable called CD, and you try to leave that project, I can do whatever I want, right? That's terrible if it picked my CD over yours. So by default, current working directory is not in either the path, or the gem path, or the load path. But you can add it. So I'm gonna add it in my E. I'm just going to add the little string load path, jabby, jabby, stab, thing, dot. And then now when I iterate over the load path, we'll actually expand that to the current working directory and stick a Maya file to RB on the end and when we find it, we run it and it prints high. And now because this will be important when we see more about how the environment variables are used, I'm gonna show you two other ways to modify the load path. There are three ways to mess with it. And all three are used by things like Bumbler. Ruby-i. is the exact same as add.loadpath. You can add dash i as many times as you want when you're calling Ruby. If you ever look at the verbose output of some complex rate command, you might see, especially if it's like building a gem, or like trying to install Nokogiri or something complex, you might see it's like rake-i.slashlib dash capital i, some other thing, and like, because it just, that sets it up so that when you just require something, it knows which thing to require and it's able to find that file. You can add as many things as you want to it and if you're on a big Rails app and you're on a version of Ruby pre-2.1, the reason require is so slow is because every lib directory of every gem on your machine and many other places is in the load path, it can be hundreds and hundreds of entries long and every time any of those gems, any of them has a file that calls require, it iterates over that and looks for every single file and pen it to the end. You'll see things like require Rails and it'll be like dun, dun, dun, dun, dun, dun, slash Nokogiri slash lib slash Rails, not found, well, then it'll look at all these places. It's really bizarre, but you can get it huge and really inefficient, though modern Ruby does it pretty well. And the third way, besides the load path variable in Ruby and the dash capital I is this thing called Rubylib. Now this is just your environment variable. This lives alongside path. You can modify your path, you can also modify your Rubylib. As long as it's exported, Ruby will pick it up and it is a, from the documentation of you, man Ruby, it says it's a co-inseparated list of paths to add a load path. That sounds a lot like path, right? That's exactly what it is. And anything you add to there and you require something, the file will be searched for right there. Okay, the three paths so far. They all work the same. They're all editable by you and they are each co-inseparated list of directories. Now if you get nothing else out of this talk, it's that these paths are how things are found on your machine and if you're in a bind and nothing's working and you're on a deadline, you can do on your machine or in staging or, heaven forbid, but maybe in production, you can remove RubyGems and Bundler, all that. Just get rid of it, not require it, not use it and you can hand edit these values and your Apple boot and it'll work perfectly. You don't want to do that because it's brittle. You want to use a tool that does it automatically for you. But that's all you actually need. So this comes down to the basic strategies we have for how we find the right version of a gem. If you have a version of Rails that requires a specific version of Rack and two versions of Rack installed, one too new maybe that doesn't work with the version of Rails you're using, how do you activate the right version? There's two approaches. One is for each project you have, use a different gem home and gem path and it's got just the gems you need for that project. And this is what PIP and virtual end, if you have any Python exposure does. This is what RIP did, which is when Chris Wonstrath of GitHub, back in the day, we started doing some Python stuff and he was like, wow, this PIP just has a file with a list of the requirements I need and the diversion numbers and it can just install them and keep a local cache in the project. This is great. So he wrote RIP for Ruby port of it, which Yehuda saw and knew of as he was building initial forms of Bundler. But Bundler does a different approach. Bundler, it's not quite right to say it ignores gem path and gem home, but it doesn't use them except when necessary. And it Bundler identifies each gem you need because it has the lock file or the gem file, the gem file lock. It knows what versions you need and it knows how to find those on disk. And so it looks at each one and it finds what load path would be necessary to add to your load path for what directory so that when you require Rails, say, the right Rails has its files found. Don't put the wrong Rails in the load path. Make sure just the right one. So that's the other approach. You can have either a directory or a path with only the right gems in it. We can have all your gems in big blob, but something has to be smart enough to know which gems to pick and add a load path by hand. And with that, we're gonna see how Bundler does it, given that giant bag of possibly unlimited gems with mismatched dependencies. So here, and I apologize if this is getting a little bit removed from what you thought you'd be learning, but bear with me for a second because these variables will make more sense in a moment. This one slide is how Bundler works. If you ignore the installing of gems, Bundler has a thing where it'll read your gem file and it'll recall gem install. It actually uses RubyGems for that. It uses the actual RubyGems facility of fetching and installing, unpacking and all that stuff. But if you ignore how Bundler gets your gems, when you type bundle exec something, bundle exec does this slide. It sets RubyLib equals to the load path necessary for when you require Bundler, you get Bundler. And then RubyOpt equal to bundler slash setup or dash r bundler dot setup. If you haven't noticed the dash r and the dash capital I, the spaces after them are optional. It makes it kind of weird. So this RubyOpt in Bundler is dash r no space bundler setup, but Ruby processes that as dash r with a space. It knows how to do that. I find that kind of confusing sometimes. And RubyOpt is, I kid you not, just a string of stuff that anytime you call Ruby will just get inserted right after the word Ruby and before all the rest of it. It's just like, it changes the way you boot Ruby no matter how it's invoked. Rake, IRB, Ruby, whatever. As Ruby is booting, it's like, oh, is there a RubyOpt? Well, let me just jam all that stuff into my own flags, my own command line flags and pretend it was there. And by doing these two things, by setting those two, what Bundler Exec does is it makes sure that it is available when you type require bundler slash setup. You get a gems bundler, whatever lib slash bundler slash setup dot rb, it's found, it's read. And then RubyOpt, go ahead and require that so that it is found and read. And then before your script is even running, just bundle exec itself just with right the beginning. It looks at all your gems, it finds the load pass for them, or it finds the paths where you can require them. And it adds those a lot of load path. So if you type env, you'll see something. If you type bundle exec env, you'll see something completely different. And I recommend actually running those through, diffing those two, because you'll see exactly what bundler is and isn't doing. And that's all the bundle exec is. Bundle exec just sets these so that when you're booting Ruby, it's doing something. And just so that, I don't want you to leave you confused, so we're gonna talk through the difference between the environment variables and how they fit with the command line flags. Maybe you're new to both, maybe you're new to just one, but RubyLib is the load path, and dash capital I is also the load path. And colon or dollar sign load path is the load path. RubyOpt dash r rails is the exact same as saying Ruby dash r rails. And RubyOpt is setting it to dash i fake dir, something else or something will actually just totally work because RubyOpt is a great way to take somebody completely by surprise and do whatever it is you wanna do with their code. All right, you've now learned, or at least been exposed to, everything about environment variables in Ruby that there is. There's no more hidden like, oh, if I knew that API, I would understand how RubyGems works, or I would understand how bundler works. This is it, this is how it works. These are the only hooks to get some code to run. After this point, once bundler setup starts running, this is the load path, the problem is entirely in Ruby code. And like maybe it is doing or isn't doing something you want, but you can debug that separately. As long as you know what environment variables are set, you'll be able to debug the situation you're in. If you're in a crisis, something's not able to be found, that's all you need. And we're gonna talk a little bit about the tools that have automatically managed this for us so far and where we are in current history. So RubyGems is totally old, it's great. It started early, we needed it. It's been the bedrock of how we've managed dependencies in Ruby. I'm sure it will continue to be because the gem format is what we all use. It came out in 2003. Then RIP came out in 2009. More is a prototype, although I'm not sure what Chris intended with it. And then RVM also came out because people were straddling 1.8 and 1.9 at the same time. It was a very confusing time. And Bundler followed fast after a couple years later, Sam Stevenson built a thing called RBN. He meant it to replace RVM. Now they exist kind of in parallel. A year later, a guy named Hal wrote again, built chruby, which is my personal strong recommendation. It has like tens of lines of code. It's tiny. It does just the right thing. You say, change Ruby to this Ruby. It's like, all right, there's your path entry, there's your gem home, then there's your gem path. Basically all it does. It's great. Strongly recommend it. Now, can we, you think, talk about how all these parts fit together in practice? Yeah, let's try. All right. I'm gonna show you two versions of this. One is I'm gonna talk about what piece of the environment variables and variables each tool cares about. And then we're gonna talk about how the tools actually relate together. And I'm gonna leave you with a bit of a challenge that I don't think I'll even have to say explicitly because it'll be pretty obvious when you see how these tools relate. It's somewhat problematic. Ruby. It cares about its own internal load path. It cares about Ruby opt, a lot. It really lets you just beat itself up with Ruby opt. And it cares about Ruby live to set the load path. And just to belabor the point, you've heard of Ruby as a virtual machine. The path is to your machine as a load path is to your virtual machine. And so much as running commands on Linux or Mac is the thing you do, requiring files and Ruby is the thing you do. And the paths are one-to-one. This is the path for governing how to configure this machine, virtual or not. So that's all Ruby cares about. If you grew up to the Ruby source and you exclude lib Ruby gems, which is a separate project actually that's periodically just imported in, you won't see things like gem underscore any of that. RBM cares about setting the path so that you find the right Ruby and then setting gem path in gem home. And other RBM asterisk, other tools that set your different Ruby versions. And Ruby gems cares about reading gem path in gem home so that things can be installed in the right place and found in those places. Bundler doesn't care about any of these. It'll read them all and mess with them, but you configure bundler through its own environment variables bundle underscore. And if you look through the docs, there's like a ton of stuff. You can really, really customize it. More than I've ever needed to. You can set your own gem file in a different directory. You can set your own like startup script over here. But oh man, I really went back and forth on leaving this pun in. That's just not even a necessary slide. It doesn't need to be there but I'm kind of glad I left it in. Okay, we're gonna blitz through all the how they connect again in a second. And I promise you this will be over soon. We start with Ruby. Ruby has a require method. When you require a file, Ruby will look through the load path and find it. We looked at that. We saw through strace how that works. And it's pretty reliable. Ruby gems aliases away require to gem original require just so we can remember it. And then replaces it with its own require. Which a kernel require goes away. So kernel require is now not the thing you thought it was. No longer iterates over the load path. This is not, maybe all that intuitive. And then bundler has a method amazingly, I love this, called reverse Ruby gems kernel mixin. That just undoes that, puts things back where they found them, and then takes the gem method that Ruby gem's defined and deletes it and recreates its own kernel.gem method. So you could say that Ruby gems is the bottom, or Ruby is the bottom, and Ruby gems is built on top of that, and bundlers built on top of Ruby gems. And that would make a simple digraph of dependencies. It's pretty straightforward. And I think it's a reasonable assumption that you would think that those three work in exactly that way. In fact, Ruby gems, thanks to the great work of many people who build it, but this particular feature is air codal and tenderlove. They built in Ruby gems the ability to parse and understand a gem file.lock. So you can actually read your bundler gem file in Ruby gems, using only Ruby gems. You could have an app with a gem file.lock just require standard library Ruby gems, and you can read that. Which means we now have bundler and Ruby gems which are mutually aware, somewhat, I wanna, yeah. They can delete each other's methods and do mutually aware projects that together are the system that we depend on for managing all our dependencies. And this is actually, oh and bundlers managed separately from Ruby, but Ruby gems, while managed separately is checked into Ruby core. But I think, raise your hand if you use bundler. I think, yeah, right? Okay, you also use Ruby gems because bundler uses Ruby gems, but Ruby gems is in Ruby core. So this setup here is the reason for this talk, and let's anyone misunderstand me, there's nothing wrong with the work that has been done by anybody on any of these projects, but these projects are not finished. This is not what Ruby is to deserve. They deserve, as a straw man example, they deserve to use Ruby and then the project called like gem or something that everybody uses that works perfectly, that everybody contributes to that solves other needs for making sure gems are in the right place and packaged and locked and deployed and everything. That's what people new to this environment deserve and it should be that clean. And these projects are getting there, but folks like Andrea Arco are working, Andrea Arco are working super hard to make it better and tender love and everybody in Seattle and outside of it working on Ruby gems are working super hard to make this great, but what they need is you, and now that you know how environment variables work and how the paths involved work to work with these tools and to manage your gems, it would be really great if you would look into maybe contributing to one of the open issues on either Bundler or Ruby gems and seeing if you can become a contributor that can make some of these rough edges a little bit smoother for the next person who comes down our path. And with that, thank you very much. The question is, can I explain more about how Ruby gems uses the gem file.lock and what it does with it when the gem file isn't present or the when Bundler isn't present? I can't necessarily. I know that there's a full featured parser and it gives you a DSL for working with the contents of that and if I were to look closer in the Ruby gem source and I bet Eric Hodel would come by and correct me in a moment and say like, no Jack, I built a whole thing that downloads Ruby gems for you and installs them but I don't know. So I used to say there's a in the Ruby source or in the Ruby gems project, look for I think it's called lockable or lock file, lock file I think and it's actually pretty simple. It's just like a kind of wrote up parser as he's wanted to do and which I trust him to do and it works pretty well and I guess the only answer I can really give you is it's open to use however you want. You can just use it or you can just like read that file and do what you need with it. Maybe it's like more for your own scripting, I'm not sure. All right, thank you very much.