Heads up! To view this whole video, sign in with your Courses Plus account or enroll in your free 7-day trial. Sign In Enroll
Preview
Start a free Courses trial
to watch this video
In this workshop, we go over the differences between include and extend, and also view the common ClassMethods pattern.
Workspaces
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