 Ruby Gym, going from zero to sharing code. Oops, and that just went away. Great, off to a good start. All right, so I have roughly about 10 years of professional Ruby development. I've authored a few gyms, some of them to my horror. I found out are actually being used by actual businesses. Work for a company called Spring Buck in Indiana where we do healthcare analytics for insurance brokers covering about million lives worth of data, or at least that's what the marketing department told me to say. Today, we're going to talk about three primary points. What is a Ruby Gym? Where do the gyms live on your system? And how they're made and pushed up to rubygyms.org. A couple quick housekeeping notes. This is more geared towards junior developers. It's gonna be somewhat high level. Some hand waving will happen. Little bits of details will be dug into, but it's mostly a general idea of how gyms are constructed. We're gonna talk about pure Ruby gyms only, no C extensions, nothing about JRuby, just Plano Ruby gyms. And a quick note, whenever you see the dollar sign, that is in a terminal window and double arrows is pretty much IRB. The reason why I primarily wanted to do this talk is I've noticed something going on from the various teams I worked with in the past several years. Some of my supervisors see me work and they think I'm magical. My coworkers think I'm pretty much Neo, and because of those two points, I start to get a big head and I think I'm a superhero. But at the end of the day, this is all I'm really doing. Just running require like everybody else does. So let's start with what we know. We know how to install gyms. You can install a gym with gym install. We'll use awesome print as an example. Voodoo happens, gyms installed, hop into IRB, require the gym, and suddenly you have the gym loaded and you can do whatever you want with it. Alternatively, you can have a gym file, pop in a gym in the name of the gym you want, run bundle, the gym is also magically installed, run bundle exact against your app and awesome print is just there. So let's go back to the first example, gym install awesome print. Doesn't really tell you much of what exactly it's doing. Fortunately, gym install has a nice verbose flag on it. So let's just run this and see what happens. And oh, God, no, that's quite a bit of stuff. If you're in the back, I don't expect you to read this. And if you're in the back and you can read that, please tell me the name of your eye doctor because that'd be very impressive. So let's break this down a little bit. Here's the exact same command with a lot of the extra stuff kind of ripped out. I'm using the shortcut path to Ruby that points to the actual installation of my locally installed Ruby here just to keep the lines down. But if you look up at the very top, you'll see that we are doing a normal web git request to rubygyms.org. We're pulling down a file called awesome print.gym with a version number attached to it. And we start just listing files. The very top, you notice you have a license, a readme. And then you just see some regular Ruby files. And further down, you even start seeing some spec files. And then gym tells you it's done and you have the gym installed. So these are just Ruby files? Yeah, so hate to spoil it for everyone. There's not really much magic here. Gyms are just plain old Ruby files. They look like Ruby files, red like Ruby files that executed like Ruby files. And they're downloaded in what's called a gym file, which I'm sorry to say is just a zip file renamed. Specifically, it's a gzip file. So you're dealing with a zip file full of files and that's the entirety of the gym pretty much. And yes, sometimes they're seen there, sometimes a job in there. We're not gonna worry about that right now. So that's how gyms get there. And if you notice in the previous verbose command you saw where all these files have been plopped. In this particular case, I'm using RBM for my locally installed gyms and home RBM versions. And then the version of Ruby is where Ruby is installed. And further down, you'll see a lib directory, a Ruby directory, a gyms directory, the version of Ruby that's installed and then in actual, finally, a gyms directory. This is the standard install for pretty much any system Ruby out there. As far as where gyms live by default. And all of these listed are all the gyms installed on my local system. And as you can see that there are multiple versions of the same gym actually installed. Ruby will put the each gym as well as each individual version that you installed in its own separate directory organized with a dash in the actual version number. This is how Ruby can know which specific versions of gyms to actually load up. So we know how they're installed. We know where they live. How do you tell Ruby to actually load them up? Well, there are two ways to do this. Startup IRB, you can start the Ruby package, Ruby gyms package with the simple require. Then you require the gym that you want and you have it right there. In this case, I'm loading up the I18 in gym. And unless you tell it otherwise, you will always get the latest version installed on your system. In this case, this is one dot one dot zero. Thankfully, you do not need to require Ruby gyms anymore since one dot nine. This is mostly just for historical purposes. If you go online, you may see a couple of the older example scripts manually requiring Ruby gyms. That is no longer needed. Alternatively, if you need to actually load up a specific version of a gym, there is actually a gym method that comes with Ruby gyms. You may notice this looks like the exact same syntax as you would put in a gym file. That's because a gym file is actually a glorified Ruby file that's read as Ruby. You can specify a gym to use and the actual version to run against. This is what's called activating a gym. So if you ever run a bundle exact on a rate command and it's complaining about certain gyms are activated before anything else, this is bundler saying you told me to explicitly load up this gym, but something further up the chain already activated in other gym, please fix. Quick note, never actually run the gym method. That's what bundler is for, but it's just there if you ever actually wanna do a one-off script and you want a very specific version of the gym. So in this case, I'm explicitly loading up IEteenIn version zero dot nine dot three. So that's how gyms are loaded up. We went through how gyms are installed and where they live, but what exactly makes a gym? That's the whole point of this talk. A gym is best way I could describe it is four parts. A gym spec file, a tweak to the load path, the activation which is handled by Ruby gyms and bundler and somewhere along the line a well-placed require to actually start using the gym. The gym spec is the primary focus of the gym. It is what it describes the gym both to rubygyms.org and to the actual Ruby process. It has a name, description, a version number, lists its dependencies, what things need to be loaded up before this gym could be loaded up. Similar to Rails, Ruby gyms have a very strong convention. The gym spec file is typically named the same as your gym name dot gym spec, but really a gym spec file is just Ruby code. Nothing too interesting about it other than it issues certain methods in a certain way to describe the gym to the system. And here is a default gym spec that you can generate. This is pretty much all you need to get a gym set up and running as far as describing it. Let's start at the top and let's start with the anti-pattern. Great. So there needs to be a way to tell the gym spec to tell Ruby gyms what version of this gym is. You may notice working with various gyms over the years that you have a constant name to the gym colon colon and then a version constant within that and it'll spit back out a version. This is how other gym authors and developers can ask the gym what version are you on. So if I know I need to do something different between different versions, I can do so here. But as developers we don't wanna repeat ourselves so why should we mainly write out to the version and then write out the version inside some Ruby constant for developers to use. Let's use both at the same time. Well to do this we need to tell Ruby how to actually load the file that has the version number in it and to do that you have to tweak the load path. Dollar sign load path is a global in Ruby that lists out an array of every directory that the current running Ruby process knows about. So when you run require and then some file it will look through all those directories and try to find a matching file. First one in wins and if it can't find it that's where you get the I don't know what this file is type of error. So because Ruby doesn't fully know about the gym yet you have to mainly tweak the load path explicitly require the version file and then it's available for the rest of the parsing of the gym spec. This is boilerplate don't ever touch the load path you could screw some stuff up but it's there. Further down we invoke the gym specification object. This comes with Ruby the Ruby gyms package. This is the thing that actually describes what the gym is to the system. It is a series of methods that you call and configure. Bundler has a lot of documentation about what each one of these do. The top of the gym specification commonly has the name of the gym, who made it an array of authors and array of emails for the authors. Short and long descriptions actually will go to the rubygyms.org file as well as a license, a homepage which is commonly directly linked to the GitHub repo and just other basic meta information for humans to read. So the top of the gym spec is for humans to understand and the bottom of the gym spec describes the gym to the computers or specifically the Ruby process. The files method will instruct ruby gyms. These are all the files that actually make up this gym. You may have noticed in the awesome print example we're pulling down a readme file, all the spec files, a bunch of plain text files. Nowadays, when gyms are packaged up, they don't include that. So this pattern is used typically. It's assuming you're on a Unix-like system. It's gonna shell out to get, do a couple magical flags and just pull out all the normal RB files that the gym actually uses. So it ignores the readme and ignores the test files. Binder is commonly where executable scripts are go, so the rake command, the rails command, the bundler command for the bundle gym. These are then appended to, I believe, the operating system path. So when the gym is loaded up or the gym is activated, you can hop into a terminal and actually execute them and that's where those typically live. And then also a listing of the actual executable files with the executable method. Require paths, this is commonly never touched, but it lists out the directories that Ruby gyms will expose inside of the gym. Commonly, everything is thrown in lib. That's more or less convention nowadays. I believe it will even use lib if you don't specify any directories by default. And then finally, you actually have the dependencies. There are two types of dependencies in a gymspec. You have runtime and development. Runtime are gyms that have to be installed and activated before the gym is actually loaded up. So in this particular example, we need the Postgres gym, at least version 0.19, in order for the gym to actually install and work. If we're just working on the gym locally, we also have development dependencies. In this case, we want rake version 10.0, roughly around that. And I think I even, yeah, I had arrows. Oh, well. Yeah. It's pretty much straightforward. So you have the gymspec, you have load path, which is in lib, and then we have what's called an entry file and this is what actually makes a gym. A entry file is typically a Ruby file named the same thing as your gym, similar to your gym spec, inside of the directory that is required. In this case, we have a gymspec mygym.gymspec. We have a lib directory, which the gymspec says to require inside the lib directory, we immediately have mygym.rb. That is the file that is actually parsed when the gym is required in the system. Inside of lib, you will commonly have a bunch of extra subdirectories to hold support files. In this case, we have the default version file, which just has the constant version within a namespace that lists out the current version of the gym. And that is the file that is hacked into the gymspec to load up to not have to change the version number in two places. And that is pretty much a gym all in of itself. So let's try to make a gym real quick. And by try, I mean I have slides of pre-made code because we're not doing live coding for this. We're gonna make a gym called rb21, which is a very simple implantation of the game of 21, also known as blackjack. We're not going to implement too much of the game just mostly the foundation of it. So later on an actual game could be made possibly. We're gonna have a card that has a value in a suit. We want to have a deck which holds cards. It can shuffle the cards. It can deal out the cards. It keeps track of how many are discarded. And we're gonna have a hand that holds cards and the hand can then say, do I have a blackjack? Am I busted? What is the current value of the card? Stuff like that. We're not gonna worry about splitting, doubling, no betting, you can hit by drawing a card. No standing, it's just keep going until basically you lose, I suppose. Just to build out a very simple structure pretty much. For this example, I'm using Ruby 2.5.1 as well as the, at the time, the latest version bundler which is 1.16.5. I'm explicitly calling up bundler because we're gonna have bundler actually make a lot of the files for us. So back in the day, we had to know, all right, I need a gem spec file. It needs to be this tall. It needs all these different methods. I have to dig through five different pages of documentation for. I need to know to have the lib directory. I need to have my entry file. You don't need to worry about that anymore. Nowadays you can actually have bundler scaffold a blank gem for you. And as different versions of bundlers release some of these default files are tweaked slightly. One day I hope they get rid of the load path hack and the gem spec, but today is not that day. I believe 1.17 is out. So some of these screens may vary slightly but effectively they're all identical. There's not much variation. So in my handy terminal window, we're going to say bundle gem and then the name of the gem. First time you ever build a gem on your system it'll ask you a series of questions and save them for next time. First it'll ask you wanna do tests for your gem and the answer is always yes, you want tests for your gem. It'll ask if you want no gems which is we're not doing, no tests which we're not doing. Or you can use our spec in mini test pre bootstrap that up if you want. In this case we want to say our spec. It'll then ask hey, you want a license for this gem. The answer is usually yes and it will give you a copy of the MIT gem by default if you want and pre-populate to the license value in the gem spec. We'll just default to MIT for now. It'll ask if you want to code a conduct to post on GitHub. We'll say yes to that. And then it scrolls through a bunch of files. It then automatically initializes a Git repo for you. It won't point to anything so you have to actually say I want master in origin to be on GitHub or Bitbucket or GitLab or wherever it is you wanna actually host these files. And you're done pretty much. If we look inside of the directory that was just made for us we have a gem file. We have our entry file. We have our version file. We have a gem spec and a bunch of other stuff that we can use to help build the gem. As I mentioned it already initialized a Git repo for us so we have a gitignore file. We want our spec so we have a .rspec default file for us. Also it's going to throw us a Travis file so we can have CI just out of the box if we wanted to. We have a readme, we have some bin files and a rake file. We have a rake file, let's see what's all about that. We are given four rake tasks right off the bat that we can just use. We can build our gem file which again is just a gzip file. We can then take our gem file and install it locally so we can actually mess around with it with system Ruby. We can release the gem into the wild to rubygems.org and this will actually make a git tag for us, push that up to our git repo and push that up to rubygems.org so everyone can use it as well as your standard rake spec command which will run our specs or rake test if you're using Minitest. So we have that structure. Let's build the gem. So I sketched out, took about roughly an hour to do this. The various classes that are needed to implement 21 at the most basic level. Not going to go into the actual code because we don't care about that, we just want to build the gem. And this is also available on GitHub for people to see as well. But we're gonna implement a card, we're gonna implement a deck. We have our hand class on the far right. And then the two default files that Bundler gave us which is the version file as well as the entry file. The entry file by default only has required the version file as well as an empty namespace. And if you really, really wanted to, you can get rid of the version file, you can mainly put in the version in the gem spec and have your entire gem just be one giant file if you really wanted to. Some gems actually implement like that. Otherwise, because you have the lib directory already loaded in your load paths, you can then start requiring everything in subdirectories to actually bootstrap the gem. This is where you all also want to mainly require any of your dependency gems as well. So if you need Postgres for whatever reason, you wanna require PG right here. The gem spec guarantees that Postgres is already loaded up and ready to go for you. So you just have to load it. And we wrote our code, we wrote some tests. Just pretend this is green text. Tests all pass, trust me, these are not red lines. So we wrote code, we wrote tests. So what else can we do with this? Well, we can also just mess around with the gem ourselves. Bundler makes a bin directory for us. One of the files in there is called console. This is a standard Unix executable file. And when you load it up, it will automatically bootstrap up your entire gem for you inside of a IRB terminal. This allows you to just mess around with your classes. You don't need to write certain, you don't need to write one-off specs just to see if a certain method works. You can just, it's your playground pretty much. Because it's also IRB, you can mainly require in other gems if you want or just pretty much do whatever you want. And I wish, I wish all gems had this. I think this was added roughly few years ago by Bundler, I think. I think Ernie Miller went on a small blog rant about every gem should have this and someone just copied it and now it's part of Bundler, which is great. The other file in the bin directory is a setup file which by default will run Bundler and say you're done. This is for developers where you pull down the gem from the Git repo if you wanna develop it or contribute bug fixes. You can issue bin slash setup and it'll make sure all of the development dependencies and all the runtime dependencies are pre-installed and the gem author can issue more commands in the setup script to set up certain things if you need to, like maybe set up a default MySQL database or what have you. So we wrote the gem, we wrote the specs, we messed around with it, we feel pretty confident with this. Let's release this to the outside world. Rake release, fantastic, all right, we're gonna run this. Bundler will build our gem file in a package directory which is automatically get ignored by the default get ignore file. It'll make a Git tag for the version that we are publishing our gem, so in this case 0.1.0 and it does a one final git push up to origin as long as along with all the tags so everything is on your Git repo and you're ready to go and well it blew up. So the first time you actually try to release a gem it's gonna say, yo, RubyGems needs to know who you are. So you can go to rubygems.org, you can set up a very simple account, just email and password and this allows you to have access to all of the gems that you've published on RubyGems to help keep track of them and also basically flag who actually pushed the stuff up. You can set up multiple contributors to a gem so if I'm working on a gem and another developer's working on the same gem and I give push access to it, he can commit the gem under his account and it'll all go to the same bucket. So we need to actually tell rubygems.org who we are so it says run gem push to set our credentials. Let's assume we already made our account. So let's run gem push. It asks for our email and password, it stores it in an encrypted manner locally so you don't need to set this every time and that also aired out. So this is kind of an awkward way to push gems for the first time. The previous command told us to run gem push run gem push to set our rubygems.org credentials. And when we run gem push, it's like thanks for the credentials but what gem are you pushing? This is because bundler and rubygems are still separate projects I believe. In 2.6 I believe they're trying again to merge bundler and rubygems as default stuff baked into Ruby so hopefully this doesn't become as awkward but this is the command that you would normally push to use back in the day to push gems to rubygems.org and this is like the only way to trigger hey give me your credentials so we can stash this so maybe this will be better in the future. But anyway we set our credentials, let's try one more time, rake release. It rebuilds the same gem file for us. It complains that we already have a tag made and pushed up so it just kind of skips over it but it does actually say that we pushed rb21 version 0.1.0 to rubygems.org and when we go to the internet this is what you'll actually see. So this is an actual gem that was built, it's available for everybody. rubygems.org will extract out the gem spec file for us read through the human readable bits of information as well as the dependencies and this is where the title comes from, where the version number comes from, the description that we put in, all the known release versions as well as the dependencies, both development and runtime. If this gem had any runtime dependencies it would be listed next to that. Further on we could specify a required ruby version in the gem spec that's not set by default. If you notice on the far right side required ruby version is greater than zero. You can actually say in the gem spec this is only workable on 2.5 and later for example. And that's more or less it. I mean a gem is just ruby code just put in very specific ways. Probably the most complex part of it is the gem spec which outlines where the gem, you know, where the required path should be, the name of the gem and any of the dependencies that have to be loaded up first. Bundler and rubygems nowadays pretty much handles that all for us. Way, way back in the 1.8 days gyms weren't even a thing. So just going from 1.8 to today with the leaps that we've done is kind of incredible. Especially because with Bundler you don't even need to remember how the scaffolding is set up. You don't need to know how to bootstrap our spec in a non-rails like environment if you ever just use our spec inside of Rails for example, it's just kind of all there for you. And that's more or less it about the talk. A copy of these slides are the top Git repo. If you want to mess around with the gem, the whole purpose of me making RB21 is for if junior devs want to actually try to mess with the gem and try to, you know, contribute to a gem, this is nice, safe. No one's going to really use this gem so if something messes up it's not the end of the world type of project. So anyway, it's up there at that Git repo and now that you know that you can pretty much pretend that you're a magician to all your coworkers at this point. Any questions? All right, thank you.