Controller filters allow executing code before or after a controller action. They are most commonly used to filter requests before allowing actions. They can also be used to remove code repetition and perform housekeeping tasks. Any render or redirect inside a before_action filter will prevent the action from executing.
- [Instructor] In this movie, we're gonna learn how to learn with controller filters. Controller filters allow us to execute code either before or after a controller action. They're also sometimes called controller callbacks. Controller filters help to either prepare for the action, to alter its execution in some way, or to clean up after the action is complete. As we'll see, they also allow us to filter requests before allowing the actions, they allow us to remove code repetition, and to perform basic housekeeping tasks, either set up or clean ups.
More specific usage examples include confirming that a user is authorized to view a page before we give them access to it. In fact, that's probably the most common usage for controller filters, and we're gonna see it in the next chapter when we talk about user authentication. They also allow us to set variables and default values before the action runs. Those might not even be variables that are needed by the action, they might be needed by the template that's eventually going to be rendered. Or we can find database objects. That's especially useful for saving us from code repetition, or they might help us by finding a user's shopping cart, making sure it's initialized, checking out the contents that are in there, making sure that all the products are still valid, and maybe doing some housekeeping work where that's concerned.
The methods that we're going to use as controller filters are before_action, after_action, and around_action. Around_action is just a combination of before and after actions. Before Rails 4, these were called before filter, after filter, and around filter. But those names have been updated to end in action so they more accurately reflect what they do. Let me show you an example. Here I have my page's controller, and it has a before_action at the top. So before_action and then a symbol.
The symbol is a method name. That's the method we want to run before each and every action inside this controller. It's called find_subjects, and you'll see that then, later on after my actions, I've defined that as a method. Find_subjects is going to go the database, it's going to get all the subjects sorted, and assign them to the instance variable at subjects. Now all of my actions will have access to that instance variable, and all of my templates that they render will also have access to that instance variable. It's been set ahead of time before each and every action in this controller.
Notice that I'm calling it as a private method. I've declared it below the line private. That's important because we don't want it to be treated as an action, we don't want any other parts of our code to be able to call this method. It's a method for use only by this controller. Another important feature is that if you're working with a before_action, then any render or redirect that you place inside of that method is going to prevent the action's execution. The controller will consider that the request has been fulfilled as soon as it gets to that render or that redirect, and so it won't have any reason to go on and execute the action.
That's going to be useful when we talk about user authentication, because if a user is not authorized to see a page, if they have not logged in, then we want to redirect them to a login page and prevent the rest of the action from executing. Now in the previous example, my before action is going to take place before each and every one of our actions inside our controller. Instead, I can also specify which actions activate that filter by using only and except as options. So for example, I could have before_action set subjects and then I could have only new and edit, or I could have except index and show, and that would make it apply only to those actions which I specified.
Any filters that you define in the application controller are going to be inherited by all your controllers. Remember, all of our controllers inherit from that application controller class, so they get all of their behaviors as well. If there's ever a time when you don't want one of these inherited filters to run, it can be skipped, and there's three methods that you can use for that: skip_before_action, skip_after_action, and skip_around_action. And then right after that, you'll provide it the name of the filter that you want it to skip, because there might be more than one. So we might have skip_before_action set subjects, or we might have skip_before_action set subjects only index, and then it would only be true for the index action.
Let's try using these in our simple CMS application. So these are controller filters, so we're gonna wanna go to our controllers, and I wanna start with the pages controller, that's the controller that we're gonna make changes to. I'm gonna add a before_action up here, before_action, and I'm gonna call it find_subjects. I'm noticing that down here in new and again in create and then further down in edit and update, that I'm finding a list of all of these subjects that they can use to display on their form, so instead I'm gonna have a method called find subjects that'll do that for them.
So let's take find subjects and let's drop down to the bottom of the page below private, and let's create a private method here which is called find subjects. And what's it going to do? Well it's gonna do exactly this task that we have right here, I'm gonna cut it out of the update method, I'll paste it in here, subjects equal subject.sorted. So now, I don't need to have it in update, I also don't need to have it inside edit, and I don't need to have it inside create, and I don't need to have it inside new.
Now, that instance variable is going to be set for every single action inside this controller, so I don't need to do it inside the action anymore, it's done ahead of time. However, I don't want it to apply to all of my actions, because I don't need to find the subjects when we're doing index, or show or delete or destroy, so I'm gonna specify here, only for new, create, edit, and update. Those are the only ones that should apply this filter. So now, before each one of those actions, this method will run, that method will set up the instance variable for us, and everything will be set.
I do want to call your attention to one slight inefficiency that we've created. Before, when we were calling create, we were calling our subjects here, that's where we were setting subjects, only in the case when the save failed. Now what we've essentially done is we've changed this so it works like this. Subjects is being found right at the beginning, whether the save succeeds or the save fails. So if the save is going to succeed, then we've just made an unnecessary database call.
Now that's up to you whether that's something that you can live with or not, but I just want to point it out to you that it is slightly different than what we had before. Let's go ahead and add another before action, and I'm gonna call this one set_page_count, and what I'm thinking is down here inside edit, we're setting this page count, so what I wanna do is save only for edit, and then we also do it for update, we're gonna create this set page count method. Def set_page_count, and what's it gonna do? Let's go up here and grab it, I'm gonna actually cut it out of update, paste it down here, it's gonna find the value of page count, it's gonna go to the database and count the pages, and assign that to this instance variable.
So now that's gonna be available in my edit and my update actions. Okay, let's scroll up a bit, and we'll see that we're also doing something similar inside new and create, but it's not exactly the same, right? Now we have page count being set to page count plus one. So we could make another before action that was only for new and create, or I could make set page count flexible, so that it could work in both cases, and that's what I'm gonna do. New, and create, and then I'm actually gonna take this line out of here, and out of here, and I'm gonna come down in my method, and I'm going to actually make a change here, and say if params[:action], we have access to the name of the action in our params, if it's equal to new, or I'm just gonna copy this params action, params[:action] is equal to create, then what do I wanna do? I want to increment that value by one.
I can do that with plus equals one. So if the action happens to be a newer create action, then take this additional step of adding one to the page count. Let's save it, and before we go try it out, I just want to have a brief discussion about one other thing. Some Rails developers like to take this line here, where we're finding the page, and put that into a before_action, because we're doing it a few times, right? We're doing it here for the show action, we're doing it down here for the edit action, we're doing it again for the update action, we're doing it again for the delete action, we're doing it again down here for the destroy action, so they like to do that and just have it all in one before filter, and they save themselves a lot of typing by doing that.
I personally am not a fan of doing that, and the main reason why is that I think that this just doesn't look like good, readable code. If I have update, and I'm updating this mysterious page, it's not immediately clear where that page was created or how that page was created. I would potentially have to go and look up that other method, to really understand what was going on. So I find that this is one case where even though we're creating some repeating ourselves, it's worthwhile for the clarity of the code. Other developers disagree, and you can feel free to do it the way you like.
Alright, so let's try out those before actions we created. Let's save our file, let's go over and launch our web server, let's open up Firefox, we're gonna go to local host:3000, let's go to pages, we'll wait for it to say listening, as soon as it does, here we are. Okay, so I'm on my pages page. Notice here that it went and got a list of all my pages, it also went and found out the count of the different sections that it needed, that's for this number here, but it did not make any type of database query to get a list of the subjects.
Let's try adding a new page though. Let's click add new page. It still found the subject, so that's all still good, my positions are also good. Notice that on this previous page, I had two subjects, when I go here, I now have three positions, so it is adding one, our change to how that code is working in the before filter worked, and you can see over here that it's actually selecting the list of subjects for us, alright, so it's working in that case. Let's try for edit, we get the same thing, we only have two positions this time, and it is finding a list of all the subjects for us.
So the before actions that we put at the top of this file are helping us to set things up and get things ready, they're reducing the amount of code that we have in each of our methods, and they're keeping us from repeating ourselves. On your own, try adding these same before actions to the subjects and sections controller. We'll use these again when we get to the chapter on user authentication.
- Creating and configuring a new Ruby on Rails project
- Generating controllers and views
- Handling server requests
- Using different types of routes
- Rendering and viewing templates
- Generating migrations and models
- Creating, updating, and deleting records
- Finding records with queries
- Understanding relationship types
- Writing controllers for CRUD
- Working with layouts and helpers
- Managing application assets
- Building forms
- Validating data
- Authenticating users