Get started with Component-based Rails applications!
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Hagemann, Stephan
Component-based Rails helps you regain control over your sprawling Rails application. It helps you structure your new Rails application in a way that it will stay more manageable longer. It helps you think completely differently about writing applications - not just in Ruby and Rails! This session will help you pass the initial hurdle of getting started with component-based Rails. While there is nothing really new, there is a lot that is just a little different. We will go over those differences so your start with component-based Rails is a smooth one.
 Before we start, why are you here? No, I'm actually really interested in what brings you to this workshop. Are you like, does anyone here have an app that has, say, a code base of a couple hours or a test suite of a couple hours, 100,000 lines of code, and they're like, anyone? There's a couple hands. This sucks. I want to change something. Cool. Is anyone here writing a lot of Rails apps? Someone doing consulting and spinning them up really often? And what's the, okay, cool. So when I had a three hour proposal, the second half was going to be that really large code base that we try to tear apart. In fact, you don't need 90 minutes to tear a large code base. Part of it's more like months, but it would have been condensed. So this one, this part that we're not going to be doing is only kind of refactoring out of this sample app, SportsBall. But I hope that you will see that the ideas will apply in larger contexts. Again, whenever you do something about architecture, you make these examples that make you look totally goofy because you're doing these super-uber complicated things for almost nothing. And that's going to happen again today. So bear with me. So getting started with component-based Rails applications. I should tell you why I am here. My name is Stefan Hagelman. I work for Pivotal Labs and I'm in that category where I've spun up a lot of Rails applications sound. I have a feeling that I keep touching the microphone in a funny way. Is that true or not? I wrote a lot of applications, but there was one client in particular that kind of kicked off where we were engaged with them for almost two and a half years and the app grew really, really large. And we were looking into ways to kind of bring some structure into it without leaving the realm of the monolith, if you will. So I then did that to almost every app that I did thereafter. And it's kind of still such a... It's a weird thing to talk about because if we were at a Java conference, I wouldn't give this talk. If we were at a Go conference, I wouldn't give this talk. There's many languages in which this is so common that there would be no topic for me because structure within applications is something that is a problem for Ruby because of the way it is. It's not a problem in other languages. And I've kind of become obsessed about it. If you kind of want to just get an overview, go to Conferrex and find the talks on this topic that I've given. So despite this not being a talk, but a lab, I want to answer the question quickly of why you want to do component-based Rails. I see a host of benefits when you add this one tool, namely a component, to your toolbox for Rails. Let me go over these. So the main change that you're going to see when you add components to your application is that you're going to be able to draw something like that graph. And that's OK. So let me just go over the examples because it's hard to otherwise explain how important that is to have another structure like that. Communication. So we don't know what this application is about, but I can tell you that there is an importer that has something to do with programs, as you can see at the bottom. And obviously there's also something going on where there are sources and master, so probably some sort of import and reconciliation of some sort of thing that's called a program. Oh, yeah. Look at the cluster. Yeah, probably that is the reconciliation piece where sources get kind of merged or whatever into masters. If I don't, you indeed picked the worst spot in this lab. You should probably. So you can talk about your application at a higher level, which you can't do because what else do you have? You have module names, but modules don't kind of segregate. So the important piece is not just that you have a box, classes are boxes, if you will, modules are boxes. So what do the boxes mean? And when you go to components, you have arrows and you can draw them from one box to the other box, but you cannot draw them back, and that's the important bit. So I can communicate much easier about an application because I have a new higher level structure that I didn't have access to before. It makes collaboration easier. Say I'm working on source today, stand up this morning, we decided I have a feature that touches source, you have one that touches user. In this graph, again, without knowing nothing else about the application, we know a few things. If I truly only change source, I do not need to run the tests for master programs, reports, users, and styles. My source code does not touch that source code. That source code doesn't know that I'm touching something because it doesn't even know that that exists. It doesn't care about it. So I don't need to run those tests. And similarly, you have things you don't need to run tests for because you're not affecting that part of the code. It's still one application, mind you, right? But we don't need to test those things. We only need to test boxes that either we change or that have arrows pointing into us because those are the things that could be affected by our change. And it also means that the area where we could potentially be in conflict with our change at the end of the day, there's only two if you just go strictly by numbers and set all these boxes at the same size. Two out of 10 boxes, we could potentially have a conflict. If you have one application and no further structure, you can't think about these things. You certainly don't have the benefits of running less tests and having less conflict possible. Creation is super interesting. When you say you will have a larger new feature that you want to roll out, you can put it into a component and switch that component on in development and in test and then in review, but you never switch it on in staging until you're ready. You can talk about it. You can change it. You can show users. You can maybe give beta users access. And the way you turn it on and off is one line of code where you turn that gem on or off in the respective environment. Maintenance. Save. Save reports is an API. Save, I don't know. Maybe it's a SOAP API. We wrote that and we don't like it anymore. We want a JSON API. The way you can create the JSON API is you take reports, the folder, there's a component there, you duplicate it, you call it reports2, you name every occurrence of reports with reports2 and you just start changing reports to whatever you want. You can slowly work your way to reports2 deprecating reports. And when you're done, you just turn on reports2 and you turn off reports and your codebase is as clean as it was before. And there's one major thing that kind of underlies all of these benefits and that's comprehension because you can suddenly do something, again, you can't do when you just have classes and these other concepts as your mechanisms. And that is, you can do this. When you are onboarding someone, when you're thinking about what you're doing, you can treat everything that is below you as external and everything that's above you as not mattering at this moment. Whenever you look at a part of code, you have a much lower cognitive overhead to kind of understanding what's going on because it is all smaller and you're creating more separation. All right. So Ruby installed now. You were somewhere there. Sweet. Did the test pass? Cool. All right. So what's the app? I don't know if anyone started it. It's a tiny app. Is scaffold deprecated? I don't know. It's scaffolding to create these teams pages here. Falcons, Braves, I have on my app. They also play each other, which is funny. Also the Falcons win, which I don't understand because the Braves have clubs. But okay. So the Falcons win in this case. And this is, here's the main contribution of the app. So this is all scaffold so you know the views, right? You can do everything with this. So then you can ask the app, if the Falcons play the Braves, who's going to win? We have only one game. Falcons won that game. Prediction Engine, pretty smart says Falcons are going to win again. So this is what the app does. The structure of the application, and now join me in opening some code. The structure of the application is interesting. If you open the source code, the first thing you notice is what? You notice this component, but there's no app directory. So you can, but you don't have to, you can delete the application directory because you can replicate it inside of a component. And sorry, I'm using component when I talk about gems and engines inside of an application that are vended with the code. I just, I use all of those things and I often mix them up. And yes, there is a components folder, right? But when you, when we try to understand what's going on here, probably the easiest thing is to look at routes and realize there is indeed nothing in this application that provides anything. We're gluing an engine into this app. So to see what's going on, we could look into the routes of the component. And that looks more normal, right? Now you see our games and teams and prediction. But keep browsing around. If you see something weird, just shout it. So I'm just going to talk over how I think, or how I view these things. So the interesting bit in the gem file, which obviously is, so app, app is the component. This app needs to be loaded. So we have a gem app here in line four. And it pulls it out of the path. This path thing is usually, is documented as being for gems in development, where you have a local, a dependency on another gem and you're developing that, so you're pointing to it locally. And I guess that's kind of still true because I'm working on this app so everything's under development, if you will. But so this is how this application, and when you bundle that, it's going to show you everything and it's going to show you here that it's loading app from source. And this is something that, when other people talk about engines, they often say, and then you have to publish it. And what I say is, and then you do not publish it, not ever, unless something else happens way in the future. But you don't publish it. This is wonderful. You don't have one application here, and if you look into app, in fact, this is totally normal except for this annoyance of seeing this namespace app everywhere. But if we go through this, there is nothing surprising here. Again, yes, an engine. So we use mountable engines, as we'll see in a second. But so everything is namespaced with the name of the engine. Namespace controller, pretty normal, predictions controller, teams as a welcome page. And then game again. Namespace, so yes. If we find the migration, that's also an interesting bit, the migration for this team, obviously it's called app teams because also the tables are namespaced to the component. But as you can see, the migration lives inside of the engine. And you may have seen for what is it, device, I think, has a helper to install a couple tables. And many engines that you import as kind of external dependencies will have this rake task, which is a rake engine name migrations installed or something like that, which installs the applications into the main application. Now, we didn't mention earlier that in DB, there are no migrations. And there's a little trick you have to do to make this work. And that is a through an initializer in here, which append migrations, it detects when it gets loaded into a Rails application. And in that case, it takes its own migrations, so the apps migrations, and adds them to the main apps migrations. That way you don't have to copy them, and they just get run. That's why when we run rake, DB drew up. So again, you saw the main app doesn't have migrations, but when I run migrations, they're just there, because the app kind of hooks them in. See, I was going to have a later slide that says app is the worst component name you could ever come up with, but I've already confused myself three times. So app is clearly the worst component name you could ever come up with. I'll have more of those later. Okay, let me check if I've talked about all the things. Other questions, people are concerned about me doing this. So no app folder, gems loaded in the path that we saw that is an engine. It was a mountable engine, I mentioned that. We saw the routes migrations. Non-published gems are funky. This is one thing you will find annoying, and it is truly annoying. When bundler pulls in a gem, it looks at the gem spec of that gem, which is why it's important that you always list all the dependencies of that gem, and I would suggest you do both runtime and development dependencies. You list them fully in the gem spec. Now you can still give gems gem files. That's used for local development. And so we're locally developing, and I happen to check out this true skill, which is the prediction algorithm this thing is using. In fact, it's a ranking algorithm. So it's using a git version because the published version is broken, it's unmaintained, so I'm using this. Why is this funky? Well, when bundler loads this gem, it looks at the gem spec, and it says true skill, and it's going to go to RubyGems and download any version of true skill because, in fact, I didn't even give a version. To prevent it from doing that, I need to restate that line in the main app. And in fact, up the transit dependency path, I need to restate such an unpublished gem should I have it. That's an annoyance. It is, unfortunately, I looked into bundler. I think it's not possible to change that in an easy way where we would kind of not have that problem. You also saw that in the gem spec, every other production dependency has an explicit version, the tilde arrow or not a grater. The reason for that is we don't want untested code in production. It could happen that if you just say, you know, rails tilde arrow 419, that you actually get a higher version. So you can run your tests potentially. You can run your tests in the gem with some version of Rails. And if your application states this dependency slightly differently, you could be running the application in production against a different version of Rails. That's like having untested code because you're testing it with some dependency and you're running it with some other dependency. You lock the dependencies down so you're ensuring that you force the app to have this version of Rails. This is a lot of stuff. And there's one more thing I wanted to talk about. So I created this test file here which kind of wraps the RSpec test that this engine has. And the reason for that is that I don't want to have to worry about changing my build script, the thing you ran at first, when I add more components. I want to be easily able to refactor. And this build script just finds all the test.sh files under this application and runs them. So as you notice, there is a test.sh right next to this which runs the test for the main app. What you probably also notice when you ran it is there are a lot of tests in the engine app and there are very few tests in the main application. The reason for that is I highly recommend that you run, that you create your tests local to the code so that you can in fact think about those things as components and they're independent in a certain way. And so you have to think about what is the contribution of some component that it has, needs to have tests for. And there's only four tests in this main application because really the only thing it does is this. And so the tests are in fact, you could say, that's a little bit much because it's testing that the structure inside of app, right, teams, games, and predictions are not a feature of the application. They're a feature of that component. And you can kind of go, give me a beer. I'll discuss that more. But this kind of test just high level that the glue code works. That's the intent, right? Test that the glue code works if your job is to glue, test whatever you're implementing if that's your job. All right. So that's all you need to know about components. So now we're going to make some. But which one? So I showed you the app. I showed you a couple of the files. You have them in front of you. Please look through them. What should we extract first? So right now it looks like this. But do you see anything in terms of structure that might be in this application? Sorry? The prediction engine, yes. Thank you. Did I show you my slides? So the prediction engine is one suggestion. Any other suggestions? Is there no other suggestions? I couldn't quite see who that was. Why the prediction engine? It feels natural to extract. I think that's, yes, it does to me too. If you dig why does it feel natural to extract prediction? Yes? Yeah. I totally agree. So games and teams are tightly coupled. In fact, if we look at game, if you open game, you see that game has associations to the teams. The prediction engine is kind of a layer on top of that. And if you look at prediction, open the predictor. The predictor has, gets things, right, that are incidentally called teams and games. But if you look at what it cares about, the predictor doesn't care that teams have names. The only thing it cares about is that teams have IDs. The only thing it cares about for games is that two IDs have a certain outcome in the thing that is called games. So it looks like that, at least when you look at predictor, it doesn't care at all about teams and games. And so let's extract predictor. What I'm saying by that is let's try to extract predictor and prediction. If you look at prediction, that's kind of the result, I think, anyways, of that prediction. So extract the domain gem. If you watch the Uncle Bob talk where he rants about having lost so many years in architecture, where he says that we have this problem where we bind our application to this weird delivery mechanism that happens to HTTP and this library that does that well, which is Rails. If you think about this app, and yes, it's tiny, and I'll get off my soapbox in a second, this app is tiny. You can see that the left side, teams and games, is kind of crud. That sets things up. What we're really changing the world with, the Uber of predictions here is this right side where you put in two games and it tells you who's going to win. That's going to make you a millionaire if this thing actually works. So that's its contribution. That's our domain. So it feels natural. It should feel natural. And if we extract it, and if I just am right about this, what we just kind of discussed, namely it might not have that much to do with Rails because it doesn't even care about teams and games being active record, then maybe we can make this thing not know about Rails. And we can't. All right. So let's do that. And by that I mean you do that. So the idea is we have one component already. It's app. We want to create a second component that's going to be predictor. And you're going to do it. This is terrifying, by the way. I have no idea if this works. So because we said this is likely not caring about Rails, do these first two lines. So what they do is we're creating a gem, a straight up gem, not an engine. And you'll see it generates a couple files for you. And then I want you to delete git because we are keeping it in this repository and we just want, you know, mother git to kind of take over. And then there is just a few steps, albeit very gnarly steps that we have to do to extract predictor into this. So it felt natural to think about prediction separately, move the classes, so predictor and prediction, move the tests. There's I think only a predictor test in there. Now these things were namespaced as app, right? We're taking it out of the app component. We're putting it into a predictor component. So we probably need to rename space, everything that's in there. Then we have to fix tests. Then we have to obviously, we're made this a gem now, so we have to make sure that it gets properly loaded in the component because app depends on this thing. And then hopefully we can fix the tests in the engine and we can run it, we can check that the main application still works. Just so I'm not kind of doing something that most of the people are not interested in, is anyone actually interested in doing this right now? Then raise your hand. Okay, so just go, I'll walk around and see how this is going. And it would be really interesting if you have, if you can't run your tests or if you find out what you need to do to make the tests in predictor run. Shout out. I'll come by and we'll discuss. So what you should do is you should start by creating this gem and making sure you can run tests in there. When you move the files in, it'll blow up all over the place. Keep running the tests and fix the first thing that breaks these things and keep going. And as soon as you have the tests inside of this new component working, go one step out. What's one step out for this one? Well, the app component depends on the predictor component. So the next step out is run the tests of the app component. And only when you've done that, run the tests for the main app. Rails Engine New Mountable is that off limits? Rails Engine New Mountable is that off limits? For this one, yes. Just create the straight up gem. So are the tests running for anyone? And if so, what did they do for it? Someone here had a bundle install error for PG. Right. It was going pretty good. Are you going into the component that you just created? So go into predictor. I can't even get the app to run from source. This app does not depend on PG, so I'm kind of concerned that you seem to have a PG dependency. There you go. Where was it? In the production gem file, there's Postgres. If you have trouble with Postgres, delete that. So anyone else having a different problem than Postgres? Yes. At least for me, if I do bundle gem predictor, it'll create an RSpec. Oh, okay. Oh, yes. I guess it says there, answer the questions. I did that again this morning and it didn't ask me questions. I would take RSpec because the rest of the app is tested with RSpec. Yes. Which folder do you want predictor to go into? Which folder do I want predictor to go into? That's a great question. So these things are gems and the only thing we care about is that we can load these components. You saw the path command. You saw the path option on this gem. It doesn't matter. If you put it wherever you want, you just change that path to be correct and everything else is going to work. I already created that components folder. So the next command might be for you to move predictor into the components folder. It will also have benefits when we do the next one. Is anyone at least kind of getting an RSpec test output? Is anyone getting read or is everyone still blowing up? Yeah, okay. So if you've created the gem, you can actually run RSpec right away, which is great because it's going to tell you that it's passing because you have a sample test in there. I think that is like expect true to be true. So now if you're in that state, take the predictor and the prediction, put them into the predictor gem, move its tests into and try to run that. That's going to blow up. Yes. So in the prediction, predictor gem, am I wrong? When you copy the predictor spec and you created the bundle correctly, it will ask you probably to overwrite the existing spec file in there. So then you'll get rid of that sample test and you should see breaking tests. I'm not sure what to do next. I finished building it. Sorry about that. Oh, no. It isn't really timey. So you go into predictor. So you have no tests in here. So take whatever IDE you use and take the predictor test out of the app component and move it in here and take the files, predictor and prediction out of the app component and move them in here and then run the test again. Yes. Here's someone else's lost. So this is working out great. So we just do those two first two lines. Okay. Cool. All right. Keep going if you're going. Yes. I think if you're going to do it on the screen. So you're hacking away and yes, you're realizing it's kind of gnarly. So let me flail in front of you. And by the way, I always use RubyMind and I think it's pretty good at this. All right. So I said we remove git from predictor. If you're still going strong and you have a feeling you're getting there, please keep going. I'm just going to try to do the same. All right. We're taking the predictor. I'm moving it into components. If I now look at my application in RubyMind, I have predictor in here. Now predictor doesn't do anything. Right? It's an empty gem. But it was nice enough and again, I'm sorry, you have a different version of this bundle so it asks you a question. For some reason it didn't ask me a question. So we want to take, extract just the prediction. So we're going to look for models. Here's the predictor spec. Moving that into the spec here, just overwriting what was there. And going into models and taking prediction and predictor out and moving them into the namespace predictor inside of the gem. Okay? Is that clear? Stop me at any time. You realize you can just start talking. So if we now go into this gem and run specs, it's blowing up. The error I'm getting is unalitialized constant app. And that makes a lot of sense because we just took something out of app and moved it into the module predictor, which is what the gem has. And obviously that doesn't exist. This particular error comes out of the predictor spec. If you want to do this test driven, you just fix this one thing here and run the test again and it's going to fail on some different line. It doesn't know predictor predictor. If you don't want to do this test driven, you can kind of just start searching for app and replace it with predictor. So let me do that here. So my tests kind of look okay, right? There is one caveat. Sorry. That's garbage. No, there's many caveats. One particular one right now is that predictor being the class name is also the module name which confuses Ruby. So in this particular case, and there's one more, you should use the colon, colon in front of predictor. All right, let's go to the prediction. This is where it's going wrong. Or in fact, if you run the tests right now, it's going wrong somewhere else. It doesn't know predictor. Why is that? Gems don't auto load stuff. So we have to actually require those two files. So I like to put my local requires here. Oops. Require relative predict. This is to the prediction. The predictor, if I run the tests now, still same problem because, well, this is still the wrong namespace. Using the namespace in these two files, tests run and they're read. Does anyone so far get to this stage? I have no idea how complicated this is if you have never done it. Okay, did these steps make sense? Do I need to go over one of them again? Okay. All right, if you try, I mean, try to catch up. The thing I didn't meant, yes. Why is it not a mountable engine? Because we don't have to make it a mountable engine. Thank you for asking us why is it not a mountable engine? Why is it just a gem? The question should be the other way around. Why Stefan, have you been so nice to me that you didn't give me all the crap that is connected to an engine? Why can I live in this wonderful world that is only a gem? Because only I screw up my world engine. So many dependencies come with an engine, don't add it if you don't have to. We had this discussion earlier that the predictor feels like it doesn't need to know about rails. It seems to care only about a few things about these things called teams and things of games. So we're just trying right now to make this thing just a gem and not include everything of rails. If you have a lot of gems components in your app and they don't depend on rails, do you know how easy it is to update rails? One of your app cares about rails, so it's going to be really easy to update it. There were two questions. Yes? Yes, great question. So are we moving everything of predictions into this new component? When the comment was made over here that the prediction engine feels like it is a separate part. I did clarify that I considered that to be the thing that drives the prediction. So what I want you to extract and what I just did extract were only predictor and prediction, not prediction controller and not its views. We'll get there later. Also, if you get lost at any point in time, look at the history of this project. You'll find a branch called Workshop. It has 10 steps. We're trying to do step one. If you just want to kind of see what it will look like, check that out while we're going over the rest. Yeah, sorry I didn't clarify that. I want to do that later. So this is only kind of, as I said, the core of this application is to predict the outcome of a game. The delivery mechanism in Uncle Bob's words is that it has a controller and you can see it. So let's separate those two first. It's kind of the intent of this first refactor. Yeah? Yes? Are we expected to put our spec into the game? Yes, are we expected to put our spec into the dependencies of this component? Yes, make it a development dependency in that gem spec, which will make sure that you have when you try to build just the tests of that component that you'll be able to access our spec. What is your wire load? Oh, sorry. So again, in a gem, you don't get the rails auto-loading and you, since Ruby 1.9 something, you can use require relative. So you load those two files to get access to predictor and prediction. Okay, is there at least someone who's making more headway now? Yes? So you have your own lid? Ah, good question. Yes, another pitfall. So you can see that the folder structure between an engine and a gem is not quite the same. Engines are the core of every rails application and they tend to, it's automatically supported that you put stuff into app and in the module they're under. Models on the other hand are libraries so they historically put all their code into lib. Now you can make the reverts work with either one, but yes, the easiest thing is to take it out of app models, app prediction and predictor, and put it into lib predictor in the gem. All right, so with these moves that I did, so I had to slide up earlier, I moved the two classes, I moved the tests, I made sure RSpec was there, I ran the tests, I changed a couple of module names from app to predictor. By the way, people will say that I'm doing Java when I do this kind of stuff and then you could answer yes, and in Java this would be easy because with static typing it's much easier to rename all these modules, so that's why we have to do search and replace and don't have those niceties. But we have a different problem right now here up on the screen. So just these renames led to the problem that we don't have access to this create team. This is something in the test. If you go find the source to that thing, I'm not a huge fan of Factory Girl and those other object mother libraries. I find it such an easy pattern that I kind of always write it myself. So create team is a short method that saves a team that we've newed up. But as you can see here, so first off, the gem didn't find this method. So we could add it, but then we would have the problem that you see in line six because that's actually creating an app team. And I was saying earlier, when you create components, you have arrows and they point in one direction, but they don't point back. And remember, we're extracting the predictor. App is going to depend on it. Predictor will not have access to any of the things that are in app, and it shouldn't. But we also said that prediction doesn't really care about anything about team and game. Let's look at what it exactly cares about. What it cares about, so you give it teams when it initializes. So here's how it learns. You initialize it and you give it a bunch of teams. That's kind of a setup as a lookup, as you can see here. It kind of makes a hash. In fact, it's really crappy code. So it cares about the team ID. That's how it does the lookup. And then it stores the team in line six, but we don't know if it does anything with it. And then if you look at learn, it takes games and it cares about the IDs of those teams again. So it cares about the IDs. And then it cares about the winning team. This is line 17. Here, this stuff. It cares about who wins. And then it cares about the ID when it predicts, and that's pretty much it. And it has some internal stuff, but it doesn't care about these things being active record. So I suggest that this, that we can change this test to be something like, oops, to be something like this. As I said, I think what the test cares about is that there is an ID on this team. So why don't I give it something? This is duck-tapping. Why don't I give it something that has an ID? Does anyone fundamentally disagree with this? Cool. So there is, of course, more. Here is a new game. So I'm going to do the same thing for the game. And in fact, there's a couple more down here. Let me throw that in there and just run the tests again and then go into the spec helper and require Ostrut. OK, error changed once again because I can't spell. All right. So and to your question, right, if you can do something like this where you now got rid of the entire dependency that is active record and you never have to worry about instantiating a database, again, predictor doesn't have a database, you don't need to run migrations, nothing gets stored, these tests are going to be really, really fast just from this one change. So you could argue, I asked about if someone fundamentally disagrees with this because you could argue, but now you're just assuming that OpenStruct, this thing, is the same as a game or a team. And that's not really safe. Well we have tests around it, right? App tests what it's doing with these teams and games. So that's one argument for it. And if you're really uncomfortable with it, look up bounded context objects. What we could do instead of this is we could create a prediction game class and a prediction team class that get newed up with something that has an ID and with something that can kind of show if you win or lose. And you use these two things and new those two things up in the test and the app will change its usage of this predictor to create these bounded context objects and passes them in to when it uses the predictor. So that's an alternative you can use if you like to see more classes. The error we now have is that we actually had a dependency in app that we currently didn't give predictor and that was the actual prediction engine, the external one that we currently use. So it's also this nasty thing here. So as I said, we can't remove this dependency line from app because it is not a published gem. So I'm going to throw this into the gem file. But as we said earlier, we also need to have this in the gem spec. This looks really nasty, but there are no runtime dependencies in this gem, but now there are, namely this true skill thing. Let's run the test again. Nothing changes. Gems don't auto load things. We need to add this true skill thing. And in fact, we can probably also remove it from app which should be loading it in its gem file. So if you've never worked with gems, all of this is really, really weird. If you've done gems, this is all just normal kind of gem behavior. We're just refactoring a little bit faster over these gems. So I can remove the require from app because app, in fact, can also remove its dependency on true skill. True skill is now an implementation detail to the predictor engine. We can, yes, the others will depend on it transitively, but they won't know about it directly. And once I add the require up here, I have some app somewhere. So let's see where that is. Line 28 in predictor. There's app. So app prediction, obviously wrong. This is that moment where these two columns here at the front are needed because of me naming the module predictor and the class as new predictor. And there you go. Has anyone got these green now? Yeah. Oh, I put the require for what? Oh, predictor. The require for the external prediction engine, I put into the libraries, I don't know what you call this file, actually. This is kind of the file that you require that gets required first when you require this gem. That's where I put the require. I don't like requires kind of splattered out all across files. So I put them, I kind of up front load everything. Okay. Any questions up to this point? We basically just extracted a gem. Yes? Yeah, predictor gem spec. Oh, thank you. This is not going to work. Correct. Yeah, that's funny because we didn't bundle. So yes, that was, you know, my gem spec didn't compile parse run, whatever. The test passed because we didn't bundle again. How do we bundle, it would have blown up. Now that we're bundling, we're pulling in the dependencies. And so one thing I suggest we do to kind of clean things up is we take this test here, I'm just going to copy it because I'm lazy. We dump it into the gem. And now obviously we need to adapt it because we're not going to, we're going to maybe do bundle install. Yeah, that would be nice. But we don't want to do anything database. So if I now come back out to the main application and run build, what did I say? Test outward. So we're going to the app component and running the tests here. They're not going to pass because obviously we never renamed predictor in the app component. So it's going to be in the controller. We could test draft this or just find all the places real quick. So in prediction controller, we're referring to predictor that's no longer found because it is now predictor-predictor. That's not the only thing that's going on because we have a new dependency as app. And that is the predictor. So we need to add it to app the component. We need to add predictor. And we need to also make sure that the app component knows that the predictor component is in the path. And I was showing you earlier that you can do that with path as an option. There is a nicer way to do that, which I suggest you use all the time, which is do block. So you can say path do, and I give it the path where it will find components. And I say gem predictor. And that still is not enough because gems don't auto load anything. So I need to require the predictor in the app component. We'll see if I got it right. No, I didn't because there's tests that are also using app predictor. So I have one test failing because it still refers to app prediction. And that is right about here. So we're working our way outward, right? Move everything into the gem, run the tests. They pass inside of the gem. Move one level out. Make sure we require it. Make sure we rename what needs to be renamed. Run the tests again. And if we did it right, because this is otherwise a truer factor, and the tests on the app level are high level enough, I think I can run the build and all of the tests we've passed. And you see it just added the tests for the gem. It added it right there. Whoops. What happened here? So this is where, yes? What was the command that you used to eliminate the test for the gem? The test status age for the gem file? Sorry for the new predictor gem. I just copied it from app and removed everything that referred to migrations. Yeah, it's the same test status age because we're just using RSpec, really. But sometimes it's helpful when you have RSpec and you have maybe feature tests, you run slightly differently, and then you have Jasmine or something that tests front end. So you have multiple things, so I wrap them all in this test status age. This is dangerous. What happened here just now is that we installed predictor 2.3.0. So this might be the most problematic thing about doing component-based rails. Bundler is all set up to pull everything from RubyGems. So if you forget to tell the application that what you're looking at and what you're wanting to use is a local gem and you're not pointing to it, because remember, I never went into the main application's gem file and informed it about the fact that we now also had a predictor. So it just went out to RubyGems and funny enough because this name is generic enough, it found a predictor and we've now pulled that into our app. And in fact, you would not find a single breaking test. Actually, you would. You would. There is a test breaking. Whew, happy about that. So there is a test breaking because that predictor happens to not define the class predictor. Because it doesn't do that, the main app, which I had said, oh, I'm covering this glue code here when you look at AppSpec. If you look at the AppSpec in the features of the main app, it actually does test a little bit more than the glue and this is the reason why I added that because I knew I was going to miss it today. So it's testing a little bit more to verify that we have the right kind of predictor. So to fix this, we can do this, but I just showed you the path block. So it's much nicer to give it the block than you can remove these paths. We're going to uninstall the predictor and we can hopefully bundle. And we see here it takes apps from our components and here it takes predictor from our components. And in fact, as I said, with the path, you don't need to specify transitive gem dependencies. So we can just bundle this and it will still pull predictor from the right place. How long is this session? Does anyone know? 520. Okay. Pretty much. Cool. Questions? This one? I removed the predictor gem from the main gem file because the path do block is smart and it knows to look for any transitive dependencies in that path. So we can remove this because the main app doesn't depend on predictor. The main app depends on the app and that depends on predictor. So you wouldn't specify, right, if a rail depends on active record, you don't specify active record. You just specify rails. And the same thing with the do block is possible for components. All right, so path do require the gems. There's no kind of, no changes to the production code. I think this is wrong here. Oh, sorry. In the module app, the only production change we have is that we need to refer to the right predictor. Right? Open struct this little trick which works in this case might not work in yours, but getting rid of dependencies when you don't need them is a great idea. All right, so I don't know if anyone feels this is not relevant for me at all. I hope you will see it is. If you have good tests, so you can refactor, right, if you have unit tests, functional tests, if you have a couple layers of tests, you will be able to perform such refactoring. I've done it in code bases that were hundreds of thousands of lines long and you just find a smaller component that you can tear apart and you just tear it apart. And when you've done it a couple times, it will feel more natural. I've seen teams that start having templates for their components because they're kind of always the same, so they kind of use the template and go off of that. We keep running the tests, right? You saw me doing this. I didn't know what was going to happen. You keep running the tests and ask the code what's wrong. You run them inside out because if you don't run them inside out, you're just going to get so much test problems that you're just going to be overwhelmed. Start with the gem. Go to the things that depend on it. So go in the reverse direction of the dependency tree, right? Go up the dependency tree for your tests. And then where you can remove dependencies. We didn't need Rails for this prediction. We shouldn't have it. So the predictor is now completely free of it. If you want to update, in fact, we could extract predictor, publish it to a gem server, and use it in multiple applications, and they would indeed not care about Rails. It's also kind of this single responsibility principle on a higher level, right? The least amount of reasons to change. When we can achieve that, well, if we don't have Rails, there's a lot less reasons to change. So what else is in this application? Is there more structure in this application? And if you're feeling, oh, this is all very academic or very labsy, yes, because it's a small example. But you can apply the same thing to larger applications. There is more structure in this application. If you look at what is actually going on, you see that the team controller doesn't need to know that games exist. It crud's teams. Anyone disagreeing? Does it need to know that games exist? Because it doesn't. This application, it does because this blue box is currently the truth, right? It's not these little boxes in there. The game UI needs to save teams in a way because it's storing their relationship as it relates to games. The prediction UI kind of needs both of them, right? Because it needs to show you the teams. It needs to access the games to make the prediction. The predictor, however, is now kind of out of this game. Given the time that it took to do these refactoring, we'll do the other one. The other one's just up here. We'll check out code and take a look at it. So the mantra is if it doesn't have to be connected, if it doesn't have to be together, it shouldn't be. If you don't have to entangle it, don't entangle it. In fact, that's my opinion is whenever you have code that can't touch each other, it will. There were a couple of people who raised their hands and said, I have very large Rails app. When you look at your app and you go into the lib folder, have you ever checked whether you can delete the Rails app and delete all the tests that are not pertinent to the lib folder and run the tests for the lib folder? Do you think that's going to work? It's not going to work. The reason it's not working is because your lib folder's entangled with your application. If you truly wanted to have a library, you put it into some component like this predictor and force that the tests run on their own. Even if you have no active record or whatever in there, try it. You will have, I don't know, hash within different access in there or something and suddenly you have a dependency on Rails. What can be separate should be separate. You also saw something that was very interesting. It's freaking painful to rip these things apart. If you can create something new and start immediately with a new component, you don't have to go through this pain. It's super easy to take two components and smash them together because all you do is search for that one namespace in the one and merge it all into one namespace. It's really, really easy. Trust me. If you're doing components, it's super easy to do separately two components right off the bat. It's really hard to extract them like we just did, which is also why that took so much longer. But I said this wasn't going to work at the outset. If we wanted to make these internal arrows that are kind of put in there, if we wanted to make those happen, we needed to make every one of these things into a component. This is where truly, I believe the predictor is a finer factor and I would probably do it in many applications these days. The next ones I would probably not do because they're a little bit small. But you could argue there might be such a thing as a team admin. The only thing they do is scrape the newspapers and admin teams. I actually want to give them a separate UI. How cool would it be if this here were one application and it didn't know about all the other stuff? Sorry for you guys on the left. If teams UI and teams were separate and didn't have to know about the other stuff, less reasons to change. This dependency, what if we could actually make this happen? We can. When we try to do this and we push persistence down, which is the first step, we take teams and games and move them out of the app component. Like so, this is where we will have to use an engine. Rails plug in new. I realize that it takes too much time. Let me just check out a version of the code that has both teams and games extracted into components. Maybe this is the reason it takes so long. Yes. If you check out tag number three, you should see four components inside of components. If we look at games, let's just take one of the two and look at them. They're structurally very, very similar. The only thing they have is a models folder. The only thing that these engines need are the models. I said I wanted to extract the persistence down. This game is otherwise unchanged. Same stuff. There was a reference to the team's class name here. That needed to be changed to point to the team's module name now. Then we do have the migrations in this engine. There's another cool side effect if you leave the migrations in the engines, as I said earlier. You can move them around. This migration is just the moved file that was earlier in the app component. It's just moved over here. To be able to do that, you should adopt the rule of one migration per table. If you have multiple active record models, you're not sure whether they're going to end up potentially in two different components. You need their migration separate so that you don't have to go through a whole hassle of migrations back and forth to separate out the migrations. You don't touch the old migration. You can't. Remember, in production, this code is already running. You just add a second migration that does the rename. In fact, if we look at teams, it has exactly the same thing in its migrations. Make one migration per table, and this will always be possible no matter how complex your app gets. What changes with engines is that we get, well, the engine code. It uses isolate namespace so that anything inside of games is in the module games. The lines 5 to 9 are the same that we had earlier. They will show up in every engine that adds migrations to the application. I also, I had showed you before that I don't like to use Factory Girl, and I showed you my hand rolled object creation method. When I extract teams and games, in fact, how are the controllers that are now in a different gem, how are they going to new up teams and games if they need them for their tests? Those object creation methods shouldn't live in those controllers because that's not their domain. But you've seen this in other gems. Why not expose a games test helper? The games test helper has an object creation method inside of games, which only can create games, right, new and create game. If you look into app and its specs in the spec helper, here in line 10 and 11, sorry, 11 and 12, it requires the test helpers from teams and games. So in effect, the games gem and the teams gem, they were nice enough to provide their own test helpers. Something that is good libraries will always provide you something like that. So here we're doing it for ourselves. So let's see what else we need to talk about for these. So the process is otherwise very similar. So you're creating an engine and these BGVSJT means skip bundler, skip git, skip, what's V, skip views, skip views, skip sprockets, skip JavaScript and skip test unit. I'm skipping basically everything because this engine only cares about models. And I believe if I remember correctly, this engine doesn't care about Rails. So I actually made it dependent on Active Record because it doesn't need Active Support, Active, all the other Active things, all the other adequate things either. So only thing it gets is Active Record. And as few dependencies as you can have, create them. All right. There is a dummy app that gets created with engines. That dummy app makes people say, this is a really bad approach because the dummy app is so large. And that's true. The dummy app is very large and it gets auto created. There are people who try to avoid it. There's a gem, I think, that gets rid of this dummy app. You can see that this dummy app is massive. What it is, you can see it's in spec dummy. And the spec helper, in fact, does not load the engine. It loads the dummy app's Rails app, which in turn has a requirement onto the engine. It's just the way you test an engine. For the tests, you put it into a skeletal Rails app so you can run tests against it. You can get rid of it. I often don't do it because the dummy app never hurts you. It just sits around and it's just obnoxiously big. But that's really its only problem. I already showed you the renames that we had to do. I talked about the test helpers and how they're used, how they're exposed by the engine that has the content and exposed to other parts of the application that need to use those parts. So if you have checked out, tag three, then you have this structure. In fact, so what's in the blue box is only obviously one thing, but conceptually we're still thinking there is more there. So because we couldn't do this in practice, there's two ways to do these engine extractions. Again, it's nicer if you start creating the fresh best thing. Decide whether this engine is going to be very different or very similar. If it's very similar, instead of going through the motions of creating a new plugin, if you already have a component, it might be easier to just duplicate the component, delete anything you don't need from the new component and the old component and start renaming in the new component. It's sometimes just easier. It's basically kind of a blue print approach where you just kind of, this is all the stuff I need anyways. Don't mess with migrations. You need to move them over and then you add new migrations so that you can change the names as appropriate. The test helper approach I think is a really nice one. And also, had I shown this, I would have tripped up on trying to refactor away from this name app the entire time. So let me get on the soapbox again for a second. If you feel like you need to name your engine Common, Base, Miscellaneous, General, Lib, or Company's name, anything like that, just take everything, croft random, don't care, don't know, or duh. It's about as good. Sorry, these are proper module names. Give anything you extract into components. The most specific name you can think of, a good name, one that will start a good conversation. Continue to refactor it. Once you're in the habit of doing this, it's not that hard to refactor these things. And then continue to talk about it. I was once on a project that was one of the coolest projects I was ever on and it ended up with 40 components. And there was continuously one pair, we were, I think, four pairs, and one pair was continuously working on just refactoring stuff. And we still had one of the highest velocities I've ever, like it felt like one of the fastest projects I've ever been on. Because the refactoring were good for the health of the code base and we were able to create a lot of code, despite, or rather maybe because we did so many refactoring and kept these concepts clean. And this section, sorry, I skipped over a couple, app is a terrible component name. It's time we get rid of it. So if you check out number four, this is the slide I'm looking for, it's renaming app to WebUI. I know it doesn't, it's not a really good name, right, it's still Web, what the hell does that mean? At least it captures, however, that this thing is concerned with the delivery mechanism, like again this Uncle Bob thing. This is the UI as delivered by the Web and it contains the controllers and the views to do so. So if you check this out, you'll see we're not calling it WebUI and that actually makes a lot more sense than you could stop right there. But again, I said we'd keep going and we already found TeamUI, GameUI, and PredictionUI. Without looking at the code, does anyone see a reason why we wouldn't be able to just kind of vertically split TeamUI, GameUI, and PredictionUI out of WebUI? Yeah? Say again? Which ones though? So the question was, aren't there pages that need to reference multiple? Of what and which ones? So what is true about your comment is that the pages we're looking at contain a reference to Teams, top left, games, center, and predictions. So these things are kind of connected. I am. How did I do that? So, they're somehow tied. Also there's something even more obvious. They all share the same UI. They share the same application layout. So you can see that doesn't change. If you look at the source code, you cannot currently extract these things from each other because essentially because this file exists in the app component. And if we would try to extract, say, GamesUI out of the WebUI, how would it get to the layout? It couldn't. So before you can extract any of those UIs into separate ones, you have to extract layout and style. This is a very common place where people will start calling that base styles or something like that, layout, just very generic. Don't do it. Try to be more specific. I'm not much better in this case. I called it WebStyle. If we check that out, it's a really easy refactor. WebStyle only contains the application layout, a couple assets, CSS, JavaScript, and that kind of stuff. And it doesn't contain tests. Check out five. All right. We've gone over the basics. Now WebStyle contains only assets and views. So there's that image, that logo that we've seen. There's application.js, application.css. And here's application.html. So now we have an external component that if we split something off of app, we can depend on because in fact app already depends on WebStyle itself. Oh, sorry. This is just a, I meant WebUI. So WebUI depends on WebStyle and the big change there is that in the controllers, sorry, in the application controller, we're saying please use the layout from the WebStyle component. All right. So and to go into WebStyle again, there's only the engine code that loads it. There are the assets and the views, and there are the dependencies that are Rails, use slim here and jQuery. Right. Very small engine. And it's literally just moving out, renaming all the folders that you're moving from WebUI to WebStyle so that everything inside of WebStyle obviously needs WebStyle here, here, here, and so forth and not WebUI. And the dependency graph looks like this. Now that we have that extraction, now we can take prediction UI and move it out of app, sorry, WebUI, and it can depend on anything it needs because it doesn't depend on the other controllers. And that, so this was WebStyle. So we have remove everything but assets and layouts, remove assets from obviously the WebUI. It's always the same pattern. Take whatever you need. Either you move it out or you duplicate it and then you delete it in the old place. In this case, no tests to write. What is important to verify is if you got all the loading right because one thing that I just kind of showed for a second, obviously the application layout is now referring to the WebStyle application, JS and CSS. All right. So in the spirit of ripping all of this apart, we can in fact do this and move all of these to be separate because they live next, they can live next to each other because, oh, that's what I wanted to show you. The trick that has happened for this checkout is this here. So WebStyle uses these paths and that will only work in the main application. In fact, I don't even know that I ran the tests for this because that looks to me like it won't work. We should check it out later and verify that this works. So the problem is when you extract WebStyle, it has this navigation section in the layout that suddenly is no longer in its dependency path, right? WebStyle contains the template, but it doesn't know about Team UI, Game UI, and prediction UI existing. So these couple lines here, they break the dependency path. They should only be dependencies down and this thing makes assumptions about the world that it shouldn't. Let me jump to the very end of the refactorings. How did everything we could do 10 refactors? So at the very end of these refactorings, there's a very simple way to fix this and it's a very important one to know about for components. If you cannot depend on something, how do you get information about it? This is the final state of the refactorings. It turns out when you split these three things off, there's actually one thing left in the first page you ever get to, which is just an empty page, but you could split it off too. So that's what we've said here. So if you check out the workshop branch, you will find Team UI, Game UI, Prediction UI, and Welcome UI, and all these other things. And the problem of how to glue together this navigation menu, you solve with what is essentially dependency injection. So the WebStyle has the application layout, but it doesn't know what things are going to be part of the app that need to be shown. So in the main application, I just set a config variable here, main nav, and it gets a nav entry from everything that wants to show itself, if you will, in the nav. If you look in here, this is just on the module. It's a name and it uses the route helper to find one path. This is for Teams. Again, the config variable gets set in the main application in this case, and WebStyles... Where does it make use of that? Right here. So WebStyles just goes over the configuration and shows the links. Now WebStyles here says rails.application.config. So WebStyles expects and will actually blow up if the main application does not define a config called main nav. And that's why I call it dependency injection is because you're configuring something over here and it's just expected down here. So it's not really injected, but it's expected. So you should write a test around verifying that this works. So the way to test WebStyle now is to create an empty dummy app, but make sure that this configuration variable gets to something so you can test in isolation that the navigation construct at least works. And when you pull it into the main app, you should test that everything still pulls together correctly. And I think the app spec never really changed for this application. So it still navigates to all those pieces. So throughout the entire process, this test made sure that the entire application worked as expected. When you have done this, your test suite looks a lot different from what a normal test suite looks like. It's going to continuously create migrations because there's a ton of things that need to create migrations. All these separate engines, you know the dependency path now or the dependency tree, everyone that is above teams and games needs to run migrations. But they all just run a couple tests, and those tests typically run pretty fast. Incidentally, when you run this test suite on Travis, it's very nice because you can do this. You can tell Travis, you can use the gem file option that you have in Travis to point it to every single gem file of every single component that you have, and it will just run all of your test suites in parallel. So this is a build of this state that we're looking at right now, this checkout of workshop for this application with Travis right now. It has a build for every single one of these components, and we can see that for some reason WelcomeUI, that's hilarious because WelcomeUI doesn't have any tests, takes five and a half minutes, but it's because of this Travis problem with loading gems, so you have to do a retry and probably get stuck there. So all the test suites themselves are very small. Now we have to kind of, I changed this whole workshop up a little bit because the actual refactoring takes too long, but what in your application are these parts? I want to talk about that for a little bit. So if anyone kind of would like to share, I have this or that, right? Please go ahead, but I'm just going to kind of make stuff up that I've seen time and time again. If you have, in fact I was talking to someone over lunch today, so he was looking at TV channels and content for it, I think. What he will have is someone who can admin people so that those people can admin channels and maybe other people that can admin particular shows, and then there is logged out users who can see this content. Super admins, channel admins, show admins, and logged out users. Those are perfect verticals. The user interface, which is the website, or whatever you want to call it, will probably look totally different than those admin sections. But those admin sections could also be separate from each other, so you can make verticals out of them. Similar to what we have here, Team UI, Game UI, I tried to say this earlier, what if there is a person, an intern that just inputs all the teams? They may never touch the games because that's way too complex for them. So it's a separate UI. So in your app, these things will be larger. They will indeed be something like a channel, or they will be accounts, or tenants, or whatever. Those UIs, you can split off vertically with components. And then you'll find, as soon as you try to do that, just like we found here, that you find common things, like teams and games. They're used by multiple of these verticals. So first, you kind of think about your application. How can I split this thing up into vertical pieces? And then you realize you have something common, and then you need to slice it horizontally, because you need to pull out what is common, in our case, teams, games, and web style, so that everything that gets vertically split can depend on that if it needs to. And as you keep doing that, you'll find it's really easy to slice and dice in those things. If you get into the groove, you make more and more of these, and in the end, you can create really large component structures. Yes? I'm wondering what the... That's a very good point. So the argument is, why not leave the web styles in the main app? And you could, in fact, do that. You could create the layout that the engines use, I believe, anyways, it should be possible. You could create that in the main application. In the components, then, you would have a weird situation where you don't actually see what the component is going to get rendered into once correctly gets rendered into the main application. But you're saying, much like the application glues together just the mount points of these engines, it could glue together the UIs of those engines. I think that's a very good point. You could say, the layout belongs there, and then what you would test on the main app layer is that this application layout, in fact, does pull together the components like we would like to pull them together. And if you... You can, in fact, run any of these engines. You can just fire up a Rails server. The dummy Rails app inside of an engine, you can run to see just that component in action. It self-contains, you can run it, and that would look different than the main app because it didn't have access to web style. I think... I don't know. I'd have to try it if it looks nicer, but I think it just might. It's a good idea. Yes, but now you're adding one layer of complexity. So in his product, they have extracted styles into a gem, and they have a constant struggle to keep all the applications using the same styles and keeping that up to date. Obviously that's something I recommend not doing as long as you can because this is still one application. If someone finds this predictor terribly interesting, I might entertain the thought of extracting it and actually publishing it to a gem server. Web style, I would probably never publish to anywhere. Now, if you have multiple apps and they should use the same things, then you start having these conversations. But you see why, even if it was kind of painful to try and do the first refactoring, it took me on stage, nervous, and typing at the wrong angle, 15 minutes to extract a component. Why is that possible? That's only possible because you keep this code in one code base. You don't mess around and muck around with publishing the gem servers, updating gem versions, and pulling down gems and pulling down all these applications and components from different repositories. It's one repository, so it stays one application. Yes. So he has a very large application. Did I get that right? And a couple of engines, and those are all separate repositories. So here's a, I'm a consultant, so here's a free one. Take these repositories for every single engine, and if they're not used in multiple applications, which these large engines typically aren't, download them, put them into a components folder, delete their Git folder, commit it to the main application, add the path, change nothing else, just add a little bit of the test suites as we were doing, and tomorrow you'll just be able to actually manipulate engines within one application. So you can refactor this thing from that engine to that engine, rename this engine over here, and you don't need to check out five things, and when someone new comes onto the job after the first day, they're like, shoot me now, because there's so much stuff you have to remember. That's the first and easiest thing to make you much more productive if you want to split up applications, and I think it's one of the things why we believe Rails applications can't be large, because we do stuff like that. It's completely unnecessary to put these things into different repositories, and I highly recommend you don't do it. Pull them back together, it'll be much easier. And that's just the first thing you can do. As soon as you then find stuff that you want to refactor, you have one code base. You've just seen how easy it is to extract a gem, so you extract a gem, and it won't even affect deployment. That's something I didn't mention. Deployment for an application like this to Heroku, exactly the same. There's no change whatsoever because you have a gem somewhere down there. Heroku completely, transparently deals with this path command as you push. Not a big deal. Yes. Yeah. I'm very much in the expectations of this. You're using Ruby, so good luck. No. So a Rails app is an engine. Rails app is an engine plus essentially what you see in the dummy app. Now you have multiple engines, so you have a bunch more classes. Yes. So you generate a couple more classes. How many classes is that per engine? Five classes. That's the overhead you're loading. You're loading five extra classes. On the flip side, however, we have a dependency graph like this, so we can start playing around with it. I'm trying to see if I can find some samples. Don't look at the company names, please. This is the largest, whoops, the largest component graph I've ever created. It was very, very successful. It looks terrible. That's because you can see how complicated this application is, which every Rails app tries to hide from you. Because this structure is present, you just can't see it and you can't talk about it. Now what you can do with this, if you wanted to deploy your application to this server over here, in fact, this is also this thing where you have multiple admins. What you can do with an application like this is deploy it and you can change your gem file. Your gem file for this has tons of gems in it. You could put a little magic in your gem file and deploy only or load in one context only one of the admins and load only the other admin on the other server and load only the WebUI. It's a very easy way also to get tailored applications. If you need to scale one part of it, just make something where the gem file only loads that part and scale it really big. You don't need to load the rest. I think the performance implications are really small. Then there's also another comment I should make. Enrico Teati wrote a blog post a couple months ago about building that in. Using bundler and environments to basically reduce the footprint so that you support this just through the gem file. I think, yes, there is an overhead to this. To any structure there's overhead because you need to create and maintain the structure. The benefits from that structure will outweigh, I bet you, in most situations their cost. So I'm writing a book about all this. I keep running around for the last couple of years believing that this is the way you should write Rails applications. So I said I would write a book about this. If you go to this link, I'm publishing it as it's in progress. You basically just, the refactoring that you just heard, I hope to be used for the next chapter to show sample and standard extractions. So a gem extraction of a domain, the extraction of the persistence layer, the extraction of or separation of UI components. So if you're interested, this is a coupon to where you get it for 50% off. It would be great if you could support me in that because it's a lot of work and a lot of people think I'm crazy for doing this. So one page that kind of collects most of the information that I know about writing applications like this is this URL. And that's it. That's it, yeah. Thank you.
 Before we start, why are you here?  No, I'm actually really interested in what brings you to this workshop.  Are you like, does anyone here have an app that has, say, a code base of a couple hours  or a test suite of a couple hours, 100,000 lines of code, and they're like, anyone?  There's a couple hands.  This sucks.  I want to change something.  Cool.  Is anyone here writing a lot of Rails apps?  Someone doing consulting and spinning them up really often?  And what's the, okay, cool.  So when I had a three hour proposal, the second half was going to be that really large code  base that we try to tear apart.  In fact, you don't need 90 minutes to tear a large code base.  Part of it's more like months, but it would have been condensed.  So this one, this part that we're not going to be doing is only kind of refactoring out  of this sample app, SportsBall.  But I hope that you will see that the ideas will apply in larger contexts.  Again, whenever you do something about architecture, you make these examples that make you look  totally goofy because you're doing these super-uber complicated things for almost nothing.  And that's going to happen again today.  So bear with me.  So getting started with component-based Rails applications.  I should tell you why I am here.  My name is Stefan Hagelman.  I work for Pivotal Labs and I'm in that category where I've spun up a lot of Rails applications  sound.  I have a feeling that I keep touching the microphone in a funny way.  Is that true or not?  I wrote a lot of applications, but there was one client in particular that kind of kicked  off where we were engaged with them for almost two and a half years and the app grew really,  really large.  And we were looking into ways to kind of bring some structure into it without leaving the  realm of the monolith, if you will.  So I then did that to almost every app that I did thereafter.  And it's kind of still such a...  It's a weird thing to talk about because if we were at a Java conference, I wouldn't give  this talk.  If we were at a Go conference, I wouldn't give this talk.  There's many languages in which this is so common that there would be no topic for me  because structure within applications is something that is a problem for Ruby because  of the way it is.  It's not a problem in other languages.  And I've kind of become obsessed about it.  If you kind of want to just get an overview, go to Conferrex and find the talks on this  topic that I've given.  So despite this not being a talk, but a lab, I want to answer the question quickly of why  you want to do component-based Rails.  I see a host of benefits when you add this one tool, namely a component, to your toolbox  for Rails.  Let me go over these.  So the main change that you're going to see when you add components to your application  is that you're going to be able to draw something like that graph.  And that's OK.  So let me just go over the examples because it's hard to otherwise explain how important  that is to have another structure like that.  Communication.  So we don't know what this application is about, but I can tell you that there is an  importer that has something to do with programs, as you can see at the bottom.  And obviously there's also something going on where there are sources and master, so  probably some sort of import and reconciliation of some sort of thing that's called a program.  Oh, yeah.  Look at the cluster.  Yeah, probably that is the reconciliation piece where sources get kind of merged or whatever  into masters.  If I don't, you indeed picked the worst spot in this lab.  You should probably.  So you can talk about your application at a higher level, which you can't do because  what else do you have?  You have module names, but modules don't kind of segregate.  So the important piece is not just that you have a box, classes are boxes, if you will,  modules are boxes.  So what do the boxes mean?  And when you go to components, you have arrows and you can draw them from one box to the  other box, but you cannot draw them back, and that's the important bit.  So I can communicate much easier about an application because I have a new higher level  structure that I didn't have access to before.  It makes collaboration easier.  Say I'm working on source today, stand up this morning, we decided I have a feature  that touches source, you have one that touches user.  In this graph, again, without knowing nothing else about the application, we know a few  things.  If I truly only change source, I do not need to run the tests for master programs, reports,  users, and styles.  My source code does not touch that source code.  That source code doesn't know that I'm touching something because it doesn't even know that  that exists.  It doesn't care about it.  So I don't need to run those tests.  And similarly, you have things you don't need to run tests for because you're not affecting  that part of the code.  It's still one application, mind you, right?  But we don't need to test those things.  We only need to test boxes that either we change or that have arrows pointing into us  because those are the things that could be affected by our change.  And it also means that the area where we could potentially be in conflict with our change  at the end of the day, there's only two if you just go strictly by numbers and set all  these boxes at the same size.  Two out of 10 boxes, we could potentially have a conflict.  If you have one application and no further structure, you can't think about these things.  You certainly don't have the benefits of running less tests and having less conflict possible.  Creation is super interesting.  When you say you will have a larger new feature that you want to roll out, you can put it  into a component and switch that component on in development and in test and then in  review, but you never switch it on in staging until you're ready.  You can talk about it.  You can change it.  You can show users.  You can maybe give beta users access.  And the way you turn it on and off is one line of code where you turn that gem on or  off in the respective environment.  Maintenance.  Save.  Save reports is an API.  Save, I don't know.  Maybe it's a SOAP API.  We wrote that and we don't like it anymore.  We want a JSON API.  The way you can create the JSON API is you take reports, the folder, there's a component  there, you duplicate it, you call it reports2, you name every occurrence of reports with  reports2 and you just start changing reports to whatever you want.  You can slowly work your way to reports2 deprecating reports.  And when you're done, you just turn on reports2 and you turn off reports and your codebase  is as clean as it was before.  And there's one major thing that kind of underlies all of these benefits and that's comprehension  because you can suddenly do something, again, you can't do when you just have classes and  these other concepts as your mechanisms.  And that is, you can do this.  When you are onboarding someone, when you're thinking about what you're doing, you can  treat everything that is below you as external and everything that's above you as not mattering  at this moment.  Whenever you look at a part of code, you have a much lower cognitive overhead to kind of  understanding what's going on because it is all smaller and you're creating more separation.  All right.  So Ruby installed now.  You were somewhere there.  Sweet.  Did the test pass?  Cool.  All right.  So what's the app?  I don't know if anyone started it.  It's a tiny app.  Is scaffold deprecated?  I don't know.  It's scaffolding to create these teams pages here.  Falcons, Braves, I have on my app.  They also play each other, which is funny.  Also the Falcons win, which I don't understand because the Braves have clubs.  But okay.  So the Falcons win in this case.  And this is, here's the main contribution of the app.  So this is all scaffold so you know the views, right?  You can do everything with this.  So then you can ask the app, if the Falcons play the Braves, who's going to win?  We have only one game.  Falcons won that game.  Prediction Engine, pretty smart says Falcons are going to win again.  So this is what the app does.  The structure of the application, and now join me in opening some code.  The structure of the application is interesting.  If you open the source code, the first thing you notice is what?  You notice this component, but there's no app directory.  So you can, but you don't have to, you can delete the application directory because you  can replicate it inside of a component.  And sorry, I'm using component when I talk about gems and engines inside of an application  that are vended with the code.  I just, I use all of those things and I often mix them up.  And yes, there is a components folder, right?  But when you, when we try to understand what's going on here, probably the easiest thing  is to look at routes and realize there is indeed nothing in this application that provides  anything.  We're gluing an engine into this app.  So to see what's going on, we could look into the routes of the component.  And that looks more normal, right?  Now you see our games and teams and prediction.  But keep browsing around.  If you see something weird, just shout it.  So I'm just going to talk over how I think, or how I view these things.  So the interesting bit in the gem file, which obviously is, so app, app is the component.  This app needs to be loaded.  So we have a gem app here in line four.  And it pulls it out of the path.  This path thing is usually, is documented as being for gems in development, where you  have a local, a dependency on another gem and you're developing that, so you're pointing  to it locally.  And I guess that's kind of still true because I'm working on this app so everything's under  development, if you will.  But so this is how this application, and when you bundle that, it's going to show you everything  and it's going to show you here that it's loading app from source.  And this is something that, when other people talk about engines, they often say, and then  you have to publish it.  And what I say is, and then you do not publish it, not ever, unless something else happens  way in the future.  But you don't publish it.  This is wonderful.  You don't have one application here, and if you look into app, in fact, this is totally  normal except for this annoyance of seeing this namespace app everywhere.  But if we go through this, there is nothing surprising here.  Again, yes, an engine.  So we use mountable engines, as we'll see in a second.  But so everything is namespaced with the name of the engine.  Namespace controller, pretty normal, predictions controller, teams as a welcome page.  And then game again.  Namespace, so yes.  If we find the migration, that's also an interesting bit, the migration for this team, obviously  it's called app teams because also the tables are namespaced to the component.  But as you can see, the migration lives inside of the engine.  And you may have seen for what is it, device, I think, has a helper to install a couple  tables.  And many engines that you import as kind of external dependencies will have this rake  task, which is a rake engine name migrations installed or something like that, which installs  the applications into the main application.  Now, we didn't mention earlier that in DB, there are no migrations.  And there's a little trick you have to do to make this work.  And that is a through an initializer in here, which append migrations, it detects when it  gets loaded into a Rails application.  And in that case, it takes its own migrations, so the apps migrations, and adds them to  the main apps migrations.  That way you don't have to copy them, and they just get run.  That's why when we run rake, DB drew up.  So again, you saw the main app doesn't have migrations, but when I run migrations, they're  just there, because the app kind of hooks them in.  See, I was going to have a later slide that says app is the worst component name you could  ever come up with, but I've already confused myself three times.  So app is clearly the worst component name you could ever come up with.  I'll have more of those later.  Okay, let me check if I've talked about all the things.  Other questions, people are concerned about me doing this.  So no app folder, gems loaded in the path that we saw that is an engine.  It was a mountable engine, I mentioned that.  We saw the routes migrations.  Non-published gems are funky.  This is one thing you will find annoying, and it is truly annoying.  When bundler pulls in a gem, it looks at the gem spec of that gem, which is why it's important  that you always list all the dependencies of that gem, and I would suggest you do both  runtime and development dependencies.  You list them fully in the gem spec.  Now you can still give gems gem files.  That's used for local development.  And so we're locally developing, and I happen to check out this true skill, which is the  prediction algorithm this thing is using.  In fact, it's a ranking algorithm.  So it's using a git version because the published version is broken, it's unmaintained, so I'm  using this.  Why is this funky?  Well, when bundler loads this gem, it looks at the gem spec, and it says true skill, and  it's going to go to RubyGems and download any version of true skill because, in fact,  I didn't even give a version.  To prevent it from doing that, I need to restate that line in the main app.  And in fact, up the transit dependency path, I need to restate such an unpublished gem  should I have it.  That's an annoyance.  It is, unfortunately, I looked into bundler.  I think it's not possible to change that in an easy way where we would kind of not have  that problem.  You also saw that in the gem spec, every other production dependency has an explicit version,  the tilde arrow or not a grater.  The reason for that is we don't want untested code in production.  It could happen that if you just say, you know, rails tilde arrow 419, that you actually  get a higher version.  So you can run your tests potentially.  You can run your tests in the gem with some version of Rails.  And if your application states this dependency slightly differently, you could be running  the application in production against a different version of Rails.  That's like having untested code because you're testing it with some dependency and you're  running it with some other dependency.  You lock the dependencies down so you're ensuring that you force the app to have this  version of Rails.  This is a lot of stuff.  And there's one more thing I wanted to talk about.  So I created this test file here which kind of wraps the RSpec test that this engine has.  And the reason for that is that I don't want to have to worry about changing my build script,  the thing you ran at first, when I add more components.  I want to be easily able to refactor.  And this build script just finds all the test.sh files under this application and runs them.  So as you notice, there is a test.sh right next to this which runs the test for the main  app.  What you probably also notice when you ran it is there are a lot of tests in the engine  app and there are very few tests in the main application.  The reason for that is I highly recommend that you run, that you create your tests local  to the code so that you can in fact think about those things as components and they're  independent in a certain way.  And so you have to think about what is the contribution of some component that it has,  needs to have tests for.  And there's only four tests in this main application because really the only thing it does is this.  And so the tests are in fact, you could say, that's a little bit much because it's testing  that the structure inside of app, right, teams, games, and predictions are not a feature of  the application.  They're a feature of that component.  And you can kind of go, give me a beer.  I'll discuss that more.  But this kind of test just high level that the glue code works.  That's the intent, right?  Test that the glue code works if your job is to glue, test whatever you're implementing  if that's your job.  All right.  So that's all you need to know about components.  So now we're going to make some.  But which one?  So I showed you the app.  I showed you a couple of the files.  You have them in front of you.  Please look through them.  What should we extract first?  So right now it looks like this.  But do you see anything in terms of structure that might be in this application?  Sorry?  The prediction engine, yes.  Thank you.  Did I show you my slides?  So the prediction engine is one suggestion.  Any other suggestions?  Is there no other suggestions?  I couldn't quite see who that was.  Why the prediction engine?  It feels natural to extract.  I think that's, yes, it does to me too.  If you dig why does it feel natural to extract prediction?  Yes?  Yeah.  I totally agree.  So games and teams are tightly coupled.  In fact, if we look at game, if you open game, you see that game has associations to the  teams.  The prediction engine is kind of a layer on top of that.  And if you look at prediction, open the predictor.  The predictor has, gets things, right, that are incidentally called teams and games.  But if you look at what it cares about, the predictor doesn't care that teams have names.  The only thing it cares about is that teams have IDs.  The only thing it cares about for games is that two IDs have a certain outcome in the  thing that is called games.  So it looks like that, at least when you look at predictor, it doesn't care at all about  teams and games.  And so let's extract predictor.  What I'm saying by that is let's try to extract predictor and prediction.  If you look at prediction, that's kind of the result, I think, anyways, of that prediction.  So extract the domain gem.  If you watch the Uncle Bob talk where he rants about having lost so many years in architecture,  where he says that we have this problem where we bind our application to this weird delivery  mechanism that happens to HTTP and this library that does that well, which is Rails.  If you think about this app, and yes, it's tiny, and I'll get off my soapbox in a second,  this app is tiny.  You can see that the left side, teams and games, is kind of crud.  That sets things up.  What we're really changing the world with, the Uber of predictions here is this right  side where you put in two games and it tells you who's going to win.  That's going to make you a millionaire if this thing actually works.  So that's its contribution.  That's our domain.  So it feels natural.  It should feel natural.  And if we extract it, and if I just am right about this, what we just kind of discussed,  namely it might not have that much to do with Rails because it doesn't even care about  teams and games being active record, then maybe we can make this thing not know about  Rails.  And we can't.  All right.  So let's do that.  And by that I mean you do that.  So the idea is we have one component already.  It's app.  We want to create a second component that's going to be predictor.  And you're going to do it.  This is terrifying, by the way.  I have no idea if this works.  So because we said this is likely not caring about Rails, do these first two lines.  So what they do is we're creating a gem, a straight up gem, not an engine.  And you'll see it generates a couple files for you.  And then I want you to delete git because we are keeping it in this repository and we  just want, you know, mother git to kind of take over.  And then there is just a few steps, albeit very gnarly steps that we have to do to extract  predictor into this.  So it felt natural to think about prediction separately, move the classes, so predictor  and prediction, move the tests.  There's I think only a predictor test in there.  Now these things were namespaced as app, right?  We're taking it out of the app component.  We're putting it into a predictor component.  So we probably need to rename space, everything that's in there.  Then we have to fix tests.  Then we have to obviously, we're made this a gem now, so we have to make sure that it  gets properly loaded in the component because app depends on this thing.  And then hopefully we can fix the tests in the engine and we can run it, we can check  that the main application still works.  Just so I'm not kind of doing something that most of the people are not interested in,  is anyone actually interested in doing this right now?  Then raise your hand.  Okay, so just go, I'll walk around and see how this is going.  And it would be really interesting if you have, if you can't run your tests or if you  find out what you need to do to make the tests in predictor run.  Shout out.  I'll come by and we'll discuss.  So what you should do is you should start by creating this gem and making sure you can  run tests in there.  When you move the files in, it'll blow up all over the place.  Keep running the tests and fix the first thing that breaks these things and keep going.  And as soon as you have the tests inside of this new component working, go one step out.  What's one step out for this one?  Well, the app component depends on the predictor component.  So the next step out is run the tests of the app component.  And only when you've done that, run the tests for the main app.  Rails Engine New Mountable is that off limits?  Rails Engine New Mountable is that off limits?  For this one, yes.  Just create the straight up gem.  So are the tests running for anyone?  And if so, what did they do for it?  Someone here had a bundle install error for PG.  Right.  It was going pretty good.  Are you going into the component that you just created?  So go into predictor.  I can't even get the app to run from source.  This app does not depend on PG, so I'm kind of concerned that you seem to have a PG dependency.  There you go.  Where was it?  In the production gem file, there's Postgres.  If you have trouble with Postgres, delete that.  So anyone else having a different problem than Postgres?  Yes.  At least for me, if I do bundle gem predictor, it'll create an RSpec.  Oh, okay.  Oh, yes.  I guess it says there, answer the questions.  I did that again this morning and it didn't ask me questions.  I would take RSpec because the rest of the app is tested with RSpec.  Yes.  Which folder do you want predictor to go into?  Which folder do I want predictor to go into?  That's a great question.  So these things are gems and the only thing we care about is that we can load these components.  You saw the path command.  You saw the path option on this gem.  It doesn't matter.  If you put it wherever you want, you just change that path to be correct and everything  else is going to work.  I already created that components folder.  So the next command might be for you to move predictor into the components folder.  It will also have benefits when we do the next one.  Is anyone at least kind of getting an RSpec test output?  Is anyone getting read or is everyone still blowing up?  Yeah, okay.  So if you've created the gem, you can actually run RSpec right away, which is great because  it's going to tell you that it's passing because you have a sample test in there.  I think that is like expect true to be true.  So now if you're in that state, take the predictor and the prediction, put them into the predictor  gem, move its tests into and try to run that.  That's going to blow up.  Yes.  So in the prediction, predictor gem, am I wrong?  When you copy the predictor spec and you created the bundle correctly, it will ask you probably  to overwrite the existing spec file in there.  So then you'll get rid of that sample test and you should see breaking tests.  I'm not sure what to do next.  I finished building it.  Sorry about that.  Oh, no.  It isn't really timey.  So you go into predictor.  So you have no tests in here.  So take whatever IDE you use and take the predictor test out of the app component and  move it in here and take the files, predictor and prediction out of the app component and  move them in here and then run the test again.  Yes.  Here's someone else's lost.  So this is working out great.  So we just do those two first two lines.  Okay.  Cool.  All right.  Keep going if you're going.  Yes.  I think if you're going to do it on the screen.  So you're hacking away and yes, you're realizing it's kind of gnarly.  So let me flail in front of you.  And by the way, I always use RubyMind and I think it's pretty good at this.  All right.  So I said we remove git from predictor.  If you're still going strong and you have a feeling you're getting there, please keep  going.  I'm just going to try to do the same.  All right.  We're taking the predictor.  I'm moving it into components.  If I now look at my application in RubyMind, I have predictor in here.  Now predictor doesn't do anything.  Right?  It's an empty gem.  But it was nice enough and again, I'm sorry, you have a different version of this bundle  so it asks you a question.  For some reason it didn't ask me a question.  So we want to take, extract just the prediction.  So we're going to look for models.  Here's the predictor spec.  Moving that into the spec here, just overwriting what was there.  And going into models and taking prediction and predictor out and moving them into the  namespace predictor inside of the gem.  Okay?  Is that clear?  Stop me at any time.  You realize you can just start talking.  So if we now go into this gem and run specs, it's blowing up.  The error I'm getting is unalitialized constant app.  And that makes a lot of sense because we just took something out of app and moved it into  the module predictor, which is what the gem has.  And obviously that doesn't exist.  This particular error comes out of the predictor spec.  If you want to do this test driven, you just fix this one thing here and run the test again  and it's going to fail on some different line.  It doesn't know predictor predictor.  If you don't want to do this test driven, you can kind of just start searching for app  and replace it with predictor.  So let me do that here.  So my tests kind of look okay, right?  There is one caveat.  Sorry.  That's garbage.  No, there's many caveats.  One particular one right now is that predictor being the class name is also the module name  which confuses Ruby.  So in this particular case, and there's one more, you should use the colon, colon in front  of predictor.  All right, let's go to the prediction.  This is where it's going wrong.  Or in fact, if you run the tests right now, it's going wrong somewhere else.  It doesn't know predictor.  Why is that?  Gems don't auto load stuff.  So we have to actually require those two files.  So I like to put my local requires here.  Oops.  Require relative predict.  This is to the prediction.  The predictor, if I run the tests now, still same problem because, well, this is still  the wrong namespace.  Using the namespace in these two files, tests run and they're read.  Does anyone so far get to this stage?  I have no idea how complicated this is if you have never done it.  Okay, did these steps make sense?  Do I need to go over one of them again?  Okay.  All right, if you try, I mean, try to catch up.  The thing I didn't meant, yes.  Why is it not a mountable engine?  Because we don't have to make it a mountable engine.  Thank you for asking us why is it not a mountable engine?  Why is it just a gem?  The question should be the other way around.  Why Stefan, have you been so nice to me that you didn't give me all the crap that is connected  to an engine?  Why can I live in this wonderful world that is only a gem?  Because only I screw up my world engine.  So many dependencies come with an engine, don't add it if you don't have to.  We had this discussion earlier that the predictor feels like it doesn't need to know about rails.  It seems to care only about a few things about these things called teams and things of games.  So we're just trying right now to make this thing just a gem and not include everything  of rails.  If you have a lot of gems components in your app and they don't depend on rails, do you  know how easy it is to update rails?  One of your app cares about rails, so it's going to be really easy to update it.  There were two questions.  Yes?  Yes, great question.  So are we moving everything of predictions into this new component?  When the comment was made over here that the prediction engine feels like it is a separate  part.  I did clarify that I considered that to be the thing that drives the prediction.  So what I want you to extract and what I just did extract were only predictor and prediction,  not prediction controller and not its views.  We'll get there later.  Also, if you get lost at any point in time, look at the history of this project.  You'll find a branch called Workshop.  It has 10 steps.  We're trying to do step one.  If you just want to kind of see what it will look like, check that out while we're going  over the rest.  Yeah, sorry I didn't clarify that.  I want to do that later.  So this is only kind of, as I said, the core of this application is to predict the outcome  of a game.  The delivery mechanism in Uncle Bob's words is that it has a controller and you can see  it.  So let's separate those two first.  It's kind of the intent of this first refactor.  Yeah?  Yes?  Are we expected to put our spec into the game?  Yes, are we expected to put our spec into the dependencies of this component?  Yes, make it a development dependency in that gem spec, which will make sure that you have  when you try to build just the tests of that component that you'll be able to access our  spec.  What is your wire load?  Oh, sorry.  So again, in a gem, you don't get the rails auto-loading and you, since Ruby 1.9 something,  you can use require relative.  So you load those two files to get access to predictor and prediction.  Okay, is there at least someone who's making more headway now?  Yes?  So you have your own lid?  Ah, good question.  Yes, another pitfall.  So you can see that the folder structure between an engine and a gem is not quite the same.  Engines are the core of every rails application and they tend to, it's automatically supported  that you put stuff into app and in the module they're under.  Models on the other hand are libraries so they historically put all their code into lib.  Now you can make the reverts work with either one, but yes, the easiest thing is to take  it out of app models, app prediction and predictor, and put it into lib predictor in the gem.  All right, so with these moves that I did, so I had to slide up earlier, I moved the  two classes, I moved the tests, I made sure RSpec was there, I ran the tests, I changed  a couple of module names from app to predictor.  By the way, people will say that I'm doing Java when I do this kind of stuff and then  you could answer yes, and in Java this would be easy because with static typing it's much  easier to rename all these modules, so that's why we have to do search and replace and don't  have those niceties.  But we have a different problem right now here up on the screen.  So just these renames led to the problem that we don't have access to this create team.  This is something in the test.  If you go find the source to that thing, I'm not a huge fan of Factory Girl and those  other object mother libraries.  I find it such an easy pattern that I kind of always write it myself.  So create team is a short method that saves a team that we've newed up.  But as you can see here, so first off, the gem didn't find this method.  So we could add it, but then we would have the problem that you see in line six because  that's actually creating an app team.  And I was saying earlier, when you create components, you have arrows and they point  in one direction, but they don't point back.  And remember, we're extracting the predictor.  App is going to depend on it.  Predictor will not have access to any of the things that are in app, and it shouldn't.  But we also said that prediction doesn't really care about anything about team and game.  Let's look at what it exactly cares about.  What it cares about, so you give it teams when it initializes.  So here's how it learns.  You initialize it and you give it a bunch of teams.  That's kind of a setup as a lookup, as you can see here.  It kind of makes a hash.  In fact, it's really crappy code.  So it cares about the team ID.  That's how it does the lookup.  And then it stores the team in line six, but we don't know if it does anything with it.  And then if you look at learn, it takes games and it cares about the IDs of those teams  again.  So it cares about the IDs.  And then it cares about the winning team.  This is line 17.  Here, this stuff.  It cares about who wins.  And then it cares about the ID when it predicts, and that's pretty much it.  And it has some internal stuff, but it doesn't care about these things being active record.  So I suggest that this, that we can change this test  to be something like, oops, to be something like this.  As I said, I think what the test cares about is that there is an ID on this team.  So why don't I give it something?  This is duck-tapping.  Why don't I give it something that has an ID?  Does anyone fundamentally disagree with this?  Cool.  So there is, of course, more.  Here is a new game.  So I'm going to do the same thing for the game.  And in fact, there's a couple more down here.  Let me throw that in there and just run the tests again and then go into the spec helper  and require Ostrut.  OK, error changed once again because I can't spell.  All right.  So and to your question, right, if you can do something like this where you now got rid  of the entire dependency that is active record and you never have to worry about instantiating  a database, again, predictor doesn't have a database, you don't need to run migrations,  nothing gets stored, these tests are going to be really, really fast just from this one  change.  So you could argue, I asked about if someone fundamentally disagrees with this because  you could argue, but now you're just assuming that OpenStruct, this thing, is the same as  a game or a team.  And that's not really safe.  Well we have tests around it, right?  App tests what it's doing with these teams and games.  So that's one argument for it.  And if you're really uncomfortable with it, look up bounded context objects.  What we could do instead of this is we could create a prediction game class and a prediction  team class that get newed up with something that has an ID and with something that can  kind of show if you win or lose.  And you use these two things and new those two things up in the test and the app will  change its usage of this predictor to create these bounded context objects and passes them  in to when it uses the predictor.  So that's an alternative you can use if you like to see more classes.  The error we now have is that we actually had a dependency in app that we currently  didn't give predictor and that was the actual prediction engine, the external one that we  currently use.  So it's also this nasty thing here.  So as I said, we can't remove this dependency line from app because it is not a published  gem.  So I'm going to throw this into the gem file.  But as we said earlier, we also need to have this in the gem spec.  This looks really nasty, but there are no runtime dependencies in this gem, but now there  are, namely this true skill thing.  Let's run the test again.  Nothing changes.  Gems don't auto load things.  We need to add this true skill thing.  And in fact, we can probably also remove it from app which should be loading it in its  gem file.  So if you've never worked with gems, all of this is really, really weird.  If you've done gems, this is all just normal kind of gem behavior.  We're just refactoring a little bit faster over these gems.  So I can remove the require from app because app, in fact, can also remove its dependency  on true skill.  True skill is now an implementation detail to the predictor engine.  We can, yes, the others will depend on it transitively, but they won't know about it  directly.  And once I add the require up here, I have some app somewhere.  So let's see where that is.  Line 28 in predictor.  There's app.  So app prediction, obviously wrong.  This is that moment where these two columns here at the front are needed because of me  naming the module predictor and the class as new predictor.  And there you go.  Has anyone got these green now?  Yeah.  Oh, I put the require for what?  Oh, predictor.  The require for the external prediction engine, I put into the libraries, I don't know what  you call this file, actually.  This is kind of the file that you require that gets required first when you require this  gem.  That's where I put the require.  I don't like requires kind of splattered out all across files.  So I put them, I kind of up front load everything.  Okay.  Any questions up to this point?  We basically just extracted a gem.  Yes?  Yeah, predictor gem spec.  Oh, thank you.  This is not going to work.  Correct.  Yeah, that's funny because we didn't bundle.  So yes, that was, you know, my gem spec didn't compile parse run, whatever.  The test passed because we didn't bundle again.  How do we bundle, it would have blown up.  Now that we're bundling, we're pulling in the dependencies.  And so one thing I suggest we do to kind of clean things up is we take this test here,  I'm just going to copy it because I'm lazy.  We dump it into the gem.  And now obviously we need to adapt it because we're not going to, we're going to maybe do  bundle install.  Yeah, that would be nice.  But we don't want to do anything database.  So if I now come back out to the main application and run build, what did I say?  Test outward.  So we're going to the app component and running the tests here.  They're not going to pass because obviously we never renamed predictor in the app component.  So it's going to be in the controller.  We could test draft this or just find all the places real quick.  So in prediction controller, we're referring to predictor that's no longer found because  it is now predictor-predictor.  That's not the only thing that's going on because we have a new dependency as app.  And that is the predictor.  So we need to add it to app the component.  We need to add predictor.  And we need to also make sure that the app component knows that the predictor component  is in the path.  And I was showing you earlier that you can do that with path as an option.  There is a nicer way to do that, which I suggest you use all the time, which is do block.  So you can say path do, and I give it the path where it will find components.  And I say gem predictor.  And that still is not enough because gems don't auto load anything.  So I need to require the predictor in the app component.  We'll see if I got it right.  No, I didn't because there's tests that are also using app predictor.  So I have one test failing because it still refers to app prediction.  And that is right about here.  So we're working our way outward, right?  Move everything into the gem, run the tests.  They pass inside of the gem.  Move one level out.  Make sure we require it.  Make sure we rename what needs to be renamed.  Run the tests again.  And if we did it right, because this is otherwise a truer factor, and the tests on the app level  are high level enough, I think I can run the build and all of the tests we've passed.  And you see it just added the tests for the gem.  It added it right there.  Whoops.  What happened here?  So this is where, yes?  What was the command that you used to eliminate the test for the gem?  The test status age for the gem file?  Sorry for the new predictor gem.  I just copied it from app and removed everything that referred to migrations.  Yeah, it's the same test status age because we're just using RSpec, really.  But sometimes it's helpful when you have RSpec and you have maybe feature tests, you  run slightly differently, and then you have Jasmine or something that tests front end.  So you have multiple things, so I wrap them all in this test status age.  This is dangerous.  What happened here just now is that we installed predictor 2.3.0.  So this might be the most problematic thing about doing component-based rails.  Bundler is all set up to pull everything from RubyGems.  So if you forget to tell the application that what you're looking at and what you're wanting  to use is a local gem and you're not pointing to it, because remember, I never went into  the main application's gem file and informed it about the fact that we now also had a predictor.  So it just went out to RubyGems and funny enough because this name is generic enough,  it found a predictor and we've now pulled that into our app.  And in fact, you would not find a single breaking test.  Actually, you would.  You would.  There is a test breaking.  Whew, happy about that.  So there is a test breaking because that predictor happens to not define the class predictor.  Because it doesn't do that, the main app, which I had said, oh, I'm covering this glue  code here when you look at AppSpec.  If you look at the AppSpec in the features of the main app, it actually does test a little  bit more than the glue and this is the reason why I added that because I knew I was going  to miss it today.  So it's testing a little bit more to verify that we have the right kind of predictor.  So to fix this, we can do this, but I just showed you the path block.  So it's much nicer to give it the block than you can remove these paths.  We're going to uninstall the predictor and we can hopefully bundle.  And we see here it takes apps from our components and here it takes predictor from our components.  And in fact, as I said, with the path, you don't need to specify transitive gem dependencies.  So we can just bundle this and it will still pull predictor from the right place.  How long is this session?  Does anyone know?  520.  Okay.  Pretty much.  Cool.  Questions?  This one?  I removed the predictor gem from the main gem file because the path do block is smart and  it knows to look for any transitive dependencies in that path.  So we can remove this because the main app doesn't depend on predictor.  The main app depends on the app and that depends on predictor.  So you wouldn't specify, right, if a rail depends on active record, you don't specify  active record.  You just specify rails.  And the same thing with the do block is possible for components.  All right, so path do require the gems.  There's no kind of, no changes to the production code.  I think this is wrong here.  Oh, sorry.  In the module app, the only production change we have is that we need to refer to the right  predictor.  Right?  Open struct this little trick which works in this case might not work in yours, but getting  rid of dependencies when you don't need them is a great idea.  All right, so I don't know if anyone feels this is not relevant for me at all.  I hope you will see it is.  If you have good tests, so you can refactor, right, if you have unit tests, functional  tests, if you have a couple layers of tests, you will be able to perform such refactoring.  I've done it in code bases that were hundreds of thousands of lines long and you just find  a smaller component that you can tear apart and you just tear it apart.  And when you've done it a couple times, it will feel more natural.  I've seen teams that start having templates for their components because they're kind  of always the same, so they kind of use the template and go off of that.  We keep running the tests, right?  You saw me doing this.  I didn't know what was going to happen.  You keep running the tests and ask the code what's wrong.  You run them inside out because if you don't run them inside out, you're just going to  get so much test problems that you're just going to be overwhelmed.  Start with the gem.  Go to the things that depend on it.  So go in the reverse direction of the dependency tree, right?  Go up the dependency tree for your tests.  And then where you can remove dependencies.  We didn't need Rails for this prediction.  We shouldn't have it.  So the predictor is now completely free of it.  If you want to update, in fact, we could extract predictor, publish it to a gem server,  and use it in multiple applications, and they would indeed not care about Rails.  It's also kind of this single responsibility principle on a higher level, right?  The least amount of reasons to change.  When we can achieve that, well, if we don't have Rails, there's a lot less reasons to  change.  So what else is in this application?  Is there more structure in this application?  And if you're feeling, oh, this is all very academic or very labsy, yes, because it's  a small example.  But you can apply the same thing to larger applications.  There is more structure in this application.  If you look at what is actually going on, you see that the team controller doesn't need  to know that games exist.  It crud's teams.  Anyone disagreeing?  Does it need to know that games exist?  Because it doesn't.  This application, it does because this blue box is currently the truth, right?  It's not these little boxes in there.  The game UI needs to save teams in a way because it's storing their relationship as it relates  to games.  The prediction UI kind of needs both of them, right?  Because it needs to show you the teams.  It needs to access the games to make the prediction.  The predictor, however, is now kind of out of this game.  Given the time that it took to do these refactoring, we'll do the other one.  The other one's just up here.  We'll check out code and take a look at it.  So the mantra is if it doesn't have to be connected, if it doesn't have to be together,  it shouldn't be.  If you don't have to entangle it, don't entangle it.  In fact, that's my opinion is whenever you have code that can't touch each other, it  will.  There were a couple of people who raised their hands and said, I have very large Rails app.  When you look at your app and you go into the lib folder, have you ever checked whether  you can delete the Rails app and delete all the tests that are not pertinent to the lib  folder and run the tests for the lib folder?  Do you think that's going to work?  It's not going to work.  The reason it's not working is because your lib folder's entangled with your application.  If you truly wanted to have a library, you put it into some component like this predictor  and force that the tests run on their own.  Even if you have no active record or whatever in there, try it.  You will have, I don't know, hash within different access in there or something and suddenly  you have a dependency on Rails.  What can be separate should be separate.  You also saw something that was very interesting.  It's freaking painful to rip these things apart.  If you can create something new and start immediately with a new component, you don't  have to go through this pain.  It's super easy to take two components and smash them together because all you do is  search for that one namespace in the one and merge it all into one namespace.  It's really, really easy.  Trust me.  If you're doing components, it's super easy to do separately two components right off  the bat.  It's really hard to extract them like we just did, which is also why that took so much longer.  But I said this wasn't going to work at the outset.  If we wanted to make these internal arrows that are kind of put in there, if we wanted  to make those happen, we needed to make every one of these things into a component.  This is where truly, I believe the predictor is a finer factor and I would probably do  it in many applications these days.  The next ones I would probably not do because they're a little bit small.  But you could argue there might be such a thing as a team admin.  The only thing they do is scrape the newspapers and admin teams.  I actually want to give them a separate UI.  How cool would it be if this here were one application and it didn't know about all the  other stuff?  Sorry for you guys on the left.  If teams UI and teams were separate and didn't have to know about the other stuff, less reasons  to change.  This dependency, what if we could actually make this happen?  We can.  When we try to do this and we push persistence down, which is the first step, we take teams  and games and move them out of the app component.  Like so, this is where we will have to use an engine.  Rails plug in new.  I realize that it takes too much time.  Let me just check out a version of the code that has both teams and games extracted into  components.  Maybe this is the reason it takes so long.  Yes.  If you check out tag number three, you should see four components inside of components.  If we look at games, let's just take one of the two and look at them.  They're structurally very, very similar.  The only thing they have is a models folder.  The only thing that these engines need are the models.  I said I wanted to extract the persistence down.  This game is otherwise unchanged.  Same stuff.  There was a reference to the team's class name here.  That needed to be changed to point to the team's module name now.  Then we do have the migrations in this engine.  There's another cool side effect if you leave the migrations in the engines, as I said earlier.  You can move them around.  This migration is just the moved file that was earlier in the app component.  It's just moved over here.  To be able to do that, you should adopt the rule of one migration per table.  If you have multiple active record models, you're not sure whether they're going to  end up potentially in two different components.  You need their migration separate so that you don't have to go through a whole hassle  of migrations back and forth to separate out the migrations.  You don't touch the old migration.  You can't.  Remember, in production, this code is already running.  You just add a second migration that does the rename.  In fact, if we look at teams, it has exactly the same thing in its migrations.  Make one migration per table, and this will always be possible no matter how complex your  app gets.  What changes with engines is that we get, well, the engine code.  It uses isolate namespace so that anything inside of games is in the module games.  The lines 5 to 9 are the same that we had earlier.  They will show up in every engine that adds migrations to the application.  I also, I had showed you before that I don't like to use Factory Girl, and I showed you  my hand rolled object creation method.  When I extract teams and games, in fact, how are the controllers that are now in a different  gem, how are they going to new up teams and games if they need them for their tests?  Those object creation methods shouldn't live in those controllers because that's not their  domain.  But you've seen this in other gems.  Why not expose a games test helper?  The games test helper has an object creation method inside of games, which only can create  games, right, new and create game.  If you look into app and its specs in the spec helper, here in line 10 and 11, sorry,  11 and 12, it requires the test helpers from teams and games.  So in effect, the games gem and the teams gem, they were nice enough to provide their  own test helpers.  Something that is good libraries will always provide you something like that.  So here we're doing it for ourselves.  So let's see what else we need to talk about for these.  So the process is otherwise very similar.  So you're creating an engine and these BGVSJT means skip bundler, skip git, skip, what's  V, skip views, skip views, skip sprockets, skip JavaScript and skip test unit.  I'm skipping basically everything because this engine only cares about models.  And I believe if I remember correctly, this engine doesn't care about Rails.  So I actually made it dependent on Active Record because it doesn't need Active Support,  Active, all the other Active things, all the other adequate things either.  So only thing it gets is Active Record.  And as few dependencies as you can have, create them.  All right.  There is a dummy app that gets created with engines.  That dummy app makes people say, this is a really bad approach because the dummy app  is so large.  And that's true.  The dummy app is very large and it gets auto created.  There are people who try to avoid it.  There's a gem, I think, that gets rid of this dummy app.  You can see that this dummy app is massive.  What it is, you can see it's in spec dummy.  And the spec helper, in fact, does not load the engine.  It loads the dummy app's Rails app, which in turn has a requirement onto the engine.  It's just the way you test an engine.  For the tests, you put it into a skeletal Rails app so you can run tests against it.  You can get rid of it.  I often don't do it because the dummy app never hurts you.  It just sits around and it's just obnoxiously big.  But that's really its only problem.  I already showed you the renames that we had to do.  I talked about the test helpers and how they're used, how they're exposed by the engine that  has the content and exposed to other parts of the application that need to use those  parts.  So if you have checked out, tag three, then you have this structure.  In fact, so what's in the blue box is only obviously one thing, but conceptually we're  still thinking there is more there.  So because we couldn't do this in practice, there's two ways to do these engine extractions.  Again, it's nicer if you start creating the fresh best thing.  Decide whether this engine is going to be very different or very similar.  If it's very similar, instead of going through the motions of creating a new plugin, if you  already have a component, it might be easier to just duplicate the component, delete anything  you don't need from the new component and the old component and start renaming in the  new component.  It's sometimes just easier.  It's basically kind of a blue print approach where you just kind of, this is all the stuff  I need anyways.  Don't mess with migrations.  You need to move them over and then you add new migrations so that you can change the  names as appropriate.  The test helper approach I think is a really nice one.  And also, had I shown this, I would have tripped up on trying to refactor away from this name  app the entire time.  So let me get on the soapbox again for a second.  If you feel like you need to name your engine Common, Base, Miscellaneous, General, Lib,  or Company's name, anything like that, just take everything, croft random, don't care,  don't know, or duh.  It's about as good.  Sorry, these are proper module names.  Give anything you extract into components.  The most specific name you can think of, a good name, one that will start a good conversation.  Continue to refactor it.  Once you're in the habit of doing this, it's not that hard to refactor these things.  And then continue to talk about it.  I was once on a project that was one of the coolest projects I was ever on and it ended  up with 40 components.  And there was continuously one pair, we were, I think, four pairs, and one pair was continuously  working on just refactoring stuff.  And we still had one of the highest velocities I've ever, like it felt like one of the fastest  projects I've ever been on.  Because the refactoring were good for the health of the code base and we were able to  create a lot of code, despite, or rather maybe because we did so many refactoring and kept  these concepts clean.  And this section, sorry, I skipped over a couple, app is a terrible component name.  It's time we get rid of it.  So if you check out number four, this is the slide I'm looking for, it's renaming app to  WebUI.  I know it doesn't, it's not a really good name, right, it's still Web, what the hell  does that mean?  At least it captures, however, that this thing is concerned with the delivery mechanism,  like again this Uncle Bob thing.  This is the UI as delivered by the Web and it contains the controllers and the views  to do so.  So if you check this out, you'll see we're not calling it WebUI and that actually makes  a lot more sense than you could stop right there.  But again, I said we'd keep going and we already found TeamUI, GameUI, and PredictionUI.  Without looking at the code, does anyone see a reason why we wouldn't be able to just kind  of vertically split TeamUI, GameUI, and PredictionUI out of WebUI?  Yeah?  Say again?  Which ones though?  So the question was, aren't there pages that need to reference multiple?  Of what and which ones?  So what is true about your comment is that the pages we're looking at contain a reference  to Teams, top left, games, center, and predictions.  So these things are kind of connected.  I am.  How did I do that?  So, they're somehow tied.  Also there's something even more obvious.  They all share the same UI.  They share the same application layout.  So you can see that doesn't change.  If you look at the source code, you cannot currently extract these things from each other  because essentially because this file exists in the app component.  And if we would try to extract, say, GamesUI out of the WebUI, how would it get to the  layout?  It couldn't.  So before you can extract any of those UIs into separate ones, you have to extract layout  and style.  This is a very common place where people will start calling that base styles or something  like that, layout, just very generic.  Don't do it.  Try to be more specific.  I'm not much better in this case.  I called it WebStyle.  If we check that out, it's a really easy refactor.  WebStyle only contains the application layout, a couple assets, CSS, JavaScript, and that  kind of stuff.  And it doesn't contain tests.  Check out five.  All right.  We've gone over the basics.  Now WebStyle contains only assets and views.  So there's that image, that logo that we've seen.  There's application.js, application.css.  And here's application.html.  So now we have an external component that if we split something off of app, we can depend  on because in fact app already depends on WebStyle itself.  Oh, sorry.  This is just a, I meant WebUI.  So WebUI depends on WebStyle and the big change there is that in the controllers, sorry, in  the application controller, we're saying please use the layout from the WebStyle component.  All right.  So and to go into WebStyle again, there's only the engine code that loads it.  There are the assets and the views, and there are the dependencies that are Rails, use slim  here and jQuery.  Right.  Very small engine.  And it's literally just moving out, renaming all the folders that you're moving from WebUI  to WebStyle so that everything inside of WebStyle obviously needs WebStyle here, here, here, and  so forth and not WebUI.  And the dependency graph looks like this.  Now that we have that extraction, now we can take prediction UI and move it out of app,  sorry, WebUI, and it can depend on anything it needs because it doesn't depend on the  other controllers.  And that, so this was WebStyle.  So we have remove everything but assets and layouts, remove assets from obviously the  WebUI.  It's always the same pattern.  Take whatever you need.  Either you move it out or you duplicate it and then you delete it in the old place.  In this case, no tests to write.  What is important to verify is if you got all the loading right because one thing that  I just kind of showed for a second, obviously the application layout is now referring to  the WebStyle application, JS and CSS.  All right.  So in the spirit of ripping all of this apart, we can in fact do this and move all of these  to be separate because they live next, they can live next to each other because, oh, that's  what I wanted to show you.  The trick that has happened for this checkout is this here.  So WebStyle uses these paths and that will only work in the main application.  In fact, I don't even know that I ran the tests for this because that looks to me like  it won't work.  We should check it out later and verify that this works.  So the problem is when you extract WebStyle, it has this navigation section in the layout  that suddenly is no longer in its dependency path, right?  WebStyle contains the template, but it doesn't know about Team UI, Game UI, and prediction  UI existing.  So these couple lines here, they break the dependency path.  They should only be dependencies down and this thing makes assumptions about the world  that it shouldn't.  Let me jump to the very end of the refactorings.  How did everything we could do 10 refactors?  So at the very end of these refactorings, there's a very simple way to fix this and  it's a very important one to know about for components.  If you cannot depend on something, how do you get information about it?  This is the final state of the refactorings.  It turns out when you split these three things off, there's actually one thing left in the  first page you ever get to, which is just an empty page, but you could split it off  too.  So that's what we've said here.  So if you check out the workshop branch, you will find Team UI, Game UI, Prediction UI,  and Welcome UI, and all these other things.  And the problem of how to glue together this navigation menu, you solve with what is essentially  dependency injection.  So the WebStyle has the application layout, but it doesn't know what things are going  to be part of the app that need to be shown.  So in the main application, I just set a config variable here, main nav, and it gets a nav  entry from everything that wants to show itself, if you will, in the nav.  If you look in here, this is just on the module.  It's a name and it uses the route helper to find one path.  This is for Teams.  Again, the config variable gets set in the main application in this case, and WebStyles...  Where does it make use of that?  Right here.  So WebStyles just goes over the configuration and shows the links.  Now WebStyles here says rails.application.config.  So WebStyles expects and will actually blow up if the main application does not define  a config called main nav.  And that's why I call it dependency injection is because you're configuring something over  here and it's just expected down here.  So it's not really injected, but it's expected.  So you should write a test around verifying that this works.  So the way to test WebStyle now is to create an empty dummy app, but make sure that this  configuration variable gets to something so you can test in isolation that the navigation  construct at least works.  And when you pull it into the main app, you should test that everything still pulls together  correctly.  And I think the app spec never really changed for this application.  So it still navigates to all those pieces.  So throughout the entire process, this test made sure that the entire application worked  as expected.  When you have done this, your test suite looks a lot different from what a normal test suite  looks like.  It's going to continuously create migrations because there's a ton of things that need  to create migrations.  All these separate engines, you know the dependency path now or the dependency tree, everyone  that is above teams and games needs to run migrations.  But they all just run a couple tests, and those tests typically run pretty fast.  Incidentally, when you run this test suite on Travis, it's very nice because you can  do this.  You can tell Travis, you can use the gem file option that you have in Travis to point it  to every single gem file of every single component that you have, and it will just run all of  your test suites in parallel.  So this is a build of this state that we're looking at right now, this checkout of workshop  for this application with Travis right now.  It has a build for every single one of these components, and we can see that for some reason  WelcomeUI, that's hilarious because WelcomeUI doesn't have any tests, takes five and a half  minutes, but it's because of this Travis problem with loading gems, so you have to do a retry  and probably get stuck there.  So all the test suites themselves are very small.  Now we have to kind of, I changed this whole workshop up a little bit because the actual  refactoring takes too long, but what in your application are these parts?  I want to talk about that for a little bit.  So if anyone kind of would like to share, I have this or that, right?  Please go ahead, but I'm just going to kind of make stuff up that I've seen time and time  again.  If you have, in fact I was talking to someone over lunch today, so he was looking at TV channels  and content for it, I think.  What he will have is someone who can admin people so that those people can admin channels  and maybe other people that can admin particular shows, and then there is logged out users  who can see this content.  Super admins, channel admins, show admins, and logged out users.  Those are perfect verticals.  The user interface, which is the website, or whatever you want to call it, will probably  look totally different than those admin sections.  But those admin sections could also be separate from each other, so you can make verticals  out of them.  Similar to what we have here, Team UI, Game UI, I tried to say this earlier, what if there  is a person, an intern that just inputs all the teams?  They may never touch the games because that's way too complex for them.  So it's a separate UI.  So in your app, these things will be larger.  They will indeed be something like a channel, or they will be accounts, or tenants, or whatever.  Those UIs, you can split off vertically with components.  And then you'll find, as soon as you try to do that, just like we found here, that you  find common things, like teams and games.  They're used by multiple of these verticals.  So first, you kind of think about your application.  How can I split this thing up into vertical pieces?  And then you realize you have something common, and then you need to slice it horizontally,  because you need to pull out what is common, in our case, teams, games, and web style,  so that everything that gets vertically split can depend on that if it needs to.  And as you keep doing that, you'll find it's really easy to slice and dice in those things.  If you get into the groove, you make more and more of these, and in the end, you can  create really large component structures.  Yes?  I'm wondering what the...  That's a very good point.  So the argument is, why not leave the web styles in the main app?  And you could, in fact, do that.  You could create the layout that the engines use, I believe, anyways, it should be possible.  You could create that in the main application.  In the components, then, you would have a weird situation where you don't actually see  what the component is going to get rendered into once correctly gets rendered into the  main application.  But you're saying, much like the application glues together just the mount points of these  engines, it could glue together the UIs of those engines.  I think that's a very good point.  You could say, the layout belongs there, and then what you would test on the main app layer  is that this application layout, in fact, does pull together the components like we would  like to pull them together.  And if you...  You can, in fact, run any of these engines.  You can just fire up a Rails server.  The dummy Rails app inside of an engine, you can run to see just that component in action.  It self-contains, you can run it, and that would look different than the main app because  it didn't have access to web style.  I think...  I don't know.  I'd have to try it if it looks nicer, but I think it just might.  It's a good idea.  Yes, but now you're adding one layer of complexity.  So in his product, they have extracted styles into a gem, and they have a constant struggle  to keep all the applications using the same styles and keeping that up to date.  Obviously that's something I recommend not doing as long as you can because this is still  one application.  If someone finds this predictor terribly interesting, I might entertain the thought of extracting  it and actually publishing it to a gem server.  Web style, I would probably never publish to anywhere.  Now, if you have multiple apps and they should use the same things, then you start having  these conversations.  But you see why, even if it was kind of painful to try and do the first refactoring, it took  me on stage, nervous, and typing at the wrong angle, 15 minutes to extract a component.  Why is that possible?  That's only possible because you keep this code in one code base.  You don't mess around and muck around with publishing the gem servers, updating gem versions,  and pulling down gems and pulling down all these applications and components from different  repositories.  It's one repository, so it stays one application.  Yes.  So he has a very large application.  Did I get that right?  And a couple of engines, and those are all separate repositories.  So here's a, I'm a consultant, so here's a free one.  Take these repositories for every single engine, and if they're not used in multiple applications,  which these large engines typically aren't, download them, put them into a components  folder, delete their Git folder, commit it to the main application, add the path, change  nothing else, just add a little bit of the test suites as we were doing, and tomorrow  you'll just be able to actually manipulate engines within one application.  So you can refactor this thing from that engine to that engine, rename this engine over here,  and you don't need to check out five things, and when someone new comes onto the job after  the first day, they're like, shoot me now, because there's so much stuff you have to  remember.  That's the first and easiest thing to make you much more productive if you want to split  up applications, and I think it's one of the things why we believe Rails applications  can't be large, because we do stuff like that.  It's completely unnecessary to put these things into different repositories, and I highly  recommend you don't do it.  Pull them back together, it'll be much easier.  And that's just the first thing you can do.  As soon as you then find stuff that you want to refactor, you have one code base.  You've just seen how easy it is to extract a gem, so you extract a gem, and it won't  even affect deployment.  That's something I didn't mention.  Deployment for an application like this to Heroku, exactly the same.  There's no change whatsoever because you have a gem somewhere down there.  Heroku completely, transparently deals with this path command as you push.  Not a big deal.  Yes.  Yeah.  I'm very much in the expectations of this.  You're using Ruby, so good luck.  No.  So a Rails app is an engine.  Rails app is an engine plus essentially what you see in the dummy app.  Now you have multiple engines, so you have a bunch more classes.  Yes.  So you generate a couple more classes.  How many classes is that per engine?  Five classes.  That's the overhead you're loading.  You're loading five extra classes.  On the flip side, however, we have a dependency graph like this, so we can start playing around  with it.  I'm trying to see if I can find some samples.  Don't look at the company names, please.  This is the largest, whoops, the largest component graph I've ever created.  It was very, very successful.  It looks terrible.  That's because you can see how complicated this application is, which every Rails app  tries to hide from you.  Because this structure is present, you just can't see it and you can't talk about it.  Now what you can do with this, if you wanted to deploy your application to this server  over here, in fact, this is also this thing where you have multiple admins.  What you can do with an application like this is deploy it and you can change your  gem file.  Your gem file for this has tons of gems in it.  You could put a little magic in your gem file and deploy only or load in one context only  one of the admins and load only the other admin on the other server and load only the  WebUI.  It's a very easy way also to get tailored applications.  If you need to scale one part of it, just make something where the gem file only loads  that part and scale it really big.  You don't need to load the rest.  I think the performance implications are really small.  Then there's also another comment I should make.  Enrico Teati wrote a blog post a couple months ago about building that in.  Using bundler and environments to basically reduce the footprint so that you support this  just through the gem file.  I think, yes, there is an overhead to this.  To any structure there's overhead because you need to create and maintain the structure.  The benefits from that structure will outweigh, I bet you, in most situations their cost.  So I'm writing a book about all this.  I keep running around for the last couple of years believing that this is the way you  should write Rails applications.  So I said I would write a book about this.  If you go to this link, I'm publishing it as it's in progress.  You basically just, the refactoring that you just heard, I hope to be used for the next  chapter to show sample and standard extractions.  So a gem extraction of a domain, the extraction of the persistence layer, the extraction of  or separation of UI components.  So if you're interested, this is a coupon to where you get it for 50% off.  It would be great if you could support me in that because it's a lot of work and a lot  of people think I'm crazy for doing this.  So one page that kind of collects most of the information that I know about writing applications  like this is this URL.  And that's it.  That's it, yeah.  Thank you.
