Ruby Metaprogramming13:32 with Jason Seifer
In this video, we'll learn about basic metaprogramming in Ruby. Metaprogramming refers to writing programs that can write themselves. We'll start by building a very simple clone of the Sinatra web framework in Ruby.
The code in this video is available via a workspaces snapshot:
[MUSIC] 0:00 Hi, I'm Jason, your Ruby teacher here at Treehouse. 0:04 In this Ruby Meta programming workshop, 0:08 we're going to be going over basic meta programming in Ruby. 0:10 Meta programming refers to programs that can either write themselves or 0:14 don't know exactly what they're going to do at run time. 0:19 In this workshop, we're going to be writing a very, 0:23 very basic Sinatra clone in just Ruby. 0:25 So, let's go ahead and get started. 0:28 Okay. 0:31 So we're going to go over a quick introduction to meta programming, and 0:32 we're going to write a very, very simple Sinatra clone in order to do this. 0:37 Now, Sinatra is a Ruby web framework that allows you to very, 0:42 very simply write code that will do something like this. 0:48 So if you go to /hi in your web browser, you would get the page Hello World. 0:53 Now when that happens, there is some meta programming going on here. 0:59 This get method defines the route, and then a block is sent in and executed. 1:03 So, we're not gonna create a whole web framework in this workshop, 1:11 we're going to just get started. 1:15 So, I have created a Ruby workspace here. 1:18 We're gonna call this Mock Sinatra, so I'm just gonna create that file right now. 1:22 mock_sinatra.rb. 1:26 And this is gonna be a module called MockSinatra. 1:29 And then, let's go ahead and just start by writing a quick included method. 1:37 That says MockSinatra has been included. 1:46 Now, I'm also gonna create a separate file called runner. 1:52 And what runner is going to do is require mock_sinatra from right here. 1:59 And include it. 2:06 Now, let's go ahead and run that, and just see what happens. 2:09 So we get this little message saying that MockSinatra has been included. 2:14 And we are just doing that to make sure that we know it has been 2:18 included and printed out. 2:24 I'm just gonna throw that include statement over in the mock_sinatra file, 2:26 run it again, and we see that its been included. 2:29 So just requiring this other file can include the module 2:32 as soon as we create it, if we put the include at the bottom of the file here. 2:38 So now that we've done that, we can go through and 2:43 actually start writing our little Sinatra clone. 2:45 First, let's go ahead and head on back to the Sinatra documentation right here. 2:50 We've got this method get /hi. 2:56 So let's go ahead and write that first. 2:59 Now, go in our MockSinatra module. 3:03 And we'll just call this get. 3:06 And we'll throw a path in there. 3:11 And for right now, we're just gonna print to the screen, 3:16 defining a GET request for (path). 3:24 Now, let's see if we can get that working in our runner. 3:29 So we start off with just get /hi, and clear the screen. 3:35 Run this again. 3:39 And hey, we've already started a little bit of the API 3:41 by just writing this one method, get /hi, get /hello. 3:47 Let's go ahead and run that again, and see what happens. 3:53 Okay. 3:57 So, we got what we expect. 3:58 MockSinatra has been included, and 3:59 we're defining a GET request for both /hi and /hello. 4:01 But, that doesn't quite do anything just yet. 4:05 So let's go ahead and use a little pattern 4:09 where we create an instance variable and then return it. 4:13 We're gonna create one called paths. 4:18 Then, we're gonna say this paths instance variable 4:21 is going to be conditionally set to a hash. 4:25 And now once we've done that, we can access it from our other methods. 4:29 So we could say paths, [path], but what happens now? 4:36 We've just got this get. 4:41 Let's go ahead and print out paths.inspect and see what happens. 4:43 Clear the screen. 4:50 We have an empty hash, because we need to assign something to it. 4:51 Here's where it's going to get a little bit tricky. 4:57 In Ruby, we can send a block into any method. 5:00 We do that by writing the word block and an ampersand. 5:04 The ampersand is called pretzeling the block, 5:08 which makes it into an actual variable that we can use. 5:11 So what we're going to do is say that the paths hash key 5:15 of the current path that we send in, is going to be equal to this block. 5:21 We're gonna save the block as the value of an item in a hash, 5:28 which means that later we can access that hash. 5:33 Let's go ahead and just do something in here, 5:38 just gonna print out I am the /high method. 5:41 I am the /hello method. 5:51 And we're gonna just run this again real quick and just see what happens. 5:56 So now we can see, we define the get request for slash hi and 6:00 we define the get request for slash hello, and this slash hi hashkey, 6:03 when we inspect these paths is gonna be equal to a proc instance. 6:10 And a proc instance is what gets created when a block is assigned to a variable. 6:16 A proc instance is just going to be locally stored block code, 6:23 which means we have access to it later. 6:29 You'll notice that neither of these ran. 6:32 So let's go ahead and make a method that runs these, and 6:34 we're gonna call that method run. 6:38 And the idea behind making a method called run would be we're 6:46 gonna look up to see if the path exists. 6:50 So if this path that's sent in exists inside of our path's hash as a key. 6:56 Well now we can use a method called call to run the block, so 7:08 the block is stored, now we have to run it. 7:13 We would normally do that, like if we were inside of our get request, 7:16 we could say block.call, and that would run it immediately. 7:21 Let's go ahead and save that and run it, and just watch that happen. 7:25 So here we see defining a get request for /hello, I am the hello method, 7:30 and then that's still equal to the same block when we call it right there. 7:34 But now, instead of just calling it right there, 7:39 what we can do is take this out, and remember, this is a hash, so 7:41 we could say, paths, at that hash key, is going to return a block. 7:47 So get/hi would return this block instance, 7:52 which is the block that we pass in. 7:56 So now since that's a block, we can call it. 8:00 Now I'm gonna take out that inspec method, and we could say run/high. 8:06 And I'm gonna clear my screen here, and run this again. 8:14 Ruby runner.RB. 8:16 So, we define these get requests, 8:20 because we're running the Get method, so that is always printed to the screen. 8:23 And then to pin the block to this path's hash at the key. 8:28 And now, all we're doing is running the high method, and 8:31 it's printing out whatever is inside of this block. 8:35 Which in this case is I am the /high method. 8:38 And lets also print out to the screen Hello World. 8:45 So I'm gonna clear my screen here, just run this again. 8:54 Okay, I'm the high method. 8:56 Hello, world! 8:58 Great, and if we wanted to run the hello method, we could do that as well. 9:00 And I'm gonna clear my screen and run that again. 9:16 And we get I am the /hello method. 9:20 So we could also throw an error if that didn't work. 9:23 We could say raise StandardError.new No route for path. 9:28 And let's go ahead and run that really quickly and see what happens, so 9:36 if we try and run something that doesn't exist. 9:40 Like not found, we should be able to raise and see our own error. 9:42 No route found for not found, okay, that's exactly what we wanted. 9:48 Now I am gonna take that out for just a moment. 9:53 And, let's say, for some reason, if you've ever seen the Rubion rails framework, 9:56 there's a concept of before filters, 10:01 where you can run something before something else. 10:05 Let's go ahead and implement that right now. 10:09 So, you can say before hi. 10:11 We'll say I am running before /hi. 10:15 And then we'll print a bunch of others things to the screen. 10:22 So now if we go over to our console here and type ruby runner.rb, 10:29 we get undefined method before. 10:33 Let's go ahead and implement before and we can use the same pattern 10:35 that we used here with paths, and we can do that also with filters. 10:40 So we'll go ahead and write before_filters, and 10:45 that's gonna do the same thing. 10:50 Empty hash key here. 10:56 And now, we can do the same thing and 10:58 say before_filters path.call. 11:02 So now, any time we go to run something at this particular path, 11:07 if the key exists, even before_filters hash, that will be run. 11:12 So we're defining, basically, another proc or block at this particular path. 11:17 So now when we run it, we should see I am running before /high, and 11:24 then I am a /hi method, Hello, world, even though it exists afterwards in our code. 11:28 So now I'm gona click down here to the console, clear my screen and 11:34 type ruby runner.rb. 11:38 Undefined method before or main object, oh what happened? 11:41 It looks like we forgot to define that. 11:45 Define before, path, block, and 11:48 we can just do the same thing with before_filters, 11:52 path is equal to this block. 11:57 Then when we run it, it should work just fine. 12:00 Okay, I am running before /hi, and then I am the /hi method. 12:03 Now this is meant to be just an introduction to how this is working. 12:08 Now we're running this all on the top level name space. 12:14 So if we wanted to, we could define our own class to hold 12:18 all these things like class MyWebApp. 12:23 And that could include Mock Sinatra. 12:29 And then we could instantiate it. 12:35 And from there we could call all these methods on the app instance. 12:39 Let me clear my screen, and run that one more time. 12:48 And we get the exact same thing right here. 12:51 So, again, this is supposed to just be an introduction to method generation in Ruby. 12:54 And what we're doing is holding on to all of these different paths and filters. 13:00 We could then go ahead, and include a lot more logic in our application code. 13:06 For example, we could include the server aspect and the routing to connect these 13:13 different paths, and run these procs when somebody hit that individual path. 13:18 This kind of coding pattern is going to be the building blocks for 13:25 that more complicated behavior. 13:30
You need to sign up for Treehouse in order to download course files.Sign up