 a few of you way down the back there, and this room is very big. You can come forward. It's fine. I won't bite. I promise. Do it. It'll be great. All right. This is teaching aspect to play nice with Rails. Let's get started. I could not be more excited and more happy to be back here at RailsConf on this stage that is a lot bigger than when I thought I was going to be presenting on this year. Last year I didn't make the conference. Some of you know this story. Some of you don't, but I was suffering from a life-threatening infection in my leg, and actually exactly a year ago to this day I took this picture of myself in hospital and texted it to my mom. She had a bit of a freak out, but it was fine. This is a bag of a drug called calf triaxone. Calf triaxone is a super powerful broad-spectrum antibiotic, and were it not for the efforts of the doctors and nurses of the NHS in the UK, I would not be here today giving this presentation in a very literal sense, and I'm hugely thankful to them and the healthcare system in the UK that means that a year later I am alive and able to give this talk on this stage. But I'm not telling you this story to induce sympathy towards me, but instead to tell you about something slightly odd that happened at the RailsConf program last year. We had this problem where RailsConf last year was the conference where Rails 5 was due to be announced, and that meant that there were a bunch of talks in the program about how to get various things working with Rails 5. It's a big major version. Stuff will break. That is bad. And I was going to give the talk about how to get R-spec working with Rails 5. And there wasn't really a backup speaker that had a talk that looked like that, and that was a fairly large gap in the program. So myself and the rest of the program committee decided that the correct thing to do would be to call up Justin Sells, who's giving the keynote tomorrow. I don't know if he's in the room. He's probably not. He's probably writing his keynote. And have him give my talk instead. This is funny because the program committee rejected the talk Justin submitted outright. We were like, nah, that doesn't seem like a talk we'd like to have. And then you are now Justin morally obliged to give the R-spec talk because Sam is dying. And he took that with nothing but grace. If you've seen the talk, you know that he spent the back half of it basically trolling me. But in all seriousness, I've said this before, and this will be the last time I mention this on a Ruby Central stage. Justin, I am hugely thankful that you did that, even if you're not in the room right now. And I think everyone should use the meme hashtag. Hashtag Sam Phippen is Justin Sells confirmed. Tweet him and let him know that you're all thankful, too. So, we're now at the top of the list of the top five, 0.1. Some of you will probably need R-spec to work with Rails 5.1. That compatibility is coming, but with a couple of caveats. We do not have any support for Action Chat Box test case nor action dispatch belief system test case. Please have some faith in us. So, in all seriousness, Rails 5.1 has not been released yet, but we have a branch ready that should just work. So, there will be no changes required by you. As soon as R-spec 3.6.0 is released, you should be able to upgrade and everything will work. We don't have integration with system test because I haven't had enough spare time to do that yet, but I would absolutely welcome a pull request from anyone who would like to integrate Rails 5.1 system test with R-spec. So, that's sort of the front matter. Now, let's get into the actual talk, the integrations between R-spec and Rails and how we got there. Before I go too far, I think it's really important to discuss sort of the high-level architecture of the R-spec framework. One of the things that I think not a lot of people realize is that R-spec isn't a single monolithic gem, but instead a series of testing components that are designed to work together really well. So, when you specify gem R-spec in your gem file, the first thing Bundler is going to do is it's going to go out and fetch the R-spec gem, but the R-spec gem actually doesn't do anything on its own. It doesn't have any code inside it of any consequence. Instead, what the R-spec gem does is depend on the rest of the components of R-spec that form together to make the testing framework you're so used to using today. One of the dependencies is called R-spec core, and that provides describe and it and tagging and the runner and all of the tooling that you use to actually build and execute your test suite. R-spec expectations provides the expect keyword to all of the matches that you're used to using in the powerful composed matcher system. R-spec mocks provides the mocking and stubbing capabilities of R-spec, doubles, spies, and all of that good stuff. And so these are the direct dependencies of R-spec, but they're independent gems. You can pull each part of them on its own without having to have any of the others. All three of these gems depend on what's called R-spec support, which most people don't know about. R-spec support is our internal shared code between the R-spec gems, and we use it for various things like pulling methods off objects, determine method signatures, ruby interpreter functions, and so on. And nothing in this gem is marked as part of the public API. So you should never call code in R-spec support directly, but it is there nonetheless. R-spec Rails is an entirely different beast. It functions kind of on its own. It packages the rest of the R-spec ecosystem and also provides a bunch of its own code and links directly into the Rails version that you're using. In this manner, R-spec Rails is able to provide the entirety of R-spec that our users are already used to and then combine that with Rails-specific stuff in order to make testing your Rails app really easy. So if you see gem R-spec and gem R-spec Rails in a gem file, this is actually kind of a smell. You're specifying your dependency on the gem twice, and instead you can just pull R-spec Rails and everything will be fine. Let's talk about Rails. I think this is a reasonable statement, and that complexity also comes with the fact that R-spec is really permissive about which parts of Rails you can use and at what versions. Specifically, R-spec supports all Rails versions greater than or equal to Rails 3.0, and that means we support 3, 3.1, 3.2, 4, 4.1, 4.2, 5, and soon 5.1. That's a lot of Rails versions, and it leads to a lot of code inside the gem that conditionally switches on what Rails version is being used. It leads to our cucumber examples being tagged with different Rails version support, and it leads to here docs that contain Ruby code that's switched on Rails versions 5. That was a lot of work to get R-spec compatible with Rails 5, and in the core of this talk I'm going to be going through some of the issues that I encountered, how I thought about debugging them, or where external contributors were involved, how I helped them make their issues better, or got help from other necessary maintainers to fix everything up. This is broken into a series of lessons, and the first one is that major versions mean people can break things. At the keynote, not last year but the year before, it was announced that controller tests were going to get soft, deprecated, and what that actually meant is that assigns and assert template were going to be removed. Assigns is the thing that lets you test the instance variables your controller assigns, assert template is the thing that lets you test which template is going to get rendered. I'm like, please, no, stop. There are thousands and thousands of controller tests in the world that desperately need these things to function. You can't do this to us, and I was overreacting because it turns out the Rails team had a plan which was this gem called Rails controller testing. All the Rails controller testing gem does is provide assigns and assert template again, and so I'm like, great, this is perfect. I'll just integrate this back into our spec, we'll run our tests, everything will be fine, so let's add it, make sure it works, and oh no, the entire test suite exploded. Looking at this a little bit closer, we discover that actually it's just view specs that have stopped working, and I'm like, okay, that's less bad because mostly nobody writes view specs, and wow, maybe you do all write view specs, and I'm just terrible. No, but specifically, the thing that was broken is path helpers, things like URL4, gadgets path, user path, et cetera, you know, those things. I was like, all right, let's work out what's gone on here. So we take one of our automatically generated view tests, we stick a pry in it, we look for the gadgets path, and we get undefined local methods for gadgets path. We switch back to Rails 4.2, we call the same method, and it works. So we know somewhere between Rails 4.2 and when this gem was extracted, something has broken, and I'm like, all right, well what could possibly have changed? It's pretty likely that gadgets path is not provided directly on the test, but instead one of the things that gets included to the controller, so I'm like, all right, controller, tell me everything that you're made up of, and you get a huge list of nonsense back, but just to explain this, the top line there is the singleton class of the object, the second line is the class of the object itself, then we have action dispatch test process, three random anonymous modules, action controller base, and then a bunch of stuff. Action controller base is where we can approximately stop looking because that's the thing we're used to using. You switch back to Rails 5 and it looks materially different, and specifically those three random anonymous modules have just disappeared out of this ancestor chain, and I'm like, anonymous modules are the hardest thing to debug ever because they don't have a name, they don't have a source location, how on earth am I supposed to find this? So I stick a more prize in there, I rip my hair out, I get mad, and eventually it points me at this line of codes, and I'm like, I look at it and it's like, newing up a module, including that module into another module, and then doing some crazy hooks, and I'm like, nope, no, had enough. I know aspect Rails like the back of my hand, but I am not like an expert at the deep, deep internals of the Rails framework, and I'm willing to admit that. So I hit up Sean, and I'm like, Sean, this is nuts, what's going on here? And we hop into a Skype call, and we actually end up spending something like three or four hours trying to debug this ourselves. Sean, would you say that's about fair? Yeah, yeah, so four hours of my life, gone, and then eventually Sean's like, this appears to be a load inclusion order kind of bug, and he writes this like essay of a commit message, this is the first pro tip of this talk, write really long commit messages that explain what's going on, because anything that takes four hours of two like Ruby maintainers to work out is complicated and non-obvious and scary. The diff looks like this, and I won't make you squint, but basically it's changing the order that the controller testing gem is including stuff in, and everything works. We went from a broken gem to a functional gem, and I think this is a huge win for collaboration. When I look at this change, I was like on my own, solo maintainer, scared and lost and confused, and I'm like, hey, Ruby ecosystem, I need some help, and it helped me, and it was great. Like, I love it when we get together and fix that stuff. It makes me really mad that I had to debug anonymous modules, but I think this is a great win, and then we were basically ready to release the gem, and we did, and users started filing issues. Oh my God, so many. It turns out that the test suite that you have usually doesn't cover all the cases, and your users are doing things you can't possibly imagine, so the remainder of the issues that we'll be looking at today were issues filed by actual RSpec users as opposed to stuff I encountered while I was trying to do the upgrade. Lesson two, it is actually possible for Rails to have bugs. This may seem like a controversial statement, but I hope you believe me. So we're talking about RSpec Rails issue 1658, you can look this up, but the user was basically like, signed cookies are not available in controller specs in RSpec Rails, and I'm like, the hell is a signed cookie? I've literally never heard of that Rails feature before. Like, all right, I'm sure that's supposed to work, let's find out. So I applied the label to it, triage, and this is a pretty common thing for maintainers to have, it's like a personal state machine for how they move issues. When I label an issue as triage, it basically means you filed this, and I haven't yet had enough time to work out whether there's enough information to reproduce the issue. And then I let it lie for a little while, and I come back to it a few days later, and I basically come to the conclusion that the original issue didn't have enough information in it, so I ask this question of the original submitter. This basically says, thank you for filing this bug, I wasn't able to reproduce it with the information you gave me, please give me a Rails app that I can clone so that I can just run bundle exec Rspec and see what your bug is, and then I apply the label needs reproduction case. I think this is an incredibly common maneuver for maintainers these days because even though you filing an issue represents us having a knowledge point that something is wrong, it can be really, really hard for us to work out what exactly is broken. Rails is a complicated environment, there are many gems, there's all of your configuration, there's everything you do to actually cause this bug to appear, and frankly I can't necessarily reproduce that given just a few lines of spec in an issue, and the user comes back immediately with like, yes, done, clone this, bundle exec Rspec you're good, and I'm like, yes, go team, we did it, this is how open source is supposed to work, and so I confirm that the issue is real and I move the issue from needs reproduction case to has reproduction case, and this state machine, this triage needs has, is something I do with nearly all Rspec Rails issues because it helps me know what I do and do not have to fix, but now that I have this green issue, I can actually begin debugging the problem. So we clone the Rails app that the user has provided and we also clone a fresh version of Rails into its own repository. I do this so that I can move the commit that it's on backwards and forwards, and I point the Rails app that the user has provided at my fresh Rails clone. I check that the tests are still failing and I get bisect bad, I check out 4.2 beta 4, and there's a very specific reason it's this version. This is the last version of Rails 4.2 on the master branch, after this 4.2 was released and then they have a 4.2 stable branch which doesn't track master, so I can't simply bisect between like 4.2 and 5 on that branch. I have to use master, so I check out this specific commit. I run my tests, they pass, I get bisect good, and git tells me I have 6,794 revisions in order to determine what's wrong. Oh god, this is my life now. So I basically just run the test suite backwards and forwards, get bisect bad, good, bad, good, bad, good, and eventually this commit pops out. Commit number A29 whatever, and I leave myself a little note as exactly to what the problem is, but not necessarily a fix. This is definitely one where the bug exists in Rails, not in aspects interaction with it, and so I leave it for a while, life happens, I get a job where I have to move to New York, that's a thing, and then after a while I'm like at RubyConf and I was having a discussion with somebody and I was like, so I bisected 6,000 revisions in Rails, found the specific problem, and I'm not really sure what to do now, and Sean turns around and goes, you have a breaking commit, just show me, I'll fix it right now, and I was like, all right, so I go on to GitHub and I literally just ping at esgriff, hi. Lo and behold, immediately, this issue is not the bug in our spec, it can be reduced with Rails alone. Sean opens the bug on Rails, which provides a Rails reproduction script, and specifically states, this can be replicated purely with Rails using the public API. In open source we have a twisty, tourney maze of dependencies. Your Rails app that uses RSpec pulls in Capybara and a diffing gem and a coffee script compiler for some reason in 2017, actually you got rid of that, that's an unfair accusation. So, before filing an issue on a specific gem, it's worth noting that the bug could be anywhere in the dependency tree of that gem, and so it's well worth paring down and seeing if you can reproduce it with a subset of the things that are available. Specifically, Rails has a guide for doing reproduction scripts and before filing bugs on RSpec Rails, I would thoroughly encourage you to check it's not a bug in Rails first. Lesson three, sometimes you can't just call up Sean to fix the problem. I love you Sean. So, this issue, RSpec Rails 1662 can no longer set host exclamation mark in before block for request specs, and that's not immediately obvious, so let me show you what this means. Basically, if you have an RSpec configuration that globally sets a host in a before or block this stopped working, and there are good reasons to do this before all blocks are useful, and this host bang can happen if you have router configurations that cause you to have sub-domains in your app that are also being responded to, and I'm like, this should work, and also I feel like I touch this code when I was upgrading RSpec to work with Rails 5, so other RSpec maintainers please don't close this issue, I want to take a look at it, and I asked the submitter for a reproduction case exactly as I did in the other issue, and they come back to me not just with a reproduction case, but with I made a short screencast of how to use this app to debug it, I'm like, oh my god yes that's the best thing ever, that's so much better than just clone this and bundle exact RSpec, he walks me through, he shows me where the bug occurs, he shows me what he tried changing, so good, and so I pull down the reproduction app, I do the same bisect, shimmy and shake thing, the commit message spits out, and I go have a look, and it's Array! Basically in the change to Rails 5, this got committed which changed the application initialization logic to happen lazily, and instead happening before setup blocks, before setup blocks are equivalent to before each in RSpec, and so will always happen after that before all the user has specified, and that's our bug, so I'm like this one is simple enough that I can fix it myself, I open a pull request on Rails, explaining what changed, why it broke the thing, why proposed fix, maintainers love it when you drop context on them, the more words you write about your change increases the likelihood of it getting accepted, because it just makes it easier for us to understand what's going on. I also did that as part of not just the PR message, but the actual commit as well, and the reason for that is that if any Rails maintainer in the future is ever blame diving the source of Rails to work out what's going on, they can without leaving their terminal look at that commit message and see what changed and why and who did it, and they can like come at me on the internet if I broke everything, but I don't think I did. So after some discussion, sort of round trips with Aaron, I think Raphael got involved for some of it, Sean got involved for some of it, I got merged, and then I completely forget about it and don't close the issue on aspect for plural months, and so someone just comes along and is like, hey, you fixed the bug in Rails, you should close this issue. All right, you're right, I totally, totally should, I'm a terrible fallible human, and the truth is if I fix something and you just ping me on GitHub and are like, you have fixed this, do you still need this issue to be open? I can be like, no, and then I can close an issue, and you did a simple one-minute action that gets rid of an issue on my issue tracker as an external contributor, that makes me super happy. So to summarize, RSpec definitely has bugs, but the surface area of R integration with Rails is not all that big, and if you have a Rails specific bug in your RSpec suite, I have seen it be in both places, but it can be because of Rails, and literally every fix that I showed today was in a gem that is owned by the Rails project, not by RSpec. I know that if you're in this room, you're way more likely to be a fan of RSpec than a fan of Minitests, but if you can, use the Rails reproduction script guide, which asks you to test it in Minitests just to make sure that's where the bug lies, and Rails has a really good guide for doing that. When you open an issue that has more than the most basic form of complexity, I'm likely to ask you to send me an app where I can reproduce exactly what's going on. The reason for that is not that like, I value my time more than yours. It's that five minutes of you doing that extra work of packaging it up can save me literally hours of debugging in my framework, and that's really important. You can always just call Sean to get Rails bugs fixed. Okay, real talk, though. I think working on an open source is a really hard thing to do as a sort of career maintainer. It absorbs your evenings and weekends and sometimes stuff goes out into the world and everyone's test suite is on fire and you are the only person in the world who can fix it. I have a friend who once tweeted retweet if participating in open source has made you cry. That has happened to me. I 100% can tell you that for sure. The work represented in this talk sums to more than 40 hours of maintainer time. That's a full work week. My time, other aspect maintainers, Sean's, Rails core team, Rails Committer team, you're all wonderful people. Give maintainers hugs. They really deserve it and more importantly than that, like I think open source is more approachable than it has ever been and it will continue to do so. The previous talk was about how to get starting contributing to Rails. The talk before that was a deep dive into a new Rails feature. If your company materially depends on the existence of Rails, RSpec, Bundler, RubyGems in order to exist, it seems natural to me that you should find some time to work on that. With that, you saw a giant red slide at the beginning of this talk. I'm a fan of communism. Maybe we should find ways to ensure that maintainers can get paid for their work, just a thought. So, just one random cheer. In the meantime, though, please buy me drinks at the bar if you use RSpec. That seems like a fair trade-off, right? All right, quick wrap-up. I work for a company called Digital Ocean. We are an infrastructure as a service provider. That means we do servers, block storage, networking, all the primitive components for you to build amazing applications. We are hiring. I like working there a lot. It is a lot of fun. Our products are cool. I have swag. Come find me. Let's talk about servers in you. So, that's all I've got. Thank you very much for listening. I'm Sam Fippin on Twitter, Sam Fippin on GitHub. If you would like to email me, I am sfippin at do.co. Thank you. I will take any and all questions. Are the anonymous modules still there? Yes. To the best of my recollection, it's like an active support concern that does something with Rootset that, listen, I'll send you the commit. Like, it's, computers are extremely bad. One thing that could be done there to make it easier is to have those modules define self.name with something sensible, but like, I don't know what the performance implications of that are, or if it's a hot path or whatever. And there's like a whole section of Rails maintainers sat just over here. So, maybe just like ask them. Anymore. I literally can't see. Shout, be loud. All right. Thank you very much.