 Let's get started. I really can't see any of you. This is super glaring stage. OK. So this is my talk. So when Bundler came out in 2010 or whenever it finally came out, it did something really cool. It installed your gems. And it let you use those gems in your application. It was great. Today, Bundler does something really cool and installs your gems. Let's use them in your application. Still pretty great. But in the eight-ish years since then, Bundler has needed thousands and thousands of hours of development work. And it's maybe not super clear to most people why that was necessary if the starting point and the ending point are so similar. So kind of what happened between then and now is mostly what this talk is about. Before we dive into that, let me at least introduce myself. I'm Andre Arco. I go by indirect on the internet things. That's the tiny picture of my head that shows up next to Commits and GitHub issues. I work at Cloud City doing Ruby and Rails consulting. We mostly that means that I join teams at other startups that are already doing web development work and participate in the team as a senior dev and make suggestions and architect things totally hit me up if that's something that your company would like. I also wrote a book. I'm super fond of this book, and I keep talking about it because it makes me really happy that I learned Ruby from the Ruby way first edition in the early 2000s. And I got to update the Ruby way for the modern Ruby world. I think the third edition covers up through Ruby 2.3, so it's even kind of relatively still up to date, which is cool. My other company is Ruby Together. Ruby Together takes funds from companies that use Ruby and uses those funds to pay for development work on Ruby open source like Bumler and Ruby Gems. I will talk a little bit more about that as we get to that part of the history, but of course, the reason why you're here for this talk is Bumler. I've been on the Bumler team since 2009-ish, and I've been the lead developer since 2012-ish. And so, in this talk, it's going to be kind of a combined historical retrospective and walkthrough for Bumler's advanced features, which were almost all added after 1.0 came out as we discovered things that people needed or cool things that you could do. So by the end of this talk, hopefully, you'll have a better understanding of why Bumler has needed so much development over time, exactly what we managed to do with all that development and know about some of the cool Bumler features that you can use to do fancy and complicated things. So, here we go. When Ruby was first released, sharing code with other developers meant copying files that ended in .rb, and then using require, maybe after having edited the load path variable. It was a lot of work. There was no idea about versions, except maybe if you put a comment in the file and said this file is version something. It was not great. Later, Jim Warrick and Chad Fowler, who just gave the keynote last night, and Rich Hickey worked together and created RubyGems. And with RubyGems, you could install things with just one command, which was super great. It was so much better than downloading tar balls from people's websites. It was so good. And after a few years, we noticed that installing gems was easy, but changing the gems that you were using after you'd written code that used them was really hard. And this is not obvious up front, when you're like, oh my God, I installed this gem so fast. And then three years later, you're like, oh my God, I installed the gem and it was the wrong version and I couldn't tell why my application didn't work. So, this caused a lot of pain where installing gems would sort of suddenly and unexpectedly give you different code than you thought you were going to get. And it meant that very briefly, the most popular approach to having dependencies was to take all of the gems, put them in your applications, get repository and commit them. Which, please don't do that, it's a really bad idea. But for a while, that felt like the only way that you could put your app on another machine and actually be confident that it would run. So, there's actually a lot more details about that kind of history in an entire talk that I gave a couple years ago at RubyConf called, how does Bundler work anyway? It's pretty great actually to kind of realize the context that Bundler came into and solved the problem of if that wasn't something that you had to live through, made me really happy anyway. So, despite being used in nearly every Ruby application and script today, Bundler was developed in response to a super specific developer need originally, which was web applications that have tons of dependencies. When Bundler was first prototyped, it meant web applications that were written in a no longer existent Ruby web framework called Merb. Merb had kind of pioneered this idea of splitting up your framework into lots of tiny gems so that you didn't have to use the whole thing. Rails at that point was still only like five gems total. And so, Bundler was needed super badly when Merb was like, oh, there's 20 gems and now you have to make all of them work together. And as time went on, Merb and Rails teams agreed to merge. Bundler kind of switched from targeting Merb applications to targeting Rails three applications. And Bundler existing meant that Rails apps can now kind of like out of the box need dozens of gems rather than just needing four and life is still okay. So, there were like two specific insights that drove the development of Bundler as a tool. The first insight was that you needed an install time dependency resolver, which was not a thing that Ruby had. And the second insight was that the resolution process needed to produce a lock file so that you could reuse the results of that resolution on other machines as you went to install the application on a new developer's box or on the next server you've spun up or whatever. So, what is a dependency resolver? It takes a list of gems that you asked for, asks those gems for the gems that they need, asks those gems for the gems that they need, et cetera. And eventually it has a complete list of every gem that you could need and then it checks every gem's list of version requirements to make sure that they all fit together. For example, something depends on rack 1.0, something else depends on rack less than 2.2 and those requirements are compatible, like rack 1.1.1 works but you have to do a bunch of work to figure that out and be sure that that's true. So, how about install time? Well, if you do your dependency resolution while you're installing gems, it means that you know in advance whether your application can actually start up. And if you do dependency resolution at runtime, you start requiring gems and everything seems great until you hit something that isn't compatible and then your application crashes. Less than ideal. As you can probably imagine, finding out about these problems before your application starts up is a large improvement. So, third thing, how about the lock file? So, that just means that we, after figuring out the list of gems and versions that actually can work together without crashing, we write it down in a file and those gem names and version numbers together make up the bundle that gives Bundler its name. Amazing..(audience laughs loudly.) Installing and running Ruby software in a deterministic and repeatable way on different computers is basically the entire rationale for Bundler's existence. So, all of that tooling comes together and it's pretty recognizable even across the 10 years that this project has existed. You put your gems in a gem file, you run bundle install, your gems show up, you have a lock file, you bundle exact something to make sure that you're using the right version. Pretty cool. At the time, a super nice new feature way back then was the idea of being able to use gems straight out of Git repositories which was not possible before Bundler let you do that. Using a gem that like forking a gem and making changes to it before Bundler allowed Git repository gems was a gigantic, unbelievable hassle. You had to like fork the repo, make your changes, build a new gem, put it somewhere you could install it, maybe change the name, maybe give it a weird version number so that it didn't conflict with one of the public version numbers was a giant hassle. Bundler made it way better. Another new thing that happened was the bundle gem command so that you could make gems that worked inside Git repose with Bundler. At the time that Bundler came out, the most popular gem creation thingy was jeweler but jeweler made gems that didn't work in Git repositories so we were like, oh no, we have to do something so that this all works together. And Bundler also makes managing gems easier. So I guess today it might feel kind of obvious and natural to use Bundler to manage your gems but at the time it was really, really, I cannot stress this enough, really controversial to use Bundler to manage your gems. And the entire concept of Bundler was really met with a lot of resistance by existing very experienced devs who were like, well, we already have our crappy workaround for how to make sure that we can install gems somewhere else. I'm not really sure why I would want to use this tool that has a different workflow that I don't know yet. So we really, the entire Bundler team spent months just typing furiously on the internet telling people, no really, Bundler is actually good. And this talk that I gave at RailsConf in 2010, the first slide is why Bundler? Like I actually had to convince people that Bundler was a good idea in 2010 and it was a lot of work to do that. Fortunately, within a couple years people actually used Bundler and eventually they were like, oh, actually, this is really great. And so Bundler was kind of like a really popular, mostly used by most devs tool. And then once people started using it, we immediately had new things to argue with people on the internet about like, why is it so slow? So at this stage it was kind of like, no one's arguing that Bundler is a bad idea anymore, now it's just people saying, okay, fine, it's a good idea, but I don't wanna use it because Bundle install takes millions of years and that makes me not wanna use it. So part of that was because just making Bundler 1.0, we had been very focused on making it actually work. We rewrote Bundler like completely different UI, different commands, different internal implementation of how the Bundle worked like three times in a row, like 0.8, 0.9, 1.0. And so when we shipped 1.0, we were mostly just really excited that it actually worked. And on top of that, when Bundler first came out, there weren't any giant five-year-old applications with 750 gems using Bundler because it was brand new. And so as Bundler got more popular, either applications grew over time until they had tons of gems or people started retrofitting Bundler onto their old applications that already had tons of gems. And we suddenly wound up in this state where we were like, wait, you have how many gems in your application? Are you serious? And I can say with absolute certainty that when we shipped Bundler 1.0, we could not have imagined that there would be a single application with 600 gems. Like, and that is not that uncommon today. Sadly for totally unrelated reasons, you might think that since installing big applications was really slow, that installing small applications would be really fast. Also not true. Even if your gem file only had one gem in it, Bundler still had to download a list of every single gem in existence, just to make sure that it knew about the gem that you were going to install. As you can imagine, downloading that list maybe took some time. It really takes a long time. And so we were in this place where small gem files were slow to install because of downloading more data than we needed and big gem files were slow to install because we had literally never imagined you would install hundreds and hundreds of gems in one bundle install. And so we hadn't optimized it at all. So this is about the time where the Bundler team was sitting around saying, man, this really sucks that we have to download so much data all the time. And then Nick Quaranto was just like, oh, hey, I added a new API to RubyGems. It lets you ask for only the data that you want. Here you go. It's live in production. Go. And so it was pretty great. If you're interested in the details of that particular moment in history, Terence Lee and I gave a talk at Ruby on ALES 2012 called bundle install, why are you so slow? And we talked about the technical details of the API and the changes that we had to make to Bundler to make that work. The very short version is if you had a fast connection, or I should say accurately, if you had a low latency connection to the RubyGems servers, the new API was amazing. You could ask for just the information you knew you needed and then find out you needed some more information and then ask just for that and then find out you needed some more information and ask just for that. You downloaded a lot less data overall and as long as the latency was low enough that that multiple round trip thing didn't take too long, this was a lot faster. Unfortunately, it meant that you needed to make a lot more requests to RubyGems and if you didn't have a low latency connection to rubygems.org, like say you lived outside the US, there are a lot of devs that live outside the US. Even if you just had a wireless connection inside the US, you could, it wasn't any faster. If you were very lucky, it was basically a wash and you just downloaded less data but if you lived in South Africa, forget about it. I talked to some South African devs and they talked about how they would run bundle install after getting to work so that they could go make their coffee, drink their coffee, come back, wait a few seconds, watch bundle install finish and then get to work. It was like a multi-minute process to just run bundle install once when your round trip time to RubyGems is, I don't know, a second and a half. So the slowness was a huge problem and in response to these issues, we actually started working on a new index format that would mean we didn't need to make tons and tons of round trips to RubyGems but it took like three years to get done with that so I'm gonna have to talk about that quite a bit later. Hang on, we'll get there. At this time, we also continued to develop Bundler. We added stuff, we made it better for people. Some especially notable things from this era include the clean command which lets you get rid of gems that you aren't actually using anymore. This was a super big deal for CI or for Heroku where you push many versions of an application and you change the gems that they're using and so before Bundle Clean existed, your CI setup and Heroku had to choose between the crappy options of either install all the gems again every time there's a new version which is very slow or commit to letting your application have infinity space for gems because there was no way to get rid of gems after you stopped using them. Bundle Clean meant that they could do both and it was great. We added Bundle outdated so that you could see what was out of date without having to actually update it. We added support to the cache command to let you cache git repositories as well as just dot gems and we added git local development where you could check out a git repository and make changes to the git repository gem at the same time as you made changes to your application and Bundler would watch the repo and automatically update the commit in the lock file to make sure that you always got the right code. I'm gonna talk about that in more detail later. We also added support for application Ruby versions where you could put in your gem file the version of Ruby that your application worked with and it would raise an exception if you were trying to run it on a different version of Ruby which especially when which version of Ruby you were running was sometimes confusing was a really big help. You would never accidentally run your app on the wrong version of Ruby and then have really weird confusing bugs later. So this feature wound up causing some problems but I'm also gonna have to talk about that later. So for now let's move on to the next section of history where we find out all of the ways that we caused ourselves problems with the stuff that I just talked about. So the biggest thing that happened in 2012 to 2014 is bundler adoption really really really took off. Bundler 1.0 in 2010 averaged like 8,000 downloads per day. Bundler 1.1 came out a couple years later and averaged like 20,000 downloads per day and then by like August of 2012 it was averaging 30,000 downloads per day and that's when we discovered that we had executed a distributed denial of service attack against RubyGems.org. Yeah, basically the servers just couldn't handle how many bundler users there were and all those bundler users were like I wanna install gems, tell me about the gems, RubyGems and then RubyGems ran out of server capacity and crashed and no one could install any gems at all and everyone was very sad. So to at least let you have access to gems again we had to turn off the API that bundler was using to make installs fast and installs went back to being slow. And now we were back where we were two years ago. So at this point a team of people including myself, Terence Lee, Larry Marburger some other people built a new standalone application that served just this API and we actually got the RubyGems team super cooperative and with some help from Evan Phoenix and David Radcliffe we actually got the old URLs to keep working with this new completely separate application that we could scale independently of RubyGems itself. As you may have guessed, while this did provide an API for bundler users it provided a new completely different set of problems that we had never had before. One of the problems was that we had a completely separate database from rubygems.org and so if you pushed a gem we still had to find out about that gem somehow before you could install it in a bundle and we tried web hooks, we tried database replications and we tried scraping the API, we tried importing database dumps. There were problems with all of these options as I'm sure anyone who's had to deal with this kind of distributed data synchronization thing knows already automatically. We were young and naive and had never had to do this before and we were like, how hard could it be? It could be very hard, it could be very hard. My favorite problem that this caused was propagation delay between pushing a gem and then being able to install it because you had to wait for a RubyGems to finish processing and then send that information over to the Bundler API and then you had to wait for it to finish processing and then you would find out that even though you'd waited long enough for that to happen the CDN that we were using at the time would just kind of randomly not update depending on where you lived in the world and so if you were Canadian every fourth Wednesday you couldn't install new gems for three hours and there was nothing that anyone could do about it. The other downside of the standalone API is that we built it in Sinatra and SQL because it was super tiny and hyper focused and just returned some JSON and Marshall data but despite it being a completely reasonable decision I think at the time it meant that not as many people were familiar with Sinatra and SQL in comparison to the RubyGems.org app which was written in Rails with ActiveRecord and so we wound up with a lot less contributors to the Bundler API I think partly because people didn't know it existed but also partly because even if they did know it existed they weren't familiar with the framework and tools that we had built it on top of. There are entire talks about this process of ddossingrubygems.org building a new API and optimizing that API until it was fast enough to actually support all of the Bundler users in the world. I gave a talk called Deathmatch Bundler versus RubyGems and Terrence gave a talk called Bundler Install Why You So Slow Server Edition both in 2013, if that's interesting to you you can check that out for a lot more details. On top of working on the API we did still make some cool new stuff in Bundler including multi-threaded installs which was awesome you no longer had to wait for the last gem to download and finish installing before you could start installing the next gem. In those mythical 600 gem applications this makes a really, really, really big difference. We also switched to a non-recursive resolver. Smitsha kind of showed up in the issue tracker one day and said, I've never done this open source thing before am I doing this right? I completely rewrote Bundler's resolver. And I said, wait, you did what? And it actually worked. This was a really big deal because on JRuby in particular the old resolver was recursive and would call itself again and again and use the arguments to the function to retain the state in each iteration. And unfortunately on JRuby, every time you call a function there's not just a Ruby stack with arguments there's also a Java stack with arguments and Java stacks are big and basically maybe every, if you had a big application or at random every fourth time when you ran Bundler install on JRuby it would crash because of running out of memory for all of the recursive call stacks. So that was not cool. This fixed that. And Smitsha went on to become a core team member on Bundler after his first PR. So ridiculous. We also added HTTPS support. This is like when around the same timeframe that GitHub was saying, oh hey, you can actually connect securely to GitHub and download things. So got that put into Bundler and some uncool stuff. We had Bundler's first CVE security issue. The super short version is don't put source in your gem file twice without making sure that at least one of them is attached directly to a gem. If you have multiple sources, basically someone, because rubygems.org is public, anyone can push a gem with any name. And so if you say kind of blindly, oh just pick gems from any one of these servers, someone can put a gem that you don't want on one of those servers and you might get that instead. And that can be, it doesn't have to be, but it could be very, very, very bad. So to avoid that you should use the block form of source where you say source something, do some gems after the first source and then you're fine. You'll at least get the gems from the source that you expect to get them from. That's a little bit of a simplification but you can read more about it if this alarms you and you use multiple sources and you'll figure it out. This is the point at which having a separate API totally sucked and while the API was an optimization, so you could still install gems if the API was down which meant we didn't like set it to wake us up at three in the morning if it went down. We figured, excuse me, we figured people could install gems slowly until we woke up in the morning. But anytime it went down we did get a lot of people complaining about when we'll install it suddenly being very, very, very slow. And so it was exciting to have this great practice DevOps project, really? What's the downside risk? You're not gonna break anything. But it slowly developed into a huge source of stress because you're thinking about making sure that it's up all the time every day. And no one wants to deal with lots of stress especially while not getting paid and doing it for free. Not very surprisingly at this point, many of the people who had previously been very enthusiastic about working on Bundler and RubyJams became very less enthusiastic. But fortunately as that was happening the Ruby community kind of simultaneously came up with several ways to start funding development on these projects. Ruby Central which organized this fine conference has a fairly regular practice of giving out grants to work on Ruby open source projects. Basically if they have left over money after running the conferences they will pay devs to work on Ruby open source with that money and that's pretty cool. Grants to myself and other people on the Bundler and RubyJams teams meant that we were able to finish releases, keep working on the new Compact Index, bunch of other stuff. Around the same time Stripe started their program where they give open source grants to let people work on open source for I think the Stripe program does three month grants. And one of the grants went to a college student who was named Sam Giddens and he worked on Coco pods at the time, the Objective C equivalent of Bundler and they didn't have a dependency resolver yet and so his Stripe grant was to build a dependency resolver for Coco pods. Coco pods happens to be written in Ruby and so we kind of got together and inspired and agreed to have this dependency resolver get used by not just Coco pods but also Bundler and also RubyJams and today it's even used by Berkshaw, the Chef dependency manager and we all share this like good, well documented actually relatively understandable dependency resolver library which let me tell you is a huge improvement over the previous Bundler resolver. This is also the point at which Stripe and Engineyard started funding the Bundler project directly and on top of helping fund some development work that money made it possible for us to incorporate Ruby together. And that's how I have this shirt. And so Ruby together takes funding from the community and uses it to pay developers to work on community projects which is pretty cool. Today we pay for work developer time spent on Bundler RubyJams, the RubyJams Rails app, the gemstash gem server, ops work on the rubygems.org servers and we even started paying Christoph, the developer of the ruby toolbox to start working on the ruby toolbox 2.0 open source project which is pretty cool. So while Ruby Central does give out grants and pays the bills for the servers, they don't fund kind of the ongoing maintenance that means that the servers don't go down in the future. As Ruby together grows, we'd love to pay for more Ruby open source project development time but we can only do that if you and your companies sign up as members and help fund that for us. So please check that out and think about doing it. Ooh, I just knocked my coffee over, sweet. So with support from Ruby Central and Stripe and Ruby together, the Bundler RubyJams project started to see work done by paid devs on a pretty regular basis. We couldn't afford to hire anyone outright but we could afford to pay for a few hours a week from a few people and probably not too surprisingly, paying people meant that they showed up on a regular basis and actually got things done and that was amazing. We were able to ship stuff that had just kind of been sitting around, oh, we should do this someday for literally years. One of the big things we were able to do is finally move every request to rubyjams.org behind the Fastly CDN which was a huge improvement from before because before you would make a request to RubyJams, RubyJams would say, oh, this is a gem that's in Fastly, go talk to Fastly and then you would go talk to Fastly and then Fastly would say, oh, I don't have a copy of this gem yet, let me go talk to RubyJams and then RubyJams would send the gem to Fastly, they would say, anyway, it was terrible. This is much faster. We were also able to move the Bundler API back into rubyjams.org. It's no longer a standalone application and thanks to having people actively doing ops for rubyjams.org, we were able to, the new rubyjams can handle the 10x increase in traffic that comes from having this API and it's not a big deal, which is super cool. We finished the Compact Index which is a way of telling Bundler users about gems without having to do that like infinitely recursive round tripping API thing and it's a big improvement. You can just keep a copy of the list of gems on your computer and you don't have to get it again every time. For a bunch more detail about that, check out my talk, Extreme Makeover RubyJams Edition from RubyConf 2013. Bundle install, finally, sometimes, now fast. Couple other notable things that we did in this period of actually having people working on stuff. We made it so that you don't have to use the gem file if you are not a fan of file files. We also made it so that you don't have to use the gem file .lock if you are not a fan of files named .lock that can't be deleted. There is a ticket on GitHub from, I think, eight years ago now, right after Bundler 1.0 came out that says, .lock is the wrong extension for this file and we were like, we believe you, it's too late. To be clear, this change is optional even in Bundler 2.0, we're not getting rid of either the gem file or forcing you to use gems.rb, it's just an option that possibly makes life nicer for you. We also added Ruby version locking because after adding the Ruby version to the gem file, we discovered the problem was we didn't know things like which patch release you were locked to and we didn't have the ability to let you change that version between, I don't know, .1, 2.2, .1 to 2.2, .2, now you can and it's better. We added a bundle lock command which now lets you add platforms so if you want to install on an additional, like if you want this bundle to work both on, say, Unix machines and JRuby, you can now opt into that and install on both without a lock file that changes all the time. We added a doctor command from Misty Dimeo that tries to help you figure out why your bundle isn't working, especially if you have gems that are in a weird state or the native extensions haven't been built or something like that, pretty handy. We added a pristine command which lets you, it works just like gem pristine and lets you undo changes that you may have made locally to your installed gems in your bundle. We added options for bundle update that let you say no, only give me major, minor or patch level changes, which is pretty handy. We added mirror configuration which lets you say, oh, whenever I say gems from RubyGems, use this server that I keep locally that's a mirror that you can get to faster and we added checksum validation. So now, Butler knows what the checksums of each gem should be and checks them. That's what checksums are for. It's pretty good. Can you tell that once we started having funding, we started developing a little bit faster. There's another slide. We shipped a plugin system in beta that lets you build command plugins like new bundle commands in their own standalone gems. It lets you build life cycle plugins where you want to do something specific after say, I feel like a great example of this is you can generate C tags from gems that you, in your bundle, as you install them. And source plugins, so in theory, tomorrow, if you want to, you could build a gem that lets you install bundles from mercurial repositories. I don't know of anyone who wants to do that, but if you want to do that, could be you. So today, we just shipped Bundler 1.16 with everything that I just talked about. We're working on Bundler 2.0 with a target release date, very ideally, I'm crossing my fingers of Christmas. And I don't have room in this talk to include details about 2.0 because we're almost out of time. But Colby Swandale gave a talk at RubyKaigi maybe two months ago about just Bundler 2.0. So if that's super interesting to you, check it out. But the very short version is while we do plan on making breaking changes in Bundler 2.0, we think compatibility and stability are both super important, and we're going to make sure that it's possible to have applications on your computer that some use Bundler 1.0 and some use Bundler 2.0 and they will both keep working fine. We're not gonna like force everyone to mass upgrade to Bundler 2.0 because that would be terrible. I cannot even imagine dealing with the support headache that that would cause. But in the meantime, here are some cool things that get you most of the way towards 2.0 but are just config options that you can turn on today. If you turn on only update to newer versions, then the bundle update command will never update you to an older version of a gem. Seems like a good feature, but we can't turn it on by default until 2.0. You can disable a multi-source and then Bundler will stop you from having an insecure gem file and just raise an exception and say, actually, this is very bad and you're opening yourself up to security problems. Also will be on by default in 2.0. You can turn on specific platform which tells Bundler, oh, you should actually do your resolutions respecting the OS and CPU architecture and version that I am running on, which it didn't do before. This will also be on by default in 2.0. And you can turn on global gem cache, which is super cool because it means that Bundler only downloads gem files once and only compiles native extensions once and then shares them across all of the applications on your machine, which pairs very well with another option, default install uses path where every gem gets installed into its own place for the application, but that cache means that those gems don't get downloaded twice and Nokogiri, for example, doesn't get compiled twice. It's super great. These options will also be turned on by default in Bundler 2. Back to the HTTPS thing. Unfortunately, we shipped Bundler with a GitHub option that used HTTP, which isn't secure, but we can't change it because some people don't have open SSL support in their rubies, but you can change it. So, Bundle init creates a gem file that does this and you can overwrite the GitHub option to say, oh, only use HTTPS. We made it, that's it. Now that we're completely caught up on the history of Bundler, I have a few last second tips and then we're totally out of time. So, here's the chaser of workflows. Instead of Bundle execing, you can run bundle binstubs something and that will create a new binstub in bin whatever that you can commit to your repository and then any time you run it, you will only get the correct version of that gem for your application instead of having to be in the right directory, bundle exec, et cetera, et cetera. This is super handy for cron jobs and honestly, my muscle memory of just running bin something is much easier than having a shell alias for bundle exec. Bundle viz lets you see your dependencies in a graph. Wow, that's really hard to read, I'm sorry. You too could have a really hard to read graph of your application's dependencies. If you wanna start running your application on a new platform like JRuby or Windows, you can use that lock option add platform and you'll get a stable gem file dot lock that won't change as you install on both platforms. Pretty cool. The get local development thing that I mentioned earlier, you can check out a gem that your application depends on, start using the local copy, make changes, Bundle will update the lock file to the commit that you've made locally and then when you deploy, Bundle will make sure that you are still running the code that you expect to be running rather than whatever you had actually remembered to push. And if you've ever wanted to write a one file script but that needs some gems, Bundle inline is super good. Most people don't know this exists, but it's so good. You can write a script that requires Bundle inline and then you just put a gem file block at the top of your script and then you can start using the gem, it's amazing. Here's what it looks like when you run it. Even if you don't have the gem installed, Bundle says, oh, I will install that gem for you, now your code can run. This is super great for single file scripts. Kind of came up earlier, you can search your gems using the bundle show paths option which prints out all of the paths that your gems are in and then when you find the gem you want, you can open it directly by running bundle open and then you can edit it, finish your debugging and then you can run bundle pristine to undo the changes that you just made to that gem locally. It's a pretty good workflow. And last, in my personal favorite quality of life improvement, you can turn off post install messages and no one will ever tell you to HTT party hard again. I somehow put that slide in twice. I'm sorry, there's the punchline, the end.