Transcript

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:

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, mentioning me on twitter dot com is the surest way to get my attention.)

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)

Justin Searls

Person An icon of a human figure Status
Double Agent
Hash An icon of a hash sign Code Name
Agent 002
Location An icon of a map marker Location
Orlando, FL