 I haven't guessed by now it is on inheritance and composition in Ruby. So a little background on this talk, why did I write this? So when I started looking into these concepts, I found a lot of information that was kind of geared around more experienced developers. Maybe it was developers coming from another language that already knew these concepts, they just wanted to know how to do them in Ruby. But I didn't find a ton of material that kind of combined these two ideas in context so you could compare and contrast them. So I wrote this talk for people like me in that scenario that maybe you're new to programming, maybe you're new to Ruby, maybe you're new to object-oriented programming, maybe you're just unfunctional, and you want to understand these things and understand why people tell you things like prefer composition over inheritance. So let's get started. So inheritance is a little bit of a sensitive subject for a lot of programmers. Especially when you talk to seasoned programmers, it's kind of interesting the feedback you'll get about it. And I think that this is mostly due to a lot of people getting burned by inheritance. I think a lot of people have bad experiences with it being implemented poorly or not really fitting the problem that it was used to solve. And I love to troll my Elixir friends by talking about inheritance, so that's where that comes from. So yeah, when you talk about inheritance, you get a lot of angry people. They just don't want to deal with it, super mad about inheritance, or maybe just sad. So the goal of this talk is really to give you guys a firm understanding of what object inheritance is and what object composition is so that you can understand the trade offs of both. I'm a firm believer that there's not an absolute solution for problems that you're trying to solve, at least in programming. So we really need to understand the problem space that we're trying to work in, and understand the tools that we have available so that we can solve that problem effectively. So that's what this talk is hopefully going to do, is give you that understanding of these two tools. So before we can talk about inheritance, we need to talk about Ruby. And I'm just going to go into some actual demo code. Can everybody read that okay? Bigger? Okay. So this is about as simple of an example as we can get. So puts hello world. This is probably one of the first things that you saw or wrote in Ruby. But has anyone ever thought like, how does this actually work? Where does puts come from? How come I can use it in a top level instantiation like this? I hadn't thought about it until I wrote this talk. So maybe I'm alone there, but it's actually really interesting. So we've got some different examples here. If I run this file, obviously it works. It's going to output hello world. That's going to work top level. It's going to work if we put it into a class method. So this one, we're defining a new class hello to. We have a class method using the self keyword. So we're going to call it on the class itself called greet. And then we're going to put just a message in there. You can see that when we run this, we get hello from a class method, just like we would expect. So when we start to get into the internals of Ruby, what we're going to find out is when we start to have these naming collisions, you get some interesting behavior. So what we're talking about here, oops, is method lookup. So when we get into something like this where we have a class, and this class actually defines a puts method, and we're just accepting a message, what's going to happen is when we call puts on line 10, it's not going to use the normal puts that it usually does. It's actually going to use the puts method on line four. So we can see this working. Instead of actually outputting hello from an instance method, you can see in our puts method we're just ignoring that message and we output what is the password. So this is kind of the very basics of inheritance for method lookup, and I'm not going to go into full method lookup, it's not the point of this talk, but I really just want to give you idea of how this stuff works. So what happens is in a Ruby file, in a class, when you call a method like puts, it's going to check to see if that class has that implementation. If it does, it will use that implementation. If it doesn't, it will continue to look for it, and we'll explain where it goes to find those. So here in our next method, we've got another interesting example. We'll skip over the speaker for now, but right down here we've got a class hello four, and this little less than sign is telling us that we're going to inherit from this speaker class. So we have a speak method, we call puts, and you'll see from the output that we get hello from the grade unknown. So it's stopping here, and we're putting, this is a global variable standard outputs, and that's how the actual puts method works under the covers. Does anybody know why we can't call just puts here? It'll call itself, perfect. So if we were to instead put, well, get rid of that, so we get a stack level two deep error, and you can see this, we get from hello four, and it just keeps looping over puts, so it keeps calling itself recursively. So that's not what we want to do. In our next example, so we have hello five here, this one is going to, it has an attribute on it, and it's going to initialize that attribute with just a string in its value. So it also has another method called puts, and you'll notice my linter down here is actually telling us a little error. It's telling that we have a duplicate method. So this is kind of interesting because Ruby doesn't necessarily respond to messages with only methods. It can also respond with attributes, and that's why you see this here. So we're defining kind of a responder on puts on line four, and again on line 11. So when we call puts here, it's kind of a mystery like what we're going to get back. Are we going to get back the method, or are we going to get back the attribute? So the method is set right now to accept a default value, or it doesn't need a value, it'll default to nil. So when we call this, we get what is the password? We don't actually get the string attribute, and why that is is in Ruby the kind of the last one wins. So if we move our adder reader below this, you'll notice that my linter changes, right? So now it's telling me that this is the duplicate method, and you can imagine that when we run this, it's not actually going to output anything because it's just returning a string. If we change that to actually call puts, we'll get the string attribute. Okay, so on this example, we have another class with another method that you're used to seeing now, but now instead of speak, we have puts as a local variable. When we call this, we're going to get that same thing. We're not going to get a return value unless we actually output it, oops, and then we get the local variable. So kind of the basics of this method lookup is going to be check inside of my method that I'm calling this from. If I have a local variable, cool, use that. If I don't go into my class, do I have a method, use that. If I don't go to my parent class, if I have a method, use that. So to answer the question, where does this puts come from? We've got our last example here, and I get undefined method puts. So you might notice something kind of shady about this file, requiring this setup file. So I'm sure that's where a lot of you are drawing your attention. So if we look at this setup, you can see that module kernel, we have removed the method puts. That's why we get that error. So in Ruby, we'll go over this in the next few slides. Every object that you create will have an inheritance tree. That's just how Ruby is built. And in that inheritance tree is kernel. And that's where methods that you probably take for granted have never thought about like puts and sleep are defined. So anytime you call those, you're bubbling up that ancestor tree until you get to that kernel class unless you've actually defined them. All right. So now that you know a little bit about the method lookup, we can start to talk about inheritance. So what is inheritance? So this is from Wikipedia. I won't read it verbatim, but the basic idea is that you have a child object and when you inherit, that child object will acquire all of the properties and behaviors of the parent that it inherits from. So you might have heard, it described that inheritance should be used for an is a relationship. So an example, a duck is a bird where a duck would inherit from bird. So you could set up a base class of bird and give it common attributes that a bird would have like wings, feet, whatever you want. And then you could make a child class of duck that would inherit all of those features and then specialize it for stuff that only ducks have, right, like a buoyancy level or something like that. So inheritance and Ruby, we have a couple different types and tools available to use it. So the first one we talked about and one that you've probably seen all over the place, especially if you're a Rails developer, is this little less than sign. So in action controller, active model, stuff like that, you're gonna see a lot of inheritance in those frameworky stuff. So this method down here, so I've got at the end child.ancestors, is this method, this ancestor tree that I was talking about. So with nothing else, we have a child in its next ancestor as a parent. Parents' ancestor is an object and then it goes to kernel and then to basic object. So any class that you create outside of this parent inheritance will have these kind of, it inherits from object, it inherits from kernel, it'll inherit from basic object. We also have multiple inheritance in Ruby. So there's more than one way to do this. We're only gonna talk about include, the other two, there's also extend and prepend, but they're kind of outside of this talk. So we have the option to include multiple inheritance. So you can see that we have in our ancestor tree a child and the first thing that comes after child is the last thing defined. So in this case, it's step parent. If you were to switch those two parent, you'd actually look in parent first and then step parent. And then again, we've got object, kernel and basic object. So multiple inheritance, any dangers that you've heard about single inheritance is kind of just exacerbated in multiple inheritance. There's a lot more moving pieces and a lot more chances for you to miss something. And then the last thing I wanna touch on is namespacing. So a lot of people will see include a module and they'll kind of think, oh, I've seen classes wrapped in modules, is that inheritance? That's actually not, this is literally just kind of qualifying and creating a namespace around your class. So you'll never refer to child without that parent colon colon. You'll always have to access it that way. So it's literally just given it a different name so that you can group things together. All right, so when should you use inheritance? And the answer is when you wanna specialize an object. A child should use every single attribute and method of a parent if you really wanna use it correctly. And you should never use inheritance as a way to kind of clean or dry code up. There's much better ways for doing that. We'll actually talk about one here in a second. So the pros of inheritance are you can make code reusable and extendable, but there's a pretty big caveat on this and that is if it's done correctly. If you don't use inheritance correctly, neither of these things are true. It actually makes it really hard to reuse code and really hard to extend code. The cons, when you inherit from a parent class, you are literally creating a coupling between the two. That's usually a word that's synonymous with bad behavior. You get reduced readability. So if you have a method in a parent class that's not in a child, but the child calls that, a developer that's looking at this fresh that's never seen it before is obviously gonna have to do some digging to figure out where that method comes from. It's easy to abuse. So I would argue that inheritance and its benefits are really tied to properly naming things. So you can create these taxonomies or these hierarchies in your code where you can create kind of a visualization of how your application works. You've probably heard before that one of the hardest things about programming is naming things. So I would argue that a framework around naming things is probably gonna be hard too. I think that's why a lot of people get it wrong is it's hard to define those domains in our systems. And the last point that I wanna bring up is that it can complicate unit testing. So if you're inheriting, it's pretty hard to isolate a child and only test child behavior because you get all of that parent's behavior for free. And I have an example to show that. So you might be feeling like Tina here a little bit. Like there was only one pro and there's a bunch of cons. So why are we even talking about it? Should you just never use it? I think there is a use case for it but we'll see how limited that can be. Okay, so next I wanna talk to you about the alternative to inheritance composition. So what is composition? So again from Wikipedia, it is a way to combine simple objects into more complex objects. So you might have heard it described as a has a relationship. For instance, a car has an engine where a car would be composed with an engine. So composition and Ruby. We have, this isn't really talked about in Ruby but it's worth bringing up so you know about it. So there's functional composition which is really just creating a method from other methods. So again, you're not gonna see this in Ruby but if you look at JavaScript examples or examples in other languages you'll see this quite a bit. And then we have object composition. So this example is okay but I've got a different one that we wanna show. So we go back here. Okay. So I kinda wanna just explain what composition is and how you get to it with a concrete example that you can actually see. So we have a very simple application for explanation purposes and this is gonna have one method where we're gonna call an API and we're gonna save the result. So there's a couple of problems with this type of programming. Right now the structure of this is very rigid. It'd be very hard for us to come in here and actually change anything with confidence knowing that we're not gonna break something out. This is very small and easy to understand but imagine this in a much larger ecosystem. So what are some things we can do to make this better? So the first thing is we can start to break out these dependencies. So instead of having this method know what its dependencies are we can move them out of that method and just allow the method to access them. So this is kind of step one in composition. Step two is gonna be we actually wanna inject those dependencies into this class. So we don't want the class to hold the knowledge of what its dependencies are. We want the class to only receive that and know that it can do things with them. So this might be new to some people. It's just a key value argument and these are just default values. So we're passing in a DB which is this repo class and an API which is that API class. We're setting those to attributes and then we're using them in the method. So the benefit here is we're now able to update and change our application without changing the internal details of this method. So as long as we know that if we create a new API object all we need to know is that that API object needs to respond to this get method. So we're able to interchange anything that we want so long as it responds to this method which gives us a lot of flexibility. Same thing with the repo. And you can do this with anything that you see possibly changing in your application. Blake likes that. Okay, so when do we want to use composition? As much as we possibly can. Composition kind of pushes you towards making smaller classes and methods. That's a great thing. That's literally never a bad thing. So whenever you can use composition unless you know it's like prototype code or you're gonna throw it away but it gives you a lot of flexibility and there's not that much work. So pros that we have, it's flexible. Like you've seen, you can interchange things and swap things very easily. You can update your app pretty painlessly. It's readable. You're not guessing where things are coming from. It's very explicit. It's all in front of you. It's easy to unit test. It's really easy to isolate each part of the system that you're passing in and test it in isolation which is what you wanna do in a unit test. And all the cool kids are talking about it. So the cons, you have to write a little bit more code. That's a con. Okay, so we're gonna dig a little bit deeper and start to look at some examples of this stuff. Okay, so the first example that I wanna show you guys and this is to kind of contrast or to talk about why inheritance can complicate testing. So I have a pretty simple user class here. We have an after create method where we're going to just populate an attribute which is a good. Other than that, we've got some trivial methods like asking if there's a first name, a last name, and a method to change the first name. So we get a new feature request that comes in that we need some sort of administrator in our system. We don't wanna necessarily write all of this logic. We wanna use what we've got for user but we do wanna do some things a little bit differently. So we decide to make an admin class that inherits from user. And this class right now only has one extra bit of functionality which is to populate this admin true flag. So let's talk about testing. So when we test our user class, our unit test our user class, it's pretty simple to see. We're just gonna test all of our methods that we have available. Pretty simple, pretty straightforward. When we go to look at our admin class, we're gonna test its method. So there's a huge problem with this. When we inherited from the parent class of user, we inherited all of those methods. So how this can complicate testing is if we don't test all of those parent methods in admin, somebody can go in and change our user and not know that we're dependent on those methods in admin, right? So someone can go in, they can look at user class and say, hey, this method's really dumb. I don't think we should use it anymore. I'm gonna delete it. They delete it, they run the test suite, their user test breaks. So they go in, they say, of course it broke, I just deleted it, they fixed the test, deploy the code, everything's good. Later on you figure out that that admin class is actually using that method. You don't have a test for it now because it was tied to your user. So in order to really be a responsible developer, you're gonna have to duplicate all of the tests in your parent object in your child to know that you're actually covered, right? If you were to do that, you'd have another failing test in admin where the original changer probably could have seen, oh, this is using a little more places, I need to actually go do some investigation. So it's easy to overlook those little details like that. We've got another one. So flexibility, so we're gonna check out another example. Oh, okay. So we're gonna talk about how composition can help you when you're trying to actually isolate and test. So this is kind of the contrast to that problem. So we have a pretty simple sign up class here. It's got a couple attributes. The ones that are interesting are that persisted and invoice user payment processor and user repo or just for our initializer so that we can pass things into it. And that's the boilerplate that I was talking about. So you're gonna have to set these classes up if you're gonna use composition, but it's really not that much more work. We have a branch in here, so if we can create a user, we're gonna set persisted to true and then we're gonna set invoice to true if that payment processor can send an invoice. And if it can't, we're gonna set both of those to false. So it's a pretty contrived example, but what we can do when we're testing this is we can test each one of our logic branches however we want, right? So we know that as long as we pass in a, when we're testing this, as long as we pass in a user repo that responds true when you try to call the create method, you can get to this true logic, right? So you can stub out that actual saving and persisting because it's not really what you're trying to test here, right? You should have separate tests for your database actually making sure that you can save things and then same thing with the payment processor. So in this test, we shouldn't really be testing if the payment processor can send an invoice. We just wanna test that when they can, we actually mark it as invoiced. So you're able to isolate the bits of logic that you actually care about in this class and allow your other classes to test the logic that they should be concerned with. So you can pass in, you could create a new mock class actually think, yeah. So this is a simple example where instead of passing in the real payment processor or the real repo, you could create new classes that hold that and that mock processor could have a create method that just returns true. You could also create an open struct. So as long as you create something and pass it in that can respond to that message and you can change that, you can isolate your tests a little bit better. All right, so the funny thing is when I wrote this talk, I was really trying to defend inheritance. That was kind of my main goal. I know it's in the Ruby language for a reason. I know that there's some good in inheritance and I really wanted to find it. It was pretty difficult. There's a lot of times where it just, it doesn't solve the problem that you think you're trying to solve with it. But I did think of one that hopefully is a little bit helpful. So if you have an API where you have common things that you're always gonna share across all of your API calls, like an API token, a key, any type of kind of setup stuff that you have to do with your API provider, I think that inheritance is a great tool for that. What you're able to do is you're able to actually have these headers defined in one place. If you have very similar error handling for an entire API in different routes, you can put all of that error handling in one place. And then sometimes you get data back from APIs that isn't exactly what you want it to look like. And if it's consistent, you can also kind of clean that API response in one place. So what you're able to do is create more specialized versions of this class. So this one is a product class. It inherits from that base object so I can just call get with the route that it needs. And then I don't have to worry about any of my authorization or credentials or anything like that in this class. And then if I need to do something special to clean, like maybe the index on this one API returns in just a really weird way, I can specialize that call and clean it up the way that I need to, without all the implementation details. All right, so if you're a seasoned developer, you might be thinking what about STI, which is single table inheritance. I didn't write this talk for single table inheritance, but I'll just kind of give the same warning is it's basically inheritance taken to a database level, so you get all of the same issues that you can get with class inheritance. And I haven't had to do this personally, but I've heard that splitting them apart after the fact is a lot harder. So just make sure that you really have a good use case for it before you consider it for an option. That's all for my talk. This is my contact information. That's my dog, her name is actually Ruby. So I love Ruby so much. She's very dorky. But yeah, if you have any questions, that's my Twitter handle or send me an email or come up and talk after. I'd love to hear any questions you've got offline. I work for a company called Nav, we're in Salt Lake. Really cool company, we're growing a ton. So, and even considering remote work, so if you want to work remote or come check us out, if you have any questions come talk to me. Cool, thank you. Thank you.