Get_started_with_Component-based_0_155.png Get_started_with_Component-based_156_15635.png Get_started_with_Component-based_15636_16151.png Get_started_with_Component-based_16152_18935.png Get_started_with_Component-based_18936_19607.png Get_started_with_Component-based_19608_19715.png Get_started_with_Component-based_19716_22199.png Get_started_with_Component-based_22200_23759.png Get_started_with_Component-based_23760_24683.png Get_started_with_Component-based_24684_25499.png Get_started_with_Component-based_25500_26615.png Get_started_with_Component-based_26616_42563.png Get_started_with_Component-based_42564_42947.png Get_started_with_Component-based_42948_43163.png Get_started_with_Component-based_43164_44363.png Get_started_with_Component-based_44364_44891.png Get_started_with_Component-based_44892_45035.png Get_started_with_Component-based_45036_45155.png Get_started_with_Component-based_45156_45239.png Get_started_with_Component-based_45240_46091.png Get_started_with_Component-based_46092_46331.png Get_started_with_Component-based_46332_46751.png Get_started_with_Component-based_46752_46811.png Get_started_with_Component-based_46812_47003.png Get_started_with_Component-based_47004_51647.png Get_started_with_Component-based_51648_52103.png Get_started_with_Component-based_52104_52151.png Get_started_with_Component-based_52152_52235.png Get_started_with_Component-based_52236_52631.png Get_started_with_Component-based_52632_53591.png Get_started_with_Component-based_53592_53639.png Get_started_with_Component-based_53640_54707.png Get_started_with_Component-based_54708_56375.png Get_started_with_Component-based_56376_57311.png Get_started_with_Component-based_57312_59303.png Get_started_with_Component-based_59304_59363.png Get_started_with_Component-based_59364_59495.png Get_started_with_Component-based_59496_59651.png Get_started_with_Component-based_59652_59699.png Get_started_with_Component-based_59700_61883.png Get_started_with_Component-based_61884_62471.png Get_started_with_Component-based_62472_62675.png Get_started_with_Component-based_62676_62867.png Get_started_with_Component-based_62868_63707.png Get_started_with_Component-based_63708_63887.png Get_started_with_Component-based_63888_63959.png Get_started_with_Component-based_63960_64055.png Get_started_with_Component-based_64056_64451.png Get_started_with_Component-based_64452_64763.png Get_started_with_Component-based_64764_64871.png Get_started_with_Component-based_64872_65099.png Get_started_with_Component-based_65100_65147.png Get_started_with_Component-based_65148_66131.png Get_started_with_Component-based_66132_66227.png Get_started_with_Component-based_66228_66395.png Get_started_with_Component-based_66396_66527.png Get_started_with_Component-based_66528_66623.png Get_started_with_Component-based_66624_67031.png Get_started_with_Component-based_67032_67643.png Get_started_with_Component-based_67644_67739.png Get_started_with_Component-based_67740_68027.png Get_started_with_Component-based_68028_69263.png Get_started_with_Component-based_69264_69539.png Get_started_with_Component-based_69540_69779.png Get_started_with_Component-based_69780_69899.png Get_started_with_Component-based_69900_70103.png Get_started_with_Component-based_70104_72683.png Get_started_with_Component-based_72684_73079.png Get_started_with_Component-based_73080_73895.png Get_started_with_Component-based_73896_74207.png Get_started_with_Component-based_74208_75011.png Get_started_with_Component-based_75012_75491.png Get_started_with_Component-based_75492_75623.png Get_started_with_Component-based_75624_75875.png Get_started_with_Component-based_75876_75971.png Get_started_with_Component-based_75972_76151.png Get_started_with_Component-based_76152_76907.png Get_started_with_Component-based_76908_87479.png Get_started_with_Component-based_87480_87599.png Get_started_with_Component-based_87600_87683.png Get_started_with_Component-based_87684_88259.png Get_started_with_Component-based_88260_88607.png Get_started_with_Component-based_88608_88667.png Get_started_with_Component-based_88668_92099.png Get_started_with_Component-based_92100_93515.png Get_started_with_Component-based_93516_95243.png Get_started_with_Component-based_95244_96803.png Get_started_with_Component-based_96804_97127.png Get_started_with_Component-based_97128_100487.png Get_started_with_Component-based_100488_100547.png Get_started_with_Component-based_100548_101507.png Get_started_with_Component-based_101508_104615.png Get_started_with_Component-based_104616_104999.png Get_started_with_Component-based_105000_108107.png Get_started_with_Component-based_108108_108203.png Get_started_with_Component-based_108204_108503.png Get_started_with_Component-based_108504_108551.png Get_started_with_Component-based_108552_109067.png Get_started_with_Component-based_109068_110363.png Get_started_with_Component-based_110364_111731.png Get_started_with_Component-based_111732_114719.png Get_started_with_Component-based_114720_115187.png Get_started_with_Component-based_115188_115283.png Get_started_with_Component-based_115284_115523.png Get_started_with_Component-based_115524_116843.png Get_started_with_Component-based_116844_127715.png Get_started_with_Component-based_127716_130127.png Get_started_with_Component-based_130128_130295.png Get_started_with_Component-based_130296_131531.png Get_started_with_Component-based_131532_132035.png Get_started_with_Component-based_132036_132047.png