Include and Extend15:25 with Jason Seifer
In this workshop, we go over the differences between include and extend, and also view the common ClassMethods pattern.
The code in the video is available in the following Workspace snapshot:
[MUSIC] Hi. 0:04 I'm Jason, your Ruby teacher here at Treehouse. 0:06 In this workshop we're going to be going over the differences between extend and 0:08 include when working with modules in Ruby. 0:13 There are some finer points when working with each of these methods and 0:15 we're gonna go over them now. 0:19 So, let's go ahead and get started. 0:20 Okay, let's get into some of the differences between include and 0:23 extend when working with modules in Ruby. 0:28 Now, I'm going to create a very simple module here called the Shout module. 0:32 Now, this module is going to contain one method and it's going to say introduce. 0:37 And the introduce method is going 0:44 to return a string that says, Hello! 0:49 I'm here! 0:54 Now let's create a class that works with the Shout module. 0:55 So a person can Shout. 1:02 Now let's go ahead and see what happens when we run this. 1:06 I'm gonna have a little statement right here, we can say Person.new.introduce. 1:10 So if I click down into the console here in my workspace and 1:17 type ruby include_extend.rb we see that 1:21 this method was run that says introduce and it says, Hello! 1:25 I'm here! 1:29 Now if we tried to call this on the Person class, 1:30 we would get a no method error saying that the introduced method didn't exist. 1:36 Undefined method introduce for the person class. 1:42 And that makes sense because when we include a module, 1:47 we get all of the different methods in the instances of our class. 1:50 However, what if we had another class? 1:56 So we have a Person and let's also say we have a Dog class. 2:00 When we use the extend keyword with a module 2:05 the extend keyword will operate on the class, rather than on the instance. 2:13 So if we Dog.new.introduce. 2:18 I'm gonna take these out of here so that we can see. 2:22 We'll get the no method error in there. 2:26 However, when we say Dog.introduce, we do not get that error. 2:31 So here we go, undefined method introduced for dog and then the program stops. 2:36 So if we take that out and then just do Dog.introduce, it says, Hello! 2:41 I'm here! 2:46 So extend will operate at the class level and 2:48 include operates on the instance level. 2:52 Any instance of the class gets the methods inside of the module. 2:55 Now, practically speaking, we could include and extend something in a class. 3:00 So we could say extend the shout module on both Person and Dog. 3:07 And this could work for all sorts of different modules, and classes. 3:17 So if we run this again, both the class and 3:24 the instance would get these different module methods. 3:29 Now, this isn't very practical, so I've prepared a couple 3:34 modules here in the attribute_maker.rb file. 3:39 Now, let's go ahead and I'm just gonna scroll through this for just a moment and 3:43 introduce everything that's going on. 3:48 First, I'm gonna load this up into an irb session. 3:50 I'm gonna say, okay, load "./attribute_maker.rb", 3:55 and that prints a couple of things to the screen. 4:00 I'm gonna clear my screen here and what this does, just to give you an idea, 4:03 this allows you to create users with the attributes of first name and last name. 4:08 So we can say, create(first_name: "Jason", last_name: "Seifer"). 4:14 That will return a user instance with those attributes set. 4:21 And we'll do another one, (first_name: "Nick", last_name: "Pettit"). 4:27 So now we have two user instances. 4:35 And we can say, find_by(:first_name, "Nick"). 4:39 And it would return an array with all the users that met those 4:46 different qualifications found. 4:49 So, how do we do this? 4:53 All we did was write this include method to include the AttributeMaker module. 4:54 But, we're getting these class methods when that's included. 5:01 And one of those class methods is called attributes, which we pass 5:06 an array of symbols to, and thenwe can set those when we create a user. 5:10 Now, how in the world does all of this work? 5:15 Well it works through the power of modules. 5:18 So I'm gonna exit that for just a moment, and 5:20 then walk through how this was all done. 5:23 We have this module, AttributeMaker. 5:26 That's the core module that we sent in. 5:28 Ignore this line for a second right here. 5:31 We also have this ClassMethods module. 5:34 Now the ClassMethods module contains our create and 5:38 find_by methods as well as this method called attributes. 5:43 Now we know that these work, and 5:49 we're gonna walk through them a little bit more in just a moment. 5:52 Then our instances also have a method called attributes, 5:55 a two-string method, and our module overrides the initialize method. 6:00 So, now, let's go back and see how all of this works. 6:06 This is a certain pattern. 6:10 When we include the attribute maker module, in our user class, 6:13 there's a method inside of modules that gets run, automatically called included. 6:18 The included method runs every time you type include and 6:24 and then the name of a module. 6:29 So as soon as the include method is fired in the AttributeMaker module it gets run. 6:31 It takes one argument which is the class which is including the module. 6:36 Now since class is a reserved word in Ruby, 6:42 we misspell it inside of this argument to get into the method. 6:46 And it could be something besides class if we really want it to. 6:51 Other common names are base and klass and things like that. 6:55 Klass is kinda one of the more common misspellings. 7:00 So as soon as this is included, we extend the class with our ClassMethods module, 7:03 which is defined deeper inside of the AttributeMaker module. 7:12 Here's our ClassMethods module. 7:17 And that will get us all of the behavior at the class level and the instance level. 7:19 So here's where it gets a little bit tricky. 7:26 We define a class method called attributes inside of this ClassMethods module. 7:29 And this is going to take an array of arguments. 7:34 Now what we're passing in symbols, and we could write some more checks to make sure 7:38 that everything we pass in is a symbol. 7:42 Now this splat operator makes sure that anything sent in is an array. 7:45 Now we call it args to be short for arguments. 7:50 So we take this and flatten it in case there are multiple levels in the array. 7:52 Then we iterate over each item and 7:56 get this attribute block level instance variable. 8:00 And then just for debugging purposes, we've printed it out to the screen. 8:05 Now we can do something really fun. 8:10 We define a method using the method called define_method. 8:12 What the define_method method does is, 8:17 as you can imagine based on the name, creates a method by that name. 8:22 Now we're doing that at the class level, so 8:26 you can kind of think of this as saying, as soon as this method is included it 8:30 would be the same as writing defined method inside of the user class. 8:36 The first argument that we would get is first_name, so 8:43 it would run define_method with the symbol of first_name, 8:48 then we pass a block to it and this block is run. 8:52 What it does is return the value inside of the attributes hash, 8:56 with the argument, which at this point is first_name. 9:03 Essentially it would be the same as writing def 9:07 first_name, attributes[:first_name]. 9:12 But this is all done for us. 9:17 Now you might wonder where attributes is coming from. 9:19 That happens later on in this module. 9:23 We have this method definition called attributes which either initializes and 9:26 sets the attribute's instances variable to an empty hash, or returns it if it exists. 9:32 Now the attributes method is inside of the AttributeMaker module. 9:40 But, since this defined method is operating essentially 9:46 at the instance level, we have access to the attributes method. 9:51 The second part here defines another method where we're setting that attribute. 9:56 We can pass define_method either a string or a symbol. 10:02 Here we're passing it a string which will define the first_name_equals 10:07 method which will set that as the value inside our attributes hash. 10:13 Now, these methods are created automatically. 10:19 So, our user class has no idea, when it's created, 10:22 that it's going to have these methods for these different attributes. 10:27 Next, we have the create method. 10:33 Now, create initializes a new instance and this is still 10:35 in the ClassMethods module, so this is still operating at the class level. 10:39 Then what this does is initialize a new instance 10:45 with the arguments that are sent in as a hash. 10:48 It then appends that instance to an instances array and returns it. 10:51 The instances array is just another method inside of our ClassMethods 10:59 module that either initializes or returns an empty array. 11:05 Or, if the array exists, it returns the instances array and 11:10 this is at the class level. 11:15 So we would have access to user.instances. 11:17 Finally, our last method that we have at the class level is the find_by method. 11:21 And we pass in an attribute and a value. 11:26 We initialize an empty array that we call found. 11:30 We then iterate over the instances at the class level and, 11:33 if this instance has an attribute with that value, 11:38 we append that to the found array and then return that found array. 11:43 After that, we have that instances method which we went over earlier. 11:49 And then here are our actual instance methods that get included from 11:53 the AttributeMaker module. 11:57 We already went over the attributes method, and 11:59 we redefined the to_s method, which prints out the class and the attributes. 12:03 And finally we overrode the initialize method to pass in a hash for 12:09 the different attributes. 12:15 We then iterate through them and for each key in value we set that to 12:17 the internal attributes hash at the given key, with the given value. 12:22 So once we do all that, 12:30 we can do all sorts of fun things that we saw in our IRB console. 12:31 Let's go ahead and check that out one more time. 12:36 I'm gonna load this in. 12:39 And you can see as soon as I type load, it says attribute: first_name and 12:44 attribute: last_name. 12:48 If we scroll up, we can see that it got to this point in the code 12:49 because included was run and then this attributes method was run. 12:54 So now, let's go ahead and create a user, 13:00 first_name: "Jason", last_name: "Seifer". 13:06 And we'll create another one. 13:13 User.create(first_name: "Bob", 13:15 last_name: "Seifer"). 13:20 Now at this point, we should have access to the user.instances method. 13:23 And we do. And f I were to create another one, 13:29 and then I rerun this instances method, we can see that that was appended as well. 13:40 So now when we clear this If I say User.find_by, 13:45 pass in the symbol, firs_name: "nick", 13:50 we get back an array with one user in it. 13:55 If I do find_by(:last_name, 13:58 we get back an array with two items in it, just like we expected. 14:02 And, if I wanted to say user as an instance, 14:09 User.find_by(:last_name, "Seifer"), 14:13 and I will call the first item in there and 14:20 see that I can go to user.attributes, and it returns a hash of the attributes. 14:24 I can say user.first_name and that just returns the user's first name, 14:31 user.last_name "Seifer", 14:36 and then I could do user.first_name = "Also Jason" and 14:39 if I checked out the user, we can see that the attributes hash was also updated. 14:45 Now what we see here with extending 14:53 ClassMethods when including a class is pretty common. 14:57 This is a very common Ruby idiom that you'll see 15:01 if you start reading a lot of code. 15:04 Go ahead and read over the different ClassMethods and 15:06 the module, and even try including it on your own. 15:10 Go ahead and play around with it. 15:13 And these are the basics of using include and extend. 15:15 By combining both of them you get a lot of power and 15:19 flexibility when writing your Ruby programs. 15:22
You need to sign up for Treehouse in order to download course files.Sign up