Ever wonder how to make your very own Ruby gem? This presentation from RubyConf 2021 will show you every single step involved in creating and releasing a brand new gem—all in the first 8 minutes! The other 22 minutes will distill a decade of hard-fought lessons about how not to make a gem to help you ensure your next gem is a great one. 💎
This talk covers a lot of ground, so here are some links to items it references:
- The bundle gem command
- Using GitHub Actions for Ruby CI
- The MIT License
- How to maintain a changelog
- RuboCop
- Standard Ruby
- The Bored API
- The bored gem we created together
- My Japanese-learning app KameSame and the dictionary import gem eiwa
- Jasmine and the (now defunct) jasmine-rails gem
- Cypress and the (not yet defunct) cypress-rails gem
- Mockito for Java and Gimme for Ruby
- Jim Weirich
- Our brand-new Mocktail gem
- Test Double (you’re here already), but specifically our careers and services pages
Footage and audio were provided care of Confreaks, who do an excellent job helping small and medium-size event organizers stream and produce production-quality video.
If you enjoy the video, please share it with your friends and colleagues! And if you know a team that could use additional developers and is looking to improve, we’d love to hear from you. (If you have any other hot takes about this topic that you’d like to share, email me.)
[00:00] (upbeat music)
[00:03] - So the title of this presentation
[00:04] is "How to Make a Gem of a Gem."
[00:07] My name is Justin, you may know it.
[00:09] That's my email address,
[00:10] and you can find me on the internet
[00:12] by my last name, Searls.
[00:14] I'm, Twitter and GitHub and LinkedIn and RubyGems.
[00:17] I have so far, written 39 RubyGems over the years,
[00:20] and my goal today is just to share
[00:22] some lessons that I've learned over time.
[00:24] And in fact I've already lied to you.
[00:25] I've actually written 40 RubyGems,
[00:26] but one of the ones I'm gonna talk about today,
[00:28] I haven't announced yet, so stay tuned.
[00:30] And, I've lied again, because we've written 41 RubyGems,
[00:32] 'cause one of them,
[00:33] we're gonna do on stage together right now,
[00:34] and we're gonna release it live.
[00:37] So that's right.
[00:37] Today, you become a RubyGem maintainer.
[00:40] If you're here in this room or if you're watching on video,
[00:42] 'cause I just made that up.
[00:43] And the goal of this presentation today
[00:45] is we're gonna answer two questions.
[00:46] One, the first talk is really like,
[00:48] "how do I make a gem?"
[00:49] And, the second question is like,
[00:50] "How do I make it good?"
[00:51] And we're gonna talk about both those things.
[00:53] And it's just not as hard as it might look
[00:55] if you've never written a RubyGem before.
[00:57] So part one, how do I make a gem?
[01:00] I think this mic got turned on.
[01:03] So the right way to make a gem is using Bundler, here.
[01:07] Bundler comes with a great command called Bundle Gem,
[01:10] and you can just type in the gem name
[01:11] that you have, after the fact.
[01:13] And, here I am creating a gem called "bored".
[01:17] And so, it's kind of like an interactive fiction.
[01:19] It kind of sets up like a text adventure
[01:21] saying, "hey, do you wanna generate tests,"
[01:22] and you can pick out your test framework.
[01:24] So I'm here using minitest.
[01:27] "Do you wanna have a continuous integration
[01:28] "set up for your gem?"
[01:29] And so, you can choose between all these different options,
[01:31] and I like to use GitHub actions lately.
[01:34] It's very fast,
[01:35] it's all your code is in one place,
[01:38] it's easy to use.
[01:39] And then, it asks,
[01:40] "do you wanna license it as open source using MIT?"
[01:43] Most gems are licensed MIT,
[01:45] so, yeah, let's do that.
[01:47] "Do you wanna have a code of conduct
[01:49] "in the gems that you generate?"
[01:52] Here's what a code of conduct is and how to enforce it.
[01:54] "Yes or no?"
[01:55] Sure, let's have it.
[01:55] And, "Do you want a changelog?"
[01:56] This is a text file that tells you each version,
[01:59] what changed in your gem.
[02:01] You can read about it at Olivier's awesome site,
[02:03] keepachangelog.com.
[02:05] "Yes or no?"
[02:05] Yeah, I wanna have a changelog.
[02:07] And then, "Do you wanna add a code linter and formatter?"
[02:09] So your options here are RuboCop and Standard,
[02:11] and I actually,
[02:13] this is the first thing I ever contributed to Bundler,
[02:15] it was this feature.
[02:16] I was excited to do it,
[02:17] 'cause we maintain standard at Test Double,
[02:18] so I'm gonna use Standard.
[02:19] And then it initializes a Git repo here.
[02:21] And then, the next thing I do is I just run,
[02:23] git commit, Bundle, gem bored.
[02:25] Whenever I generate a new file, a new project, excuse me,
[02:29] I like to commit it right away
[02:30] so that way anything that I change
[02:32] I know is something I did.
[02:33] So let's go ahead and commit it.
[02:35] All right, do we did it.
[02:36] We created the new gem.
[02:38] So let's run, bundle install.
[02:39] And there's these warnings,
[02:40] now an error saying that the gem spec is invalid
[02:43] because there's all these to-dos in there.
[02:45] So we don't actually have a working gem yet.
[02:47] So let's pop into the gem spec.
[02:49] And when we do, we can zoom in
[02:50] and you can see there's all these sort of like to-do items.
[02:53] We just have to work through each of those.
[02:54] So we're gonna write a description.
[02:56] We don't need that second description.
[02:57] We're gonna put it in the home pages,
[02:58] just the Github page.
[03:00] We don't need that thing.
[03:01] We can set that and we can set the source code URL,
[03:04] and then the change link URL
[03:05] and now we've gotten all these to do's done
[03:06] so we can commit that we fixed the gemspec
[03:08] and there we go.
[03:09] We can run, bundle, install again,
[03:10] and that's now gonna bundle and install everything cleanly.
[03:13] So that's great.
[03:14] So now we can run rake
[03:15] and that's gonna run our tests and then lint,
[03:17] and you can see that the tests failed
[03:18] and here's the default test, test_bored.
[03:20] We're gonna pop into that file here.
[03:21] And you can see it's got the second test
[03:23] and just like test to make sure that it's doing something.
[03:25] We'll just throw that away.
[03:26] 'Cause I just wanna get to a passing build
[03:28] as fast as possible.
[03:29] Great, so now we're passing.
[03:30] A great way to make your build pass is to delete the tests.
[03:35] So we did it, we made a gem that does nothing.
[03:37] Congratulations.
[03:38] What this gem is gonna do
[03:39] is it's gonna call this free API called the bored API.
[03:43] So we're gonna write a quick test.
[03:44] So test that it just returns an activity.
[03:46] The public API is gonna be Bored.now.
[03:49] And we're just gonna start
[03:50] that this gives us some kind of object
[03:52] that kind of like has all these same properties,
[03:54] like price and description and that stuff on it.
[03:57] Run rake again,
[03:58] and now we're gonna see that that fails
[03:59] because there's no Bored.now method on that module.
[04:02] And this is not a test driven development talk.
[04:04] So I'm not gonna ping pong back and forth
[04:06] and show every single step.
[04:07] We're just gonna dive in and like write all this code.
[04:09] This is a generated file that comes with the gem.
[04:11] It says your code goes here.
[04:12] So let's just follow instructions
[04:14] and wipe away that comment.
[04:15] And then we're gonna write a module method called now,
[04:17] and we're gonna use net HTTP,
[04:19] passing the domain and a path.
[04:21] And then we're gonna wrap that
[04:22] in a call to JSON.parse.
[04:23] And so we're gonna take that string
[04:25] and turn it into some a JSON e-hash.
[04:27] And then we're gonna create a new custom object
[04:28] called an activity.
[04:29] And we're gonna translate that JSON
[04:31] into like a normal, Ruby-ish object.
[04:33] Now activity needs to exist too.
[04:35] So we're gonna define that class by using Struct.
[04:37] And then we're gonna create
[04:38] all these different property types in here.
[04:40] Turn on keyword initialization.
[04:41] 'Cause it makes it a little bit easier to use.
[04:43] And the last but not least,
[04:44] we got to go over require those two things that we use.
[04:46] So we got to require net HTTP and JSON.
[04:48] And those are both part of the standard lib.
[04:50] So we don't actually have to require
[04:51] any additional gems or anything.
[04:53] So we zoom way out.
[04:54] And this is like our whole first gem implementation.
[04:56] Hopefully it passes the test.
[04:58] Okay, cool.
[04:59] And so now we're passing and now we're ready to go.
[05:00] We can ship it.
[05:01] So we're gonna go ahead and just update the version.
[05:03] So by default, it gives you a 0.1.0,
[05:07] but I'm a big fan of lowering expectations.
[05:09] So I always start at 0.0.1.
[05:11] They I run bundle,
[05:12] which is gonna update the Gemfile.lock.
[05:14] And so that won't cause a need to git churn.
[05:15] Then I changed the change log and I say, okay,
[05:17] so update the version here and say,
[05:21] what is in this version?
[05:22] Just this one method.
[05:23] All right, so I can commit version 0.0.1.
[05:26] And then I run rake-T.
[05:28] Rake-T is a cool command
[05:29] that gives you all of the rake tasks that are defined.
[05:32] So I'm gonna run them here and see, oh,
[05:33] I've got this cool rake release.
[05:34] It's kind of like, does everything.
[05:35] It makes sure you're all bundled.
[05:37] It generates the gem, it packages it up.
[05:40] It pushes all the, like the git commit
[05:41] and creates a git tag.
[05:43] And then it finally releases it to RubyGems all in one go.
[05:45] Real, real, simple, easy to use.
[05:47] So we're gonna run rake release here.
[05:48] And then it's asking for my MFA code for Ruby gems
[05:50] because I've got 2FA set up.
[05:51] So here's the code and then boom, push the Ruby gems.
[05:53] So we did it.
[05:54] So this gem is really up on Ruby gems.
[05:57] That means you're a gem author now.
[05:58] And so you're also responsible
[05:59] for the maintenance of this gem.
[06:01] And whenever I put up a new gem,
[06:03] I always like look up at the GitHub site
[06:04] and I just revel in my work
[06:07] and I zoom in and I wait, I wait for it and then boom.
[06:10] It happened, our first issue.
[06:11] (audience laughing)
[06:12] So somebody's got a complaint and it's my partner Todd.
[06:15] And he's like, this doesn't make any sense as an API.
[06:17] It makes a lot more sense as a command line interface.
[06:19] Like maybe we should have like a message of the day pop up
[06:22] and tell you what to do
[06:23] instead of being on your computer.
[06:24] And so how do you write a computer,
[06:26] a command line interface?
[06:27] Actually like the bundler gem tools
[06:29] and the right tasks make this pretty easy.
[06:31] There's just some additional kind of plumbing
[06:33] and it looks arcane.
[06:34] So let's walk through it.
[06:35] So we're gonna, pop back into the gem spec again.
[06:37] And if you look here,
[06:38] this spec.files thing
[06:40] is essentially just you shelling out to Git,
[06:43] to look for all of the files that are tracked by Git.
[06:45] And then there's...
[06:46] We specify where our binaries live in the exe folder.
[06:50] And then we go and grab all those executables,
[06:52] those things tracked by Git.
[06:53] Again, we're just focused on the plumbing here
[06:55] and we can see that that exe directory doesn't exist yet.
[06:57] So we're gonna make it, we're gonna touch a file.
[06:59] We're gonna make it executable,
[07:00] and then we're gonna go
[07:01] and start writing this little script.
[07:03] So I need a shebang here to say like,
[07:04] hey, I'm written a Ruby code.
[07:06] The load path stuff is normally handled for us by RubyGems.
[07:09] But because we're inside the gem,
[07:10] we actually have to manually load that live directory,
[07:13] which will allow us to require that bored file.
[07:15] And then this doesn't exist yet.
[07:17] But I know I'm not some sort of like CLI object.
[07:19] I'm gonna pass it, RVU, the argument vector.
[07:22] And that's all of the kind of command line parameters
[07:24] that people might type and then call run.
[07:26] None of this exists,
[07:27] but I just wanna kind of get this done on paper
[07:29] and then I'm gonna actually commit it now,
[07:30] even though it's like a work in progress,
[07:32] because like I wanna actually see
[07:33] that when I run a rake install,
[07:35] which is gonna package up the gem
[07:36] and then install it locally,
[07:37] that I should be able to run the bored command.
[07:39] So when I run bored,
[07:41] I am elated to see this error message
[07:43] because it shows that it's actually successfully
[07:45] running my script.
[07:46] And so I've got all the plumbing stuff worked out
[07:48] and the next step is to actually make it work.
[07:50] So the first thing we got to do
[07:51] is got to go back into our lib bored,
[07:53] which is kind of the entry point for our gem.
[07:55] We're just gonna require relative bored CLI thing.
[07:59] Again, this file doesn't exist.
[08:00] So we got to go make it.
[08:01] And then we're gonna go create the CLI class.
[08:03] And as an initializer here,
[08:05] we're gonna pass it, that arg vector and put it on an ivar
[08:09] and then it turned out we don't really need it.
[08:10] So it's kind of future-proofing
[08:11] because all we're gonna do
[08:12] in our initial implementation here
[08:14] is just call it bored.now and it puts the description
[08:16] to the command line.
[08:18] All right, so now we can run it locally.
[08:19] You don't have to install it to actually test it.
[08:20] We're gonna run it here and it says,
[08:22] learn woodworking, alright cool.
[08:23] So step three- (audience laughing)
[08:26] Is coming, new release, got to stay on task.
[08:29] So we're gonna update the version number now from 0.0.1
[08:31] to 0.0.2 and then we're gonna run bundle again.
[08:33] That'll update our gem file.lock,
[08:35] and then we've got a change log here.
[08:37] So we got to add, yeah, there we go.
[08:39] Create a new section and then re-added the bored CLI
[08:41] and then I like to do all this sort of like,
[08:43] versioning ceremony in one nice commit.
[08:45] So they're all tied together and the stage is clear
[08:47] so I can run rake, release, give it my MFA code.
[08:51] And now I've pushed up 0.0.2 up to Ruby Gems.
[08:53] So this is awesome.
[08:55] If you go on your computer later today,
[08:57] you can gem install bored and our gem is there
[08:59] and you can run bored and it'll give you something to do.
[09:03] So we did it!
[09:04] That's all, you have seen everything.
[09:05] I ally-ed no detail.
[09:07] You made a gem, yay! (audience applauding)
[09:10] All right, so let's talk about gems.
[09:13] In addition to this,
[09:14] if you're interested in playing around with this
[09:16] and kind of dipping your toe in the water,
[09:17] I created a whole bunch of issues here,
[09:18] like filters on the API
[09:20] or options on the command line interface.
[09:22] And you can read them here.
[09:23] And if you're interested,
[09:24] just open up a pull request and give it a try,
[09:26] and we're gonna grade on a curve.
[09:29] Like this isn't about excellence.
[09:30] This is about just like getting some reps in
[09:32] and learning how to contribute to a gem.
[09:34] So if you wanna take a stab at that, do it,
[09:36] and I'd love to help you out and we can collaborate.
[09:40] Okay, now for the second part of the talk,
[09:41] like how do we make gems good?
[09:44] If you've ever bought a diamond,
[09:45] you might've heard of a thing
[09:47] called the four C's of gemstone quality.
[09:48] So we're gonna talk about each of those today
[09:50] with our RubyGems.
[09:52] Their clarity, color, carat, and cut.
[09:54] And so we're gonna start by talking about clarity.
[09:56] And this is a story for people who find it hard to focus
[09:59] in a crowded code base like myself.
[10:01] I've got this rails application called KameSame.
[10:03] It's a Japanese learning tool.
[10:05] I can search for words in it like hap-yo
[10:07] which means presentation, what we're doing right now.
[10:09] And I can read about that,
[10:10] or I can go and actually like add it to my reviews
[10:13] and do little quizzes and learn the word.
[10:15] But if I search for a word like Ruby,
[10:17] it's actually not there
[10:18] because initially when I built this thing
[10:20] on a very small dictionary,
[10:21] just 2000 characters and 8,000 words,
[10:23] and I wanted to make that bigger.
[10:25] So I looked online and there's this KANJIDIC
[10:27] and this Jmdict, these are open source,
[10:28] Japanese-English dictionaries.
[10:30] The thing is that they're in XML.
[10:32] Which means I wanted to build something in Ruby
[10:34] that would take that XML, run it through some Ruby
[10:35] and then it'd give me Ruby objects
[10:36] that I could insert into my database.
[10:38] And what it would allow me to do,
[10:40] is go from 2000 characters to 10,000
[10:42] and from 8,000 words to 200,000.
[10:43] And it seemed like a pretty big win.
[10:46] Now I'm operating in the context of a monolith
[10:48] and I love rails and I love working in a monolith
[10:49] because everything's very at hand
[10:51] and I can kind of keep it all in my head at once.
[10:52] But it has the downside too.
[10:53] If we visualize the monolith,
[10:55] (audience laughing)
[10:57] I've got all the user management stuff and the quiz module,
[11:00] and like keeping track of people's progress,
[11:02] I've got the whole like react JavaScript front end,
[11:05] I've got the search capability.
[11:06] And like I have to mentally kind of carve out space
[11:09] for this new dictionary import to come in,
[11:12] and it's feels constraining.
[11:13] And I have a bad habit of making kind of short-term,
[11:15] naive design decisions when I feel kind of claustrophobic.
[11:19] So I just pulled in Nokogiri, make a new class.
[11:21] And I take a path, and I open up that XML document,
[11:23] all into memory all at once.
[11:25] And then I use X path and I grab the entries
[11:27] and I loop over them
[11:28] and I kind of like cherry pick
[11:29] the parts of each word that I want
[11:31] and then upsert those all into my item table.
[11:33] And I take a stab at this and I run it here.
[11:36] And when I ran it, it took over 12 minutes to run
[11:38] because I was literally paging out.
[11:40] It was just way too much memory.
[11:41] And I knew what the problem was, but like I'm thinking,
[11:44] and I'm like, I'm overwhelmed
[11:45] by all the complexity around me
[11:46] and I just couldn't really get in the right head space.
[11:48] And so sometimes, it's nice to just blow it all away
[11:52] and start in a brand new and start a brand new thing.
[11:54] So I created a new gem called Ei Wa,
[11:56] Ei, wa, stands for English, Japanese.
[12:00] So it was kind of dictionary adjacent meeting.
[12:02] And so what it allowed me to do mentally
[12:04] is push all that other stuff aside
[12:06] and just focus on the dictionary stuff.
[12:08] And this allowed me to think like, hey, actually,
[12:09] what makes the most sense here
[12:11] is to do a streaming Sachs XML parser,
[12:13] which you can do with Nokogiri,
[12:14] but not very many people had done before.
[12:16] And it also gave me plenty of time
[12:18] to get the entity mapping right,
[12:20] and create a good object model for this to be useful,
[12:22] not just for me, but for others.
[12:23] And then a streaming public API that was like,
[12:27] gonna be garbage collection friendly.
[12:28] So it didn't chew up so much memory.
[12:31] So I blew away my original Nokogiri implementation
[12:34] and imported eiwa.
[12:35] And it looks pretty similar.
[12:36] You still say, I'm parsing this file.
[12:38] And I have these entries I'm just shoveling on these hashes.
[12:41] And then finally, upserting the items again.
[12:44] And when I run it now, it was way faster.
[12:45] It was up to nine seconds, right?
[12:47] So it was a huge gain.
[12:49] And now when I go in and use KameSame,
[12:50] I can search for things like Ruby
[12:52] and get a whole bunch of results back.
[12:53] And so, I think an underappreciated benefit
[12:58] of breaking out and starting a new thing.
[13:00] Pure function, none of like the incidental dependencies
[13:02] onto your big monolithic app.
[13:04] Just go and start a new folder or a new gem
[13:07] and dedicate some space for some mental clarity.
[13:10] The next thing I wanna talk about
[13:11] is the color of our gems.
[13:13] And this is a story for anyone who makes rash decisions,
[13:16] when emotions are running high, like me,
[13:19] and I think of it like sort of like a pain chart
[13:21] from here to here, where like at one end of the spectrum,
[13:24] you're not mad at the other end of the spectrum,
[13:25] you're real mad.
[13:26] But where I do my best work is like right in the middle.
[13:29] (audience laughing)
[13:29] In care mad mode.
[13:31] And I've got a long relationship with RuboCop.
[13:34] It's a linter and formatter for Ruby.
[13:37] And honestly, I didn't really use it for years
[13:39] 'cause I don't care about linters and formatters.
[13:41] And I just don't care about programming language stuff
[13:43] or abstract syntax trees or parsing.
[13:45] It just like, everyone's got a thing.
[13:47] It's just not my thing.
[13:48] But I did see a lot of teams over the years
[13:51] have pointless debates about like,
[13:53] we should have this rule or that rule.
[13:54] And then like passive-aggressively changing the rules
[13:57] and unrelated pull requests back and forth forever.
[13:59] And like, it's just not a great way to live.
[14:01] And then I saw some teams even,
[14:03] like, where they'd be a tech lead
[14:05] or somebody would sort of like,
[14:06] use it as an authoritarian means of control
[14:08] to like control how other people did their work.
[14:10] Like whether it's like really strict metrics or whatever.
[14:12] And that really, I hate to see it.
[14:15] So I got mad enough at Rubocop
[14:17] that what I wanted Rubocop to be
[14:19] was like an unconfigured tool, like just take,
[14:22] take all those arguments and get them out of there
[14:24] and just have everyone be able to focus on the work,
[14:27] what really matters.
[14:29] And so I very brashly decided
[14:32] like the solution to this was like I was gonna fork Rubocop.
[14:35] So I literally made a fork, I called it RubbyCop.
[14:38] In hindsight, it's weird that I changed
[14:39] the rubo part of that name,
[14:42] but I, I went through, I moved it.
[14:44] I did a lot of find and replace
[14:45] and I made a huge, big git diff.
[14:47] I was furiously typing 'cause I was mad at Rubocop.
[14:50] And so like, tempers cooled and now I'm in care, mad mode.
[14:54] This is where I like to be, right?
[14:55] So I'm like starting to work
[14:56] on this unconfigurable configuration
[14:58] and just picking out all of what I think are the best rules.
[15:00] But like before I'd even gotten halfway through it, boom,
[15:03] there's like a major Rubocop release
[15:05] and I look away again and there's another one
[15:07] and another one and another one.
[15:08] Turns out a lot of people, unlike me,
[15:10] really like linters and formatters and contributing to them.
[15:12] And it's a super, actively maintained,
[15:14] pretty sophisticated project.
[15:15] And I realized I was always gonna be outpaced.
[15:18] So since I made RubbyCop,
[15:19] if you look through like these,
[15:22] all of these Robocop releases
[15:23] that I would theoretically have to pull from upstream
[15:25] and then do all this renaming and decisions,
[15:28] and there's 106 releases.
[15:29] There was no way I was ever gonna keep up.
[15:31] And so instead of getting to focus
[15:33] on the thing I wanted to do,
[15:34] which was like eliminate stupid arguments.
[15:36] I was over here in this place where like, oh,
[15:39] I own a linter now, this is great.
[15:40] Like, so, I don't wanna spend my life maintaining a linter.
[15:43] So I just quit and I stopped.
[15:44] And that's why you haven't heard of RubbyCop.
[15:46] So it's there, but don't use it.
[15:50] I like to design things,
[15:53] evergreen problems and things to work on
[15:55] that I just find mildly irritating or antagonistic
[15:58] because it keeps me motivated, keeps me focused
[16:01] and it's just my happy place productively.
[16:04] And so if I think about like,
[16:06] how do I maximize the time that I spent in care mad mode,
[16:09] it was again, just like let's limit these pointless debates
[16:11] and let the architecture follow them.
[16:13] So I created a new gem called Standard.
[16:15] And what standard does is it truly depends on Rubocop.
[16:18] It uses Rubocop.
[16:19] But, it doesn't use its CLI.
[16:22] Instead it creates its own CLI
[16:24] that just happens to not be configurable.
[16:26] And then that way like,
[16:28] you eliminate all of the kind of pointless debates
[16:30] and you prevent even the real authoritarian
[16:33] and controlling stuff from even happening.
[16:35] And if you don't like a particular style,
[16:37] instead of changing the configuration,
[16:38] you just go to standards repo,
[16:39] and you create a new issue
[16:41] and then we can have it out and talk about it together
[16:42] and decide whether or not to change it for everybody.
[16:44] Because the idea is instead of your team having a bike shed
[16:47] and then all of these other teams having their own bike shed
[16:48] and everyone having the same,
[16:50] nothing fights over and over again separately.
[16:52] And then you're having to learn every other teams.
[16:54] Like, Standard's goal is to be
[16:56] one big gigantic community bike shed.
[16:57] And we can just have it all out in one place
[16:59] and we can have kind of more portability between projects.
[17:01] And so if you opt into the Standard lifestyle,
[17:03] as I encourage you to do,
[17:05] life is just a lot easier and better and fun.
[17:08] But I have also, I'm a Rubocop customer now.
[17:12] Instead of getting mad at it,
[17:13] like I've really come to respect
[17:14] the amount of work that Bozidar and Koichi
[17:16] and all these people do.
[17:17] It's a super sophisticated project.
[17:18] And I like really, I have an appreciation for him now.
[17:21] So all that to say like,
[17:22] don't let your priors, color your thinking,
[17:25] like your biases.
[17:26] Like you might wanna fork a thing
[17:27] or build a competitor to a thing,
[17:29] but like, there might be a way to be more interoperable
[17:31] or even contribute back to the thing that you're mad about.
[17:35] All right, so the next step let's talk about carrot
[17:37] or the size of the weight of our gems.
[17:39] This is a story for people
[17:39] who find it hard to say no, like me.
[17:42] So back in 2011, I was like getting mad at my computer.
[17:44] I was like, why it's so hard to just JavaScript and rails.
[17:47] And me and Corey Flanagan, we sat in a coffee shop.
[17:49] We started this gem called Jasmine-rails.
[17:51] And it's like, what I call like a glue gem.
[17:54] So it's just like on a connects these two things together.
[17:56] What Jasmine did, if you're not familiar,
[17:58] it's a spec like syntax for writing tests.
[18:01] So you can write a test like this
[18:02] and it had a little HTML runner, it looks like that.
[18:04] And so Jasmine was just providing
[18:07] just that test DSL and JavaScript,
[18:08] and just a little tiny reporter API,
[18:10] tell you how many tests were passing and failing.
[18:12] And then the rail side, what we were relying on here
[18:14] was rails 3.1 had just released,
[18:17] which was the first time,
[18:18] thanks to the asset pipeline and Sprockets
[18:20] that you could have like an engine
[18:22] include a bunch of like JavaScript
[18:23] and CSS assets and stuff.
[18:25] And so we were like, this is great.
[18:26] This is gonna enable us to build this gem.
[18:29] Now, if you dislike the asset pipeline
[18:32] and Sprockets now,
[18:33] you should have tried it written 3.1 came out.
[18:36] Lots of performance regressions,
[18:37] it came in really hot, lots of breaking changes.
[18:39] It was a very challenging time.
[18:42] So this thing depended on Sprockets from rails
[18:45] and rake and ERB.
[18:46] And then like the work in the middle
[18:48] was the glue code, right?
[18:49] 'Cause the glue gem.
[18:50] And so what we did here
[18:51] was we had to have a custom rails engine
[18:53] that could host that runner.
[18:54] We had a Phantom JS installer,
[18:56] so you could run it from the command line
[18:57] in a fake environment.
[18:59] And then a whole CLI,
[19:00] because Jasmine didn't have a CLI
[19:01] for like formatting all these results appropriately
[19:04] for your CI system.
[19:06] And it just did too many jobs.
[19:08] All of them poorly,
[19:08] it was pretty slapdash.
[19:10] And whenever that happens,
[19:11] because like, it just has a lot of responsibilities,
[19:13] you're gonna get a lot of pull requests.
[19:15] So we got a lot of pull requests
[19:16] and I kind of just like merged them all
[19:18] 'cause I assumed like surely other people will help me
[19:20] maintain this if they contributed features.
[19:21] And that turned out to be a false assumption.
[19:23] And also I just didn't have
[19:24] a lot of good tests around everything.
[19:26] And so I didn't realize that I was kind of just like
[19:28] bringing it, hoovering in like a lot of complexity,
[19:31] but I just like said, yeah, let's pull it in.
[19:33] Like make it the best we can do.
[19:34] I've never had a really popular gem before.
[19:37] But what I learned is you gotta be really careful.
[19:39] There's configuration where you're like,
[19:41] change where a path comes from
[19:42] and then there's configuration,
[19:43] like it changes the mode that you're operating in,
[19:45] like turn on and off features.
[19:47] And so there's like a lot of different optional features
[19:49] in this gem now.
[19:50] And maybe in my app, I only use one of them.
[19:52] And maybe in the thing that runs on CI,
[19:54] it actually uses none of them.
[19:56] But like if somebody is angry about a particular issue
[19:58] and they open up like a new bug request,
[20:00] like maybe they got these three flags turned on
[20:02] and they're interacting in some way
[20:04] that I didn't anticipate and I didn't test for,
[20:06] because like it's a competent, horrible math problem.
[20:08] It's like if you've got seven optional modes
[20:09] and they can all interact with each other,
[20:10] that's two to the seventh power
[20:12] or like 128 different configurations of your gem,
[20:15] you got to test all of those if you want it to work.
[20:18] And so anytime we got one more pull request, marginally,
[20:21] it might look like, oh,
[20:22] this is just one more little true false flag.
[20:24] But in fact, like if it's the eighth thing,
[20:27] now we're two to the eighth
[20:27] and now we have 256 modes.
[20:29] So you gotta be really, really careful
[20:30] about configurability like this.
[20:32] New options not only make it more confusing
[20:35] and harder to maintain, but harder to test
[20:37] and be assured about.
[20:38] Like every time I changed anything in this gem for years,
[20:41] I felt like, oh, it was probably gonna break somebody else.
[20:43] And I eventually,
[20:44] I just grew to resent it and I stopped working on it.
[20:48] So we had 67 people contribute to the gem,
[20:50] but at the end of the day,
[20:51] I was the only one accountable if it broke.
[20:53] And because I'd pulled in so much of other people's code
[20:56] that I didn't read,
[20:57] like no one understood how it all worked.
[20:59] And so last week I just merged to the final PR to this gem
[21:03] to say that it's no longer maintained.
[21:05] And so now, all that's changed here
[21:07] is no one's accountable if it breaks.
[21:09] So don't use that gem.
[21:11] So nine years later I had a new Mac book,
[21:13] but the same problem.
[21:14] Why is it so hard to test JavaScript on rails?
[21:16] I've learned some lessons this time
[21:17] and I create a new gem called Cypress rails.
[21:20] This is also a glue gem and it connects Cypress,
[21:23] which is like, a web testing tool and JavaScript with rails.
[21:28] And it's got a test API, it's got a runner,
[21:30] it's got a great CLI.
[21:31] It brings more to the table.
[21:32] And on the rail side,
[21:33] I just use kind of more hardened to pieces,
[21:35] rake, rail ties, action pack.
[21:38] And the glue's responsibility here is much less.
[21:40] Here I'm just responsible for like,
[21:41] kicking off a new test server,
[21:44] sort of like your system tests do.
[21:46] I roll back transactions between tests.
[21:47] So there's like a little like route that you can hit
[21:49] to say like, hey,
[21:50] I wanna isolate the data between these tests
[21:52] that you can do.
[21:53] And then there was a superclass that we wrote
[21:54] for mini test,
[21:55] where you could inherit from that
[21:57] and then like run rails test
[21:58] and it would include your Cypress tests
[21:59] along with your system test cases and stuff.
[22:01] But it turned out that in practice
[22:03] that was really slow and cloogy
[22:05] 'cause Cypress didn't know about it.
[22:06] It didn't work with all the extensions
[22:07] and I could just tell it was not gonna be something
[22:09] I would use.
[22:10] So I had a tough choice to make.
[22:12] I had to choose between,
[22:13] remove the feature and potentially make somebody mad
[22:16] or continue supporting that future in perpetuity
[22:18] for fear, even though it's not very good,
[22:20] mostly out of fear that somebody might get mad
[22:21] if I removed it.
[22:22] But the Jasmine rails experience had taught me,
[22:24] like the right thing to do is just to get rid of it.
[22:26] Now, if you publish a gem
[22:29] and it doesn't have a particular option,
[22:31] no one's gonna know to thank you
[22:32] that your gem doesn't do this unnecessary thing,
[22:35] but like you need to have your own backbone
[22:38] and kind of stand up and be a bit of a Vanguard
[22:39] for complexity creep.
[22:43] So now there's only two modes, right?
[22:45] In that gem and it makes testing really easy.
[22:47] I've got a test out for transactional mode.
[22:48] I've got a test that for non-transactional mode.
[22:50] And so I can be really assured
[22:52] that like whenever I release something,
[22:53] as long as the test passes, it's probably gonna be okay.
[22:56] So yeah, the lesson there is just,
[22:57] don't let complexity, whether it's complexity of like,
[23:00] you feeling like you got to do everything
[23:01] or other people telling you, you got to do everything,
[23:02] a bunch of pull requests like way down your gem.
[23:05] 'Cause like the last thing you want is five years from now
[23:07] hating the thing that you created.
[23:09] So last but not least,
[23:10] we're gonna talk about the cut of your gem.
[23:12] And this is a story for anyone
[23:13] who doesn't take feedback particularly well, like me.
[23:17] So Test Double our company.
[23:19] It's an illusion to the name Test Double,
[23:21] which is like it to be a stunt double in your tests,
[23:26] any fake thing that stands in for a real thing
[23:28] when you're testing.
[23:29] Now most mocking libraries,
[23:31] a mocking library creates Test Doubles.
[23:33] It's just one of the same effectively.
[23:35] Most of mocking libraries,
[23:36] they like their API is kind of make it real.
[23:39] Like they're focused on like how do I take real things
[23:41] and sort of poke holes in them like Swiss cheese.
[23:43] And what they really do is they facilitate testing
[23:46] of hard to test code
[23:48] is essentially how most people use mocking libraries.
[23:50] But my preferred kind of mocking library,
[23:54] same kind of tool set, but like totally different framing.
[23:56] It's like paint by numbers.
[23:57] Like how do I help facilitate writing easy to use code
[24:02] that just happens to be via tests.
[24:05] So there's a way different mental model.
[24:07] And the way that I kind of got into this mode
[24:08] was in the late aughts from a great Java jar called Mockito.
[24:12] And I kind of poured it, in 20 times,
[24:15] a spiritual successor for Ruby with a gem called gimme.
[24:18] And so I made, in my spare time
[24:20] because I had a job that was kind of constraining,
[24:22] a little bit stuffy at the time.
[24:23] And so I was using gimme to have fun.
[24:26] So I got to play with like a lot of Ruby meta-programming,
[24:29] that was fun.
[24:30] Instead of doing unit tests,
[24:31] I did executable requirements with cucumber.
[24:33] And so like all of the documentation was executable.
[24:35] I thought that was cool.
[24:36] A little some clever inheritance going on,
[24:38] but mostly I was just like having a good time,
[24:40] but I was really proud of the API.
[24:42] I knew that the API was good.
[24:44] I knew it was an improvement
[24:44] over anything else available in Ruby.
[24:46] I still use it.
[24:48] And Jim Weirich who was a Ruby hero of mine.
[24:52] Unfortunately he's no longer with us, but he wrote rake.
[24:55] He was an early contributor to Ruby gems.
[24:57] Great person, he also had a gem called Flex Mock,
[24:59] which was a mocking library.
[25:00] I saw him at a conference and I pitched Gimme to him.
[25:03] I was like, check this out.
[25:03] This is really great.
[25:04] And I was really nervous and I waited and he said,
[25:06] it's interesting!
[25:07] He was like very positive.
[25:08] He was also a really positive guy,
[25:09] but I was super heartened, I was really excited by that.
[25:12] And then later on that year,
[25:14] he sent me a pull request to Gimme.
[25:16] So he was like using Gimme
[25:17] and I was like, oh my God, my life is over.
[25:19] This is the best.
[25:20] Like my hero is using my thing.
[25:22] And it was just really exciting.
[25:24] So then I saw this tweet when he said, hey,
[25:27] he was like dismayed by the use of Cucumber
[25:28] instead of Unit Tests and stuff.
[25:30] And I was like, that sounds like something I did.
[25:32] And then I looked at the date and then I looked at this date
[25:36] and I was like, oh God, my hero just subbed tweeted me.
[25:39] (audience laughing)
[25:39] And it was really demoralizing.
[25:41] And the worst part of course
[25:42] is that Jim was absolutely right.
[25:44] 'Cause I would start to get issues like, hey,
[25:46] here's a bug from JSON.
[25:47] And I try to like pull back the covers and work on it
[25:49] and realize that all I had with these integration tests,
[25:52] I didn't have unit tests
[25:53] that were actually forcing me to write well factored code.
[25:55] And it was just a big yarn ball.
[25:56] It was changing one thing here
[25:58] would break something over here.
[25:59] So it was like the right idea.
[26:00] It was the right API,
[26:01] but it was just the wrong execution.
[26:02] I didn't do my best work to implement it well
[26:05] and to be maintainable.
[26:06] So I just gave up for 10 years.
[26:08] That's one solution to the problem
[26:11] until today, because breaking news,
[26:13] I got a new gem to announce, it's called Mocktail.
[26:16] And it's something I built over the summer
[26:19] because like, I've decided to gems can have sequels.
[26:22] So it's a totally fresh start.
[26:23] It's the same kind of API,
[26:24] but this one, like I just decided to code it good instead.
[26:29] You can check it out online.
[26:30] We're not gonna go into the API and stuff today,
[26:31] but it's called Mocktail.
[26:32] Hopefully it's well documented.
[26:33] We're gonna release some screencasts and stuff
[26:34] over the next month and a half,
[26:36] but same kind of deal.
[26:37] I've got a day job, which is more flexible now.
[26:39] And I spent time thinking hard
[26:41] about the API over years, really.
[26:43] Building prototypes and throwing them away.
[26:45] I strive to get a hundred percent code coverage.
[26:47] Had to be sure it all worked.
[26:49] Lots of small well-named classes.
[26:51] So I could be sure that I'd be able to maintain it.
[26:53] And I was feeling really good about it.
[26:54] And then I had a lot of thread safety issues.
[26:56] So then I fixed those, but I was a little less happy
[26:58] because I don't like dealing with multi-threading stuff,
[27:00] but overall, like big picture,
[27:02] I had been proud to make gimme,
[27:04] but I was super proud to share Mocktail
[27:06] 'cause I really believe in this
[27:07] and I think it's gonna work over the long-term.
[27:10] And more than just pride,
[27:11] like I know future me is gonna thank me
[27:13] because it's gonna be more maintainable
[27:14] and because I've got a really great test suite,
[27:17] if like Ruby changes under me
[27:18] or I needed a dependencies break,
[27:20] I'll have a better chance of fixing those quickly.
[27:22] It's also more inclusive.
[27:23] If somebody wants to work on this gem later,
[27:25] like they'll just be able to clone it
[27:26] and then run bundle and run rake
[27:28] and really quickly like get up and running.
[27:30] It's not gonna be arcane.
[27:31] And it's exemplary in the sense of like,
[27:33] it's a good example of my work.
[27:34] I'm pushing up my best work to GitHub
[27:36] so I can share like,
[27:37] this is how I like to code, right?
[27:39] And not a lot of us have a lot of portfolio work.
[27:41] So if you've taken taken the time to write a gem,
[27:43] it might be a good time to kind of show off
[27:44] what you know and how you like to write code.
[27:46] So over the years, even though like,
[27:48] I don't wanna discourage anyone from like
[27:49] just playing around and like programming for fun,
[27:52] if you do plan on like maintaining something
[27:53] for a few years,
[27:54] I've never regretted taking the time
[27:56] to measure twice and cut once.
[27:58] So that was a little bit about the four Cs.
[28:00] And I hope that you take this talk
[28:02] and take some energy today,
[28:03] and think about like maybe there's a gem
[28:05] that you could create or contribute to.
[28:07] And if you do create something,
[28:08] I would love to hear from you
[28:09] or even if you're having trouble or issues,
[28:11] reach out to me by email or DM me or tweet
[28:14] or if you've got any feedback on the talk
[28:15] or you'd like to share it with somebody else,
[28:16] we'll be sharing a video real soon.
[28:18] And I'd love to hear from you,
[28:20] but last but not least before I close and go away.
[28:23] I just wanna share real quick.
[28:24] This is a very special RubyConf
[28:25] because this is actually the 10 year anniversary this year
[28:28] of my very first Ruby conference.
[28:31] I spoke at Rocky Mountain Ruby in 2011,
[28:34] just down the street in Boulder
[28:35] and a guy named Marty Haught was organizing
[28:38] and he took a chance on me.
[28:40] And he's also a director at Ruby Central today
[28:42] and organizing today
[28:44] and I got to speak alongside heroes like Jim.
[28:48] And it really changed my life.
[28:49] This is actually after Jim sub tweeted me,
[28:53] but I took it in stride and it was just a great experience
[28:55] and my life has been transformed.
[28:57] That's actually me.
[28:58] I oddly enough, that talk was about Jasmine
[29:01] and the Jasmine rails gem I just told you not to use.
[29:03] Time marches on.
[29:04] We all learn lessons.
[29:07] And that's not all,
[29:08] it's also the ten-year anniversary actually next week
[29:11] of the founding of our company, Test Double.
[29:13] And that's really exciting.
[29:14] I can't believe it's been 10 years.
[29:16] (audience applauding)
[29:18] Hell of a run.
[29:19] So we just started as two people
[29:21] working at like mostly medium and small companies,
[29:22] but now we're almost a hundred consultants.
[29:25] A lot of you in the room have worked with
[29:26] or at, or are working at Test Double or with Test Double
[29:29] and it makes me so happy
[29:30] that we get to work with so many cool people.
[29:32] And we're at clients now,
[29:33] like, GitHub and Zendesk and Betterment and Gusto,
[29:36] just working with some of my favorite companies and products
[29:38] and just really awesome teams.
[29:40] And it's been a wild ride, I'm super grateful.
[29:44] If share our mission of like wanting to like improve
[29:47] how the world writes software,
[29:49] you can learn more about like being a consultant
[29:51] at careers.testdouble.com.
[29:52] And if you're like, at work
[29:54] and you're doing something ambitious,
[29:55] or you could use some additional help
[29:56] or you're maybe like got some legacy rescue
[29:58] or like wanting to learn some new skills.
[30:00] Our service is primarily to just join your team
[30:03] and help you out and get some done alongside ya.
[30:05] And you can learn more@services.testable.com
[30:07] or just send that link to your boss.
[30:10] And last but not least like really,
[30:11] I just wanna slow down for a second pause
[30:13] and really say, thank you
[30:14] because like my life has transformed over the last 10 years.
[30:19] And it's all thanks to the Ruby community.
[30:21] Like Test Double would not be the company that it is today
[30:23] without all of you
[30:25] and being able to engage and meet people.
[30:26] Some of like my favorite,
[30:28] like lifelong relationships and friends
[30:30] I've met in this community.
[30:31] And the fact that I still get to go up here
[30:33] and yammer on at you is just,
[30:35] I'm tremendously happy to be here today.
[30:38] I'm really, really grateful.
[30:39] And I wanna thank you all personally for your time today.
[30:41] So thanks.
[30:42] (audience applauding)