2.2 Adding Tasks to Projects
We’ve now added the idea of ‘projects’ to our system. However, a project object isn’t useful if we don’t add some details to it. We’ll begin by modelling the concept of ‘tasks’ related to those projects. In this lesson we’ll scaffold the task model with the standard generator, and then we’ll modify it a little.
1.Introduction2 lessons, 14:01
2.Building a Rails Application11 lessons, 2:09:35
3.Conclusion1 lesson, 02:23
2.2 Adding Tasks to Projects
At the end of last lesson, we've reached this stage. With one single instruction, we were able to create a whole system to manage projects. For the follow-up we're going to create another resource, but this time one that's related to the projects. We're going to create a task model that will belong to a project. So how can we accomplish this? In order to do that, we're gonna go ahead and create another generate instruction. Type in rails generate scaffold again. All we have to do is type in the name of the class, which is task, and give it some attributes. I'm going to give it a title, which is going to be a string. And remember that because the string is a default type, I can just leave it as is. And then I'm going to define, for example, a boolean called done. This will make sure that we keep track of whether the task has been accomplished or not. The other one needs to be a reference to the projects. We're going to use the references keyword here because we want to establish a relationship between the project and the task. Let's press Enter now and see the result. It has a very similar output to the previous instruction. So let's just go ahead and open the editor, and this time we're gonna do a couple of changes. In this specific scenario, we're gonna go to app > controllers > task_controller. And notice how this looks very much the same as the projects. So the scaffold will do something that's very similar. The main difference that we need to take into account is in the views folder, going on tasks, and then open the _form partial here. The fact that you see an underscore before it means that this template is a partial. In this specific example of the new html.erb file, you can see that we are rendering a form template. Rails automatically detect this form with an underscore behind it, meaning that is a partial, and because of that it injects it directly. Think of it as an extraction of the previous template. Basically you want to not repeat yourself and create one single form that's tucked in in its own place. And then you call it as many times as you see fit. So back to this form template. You can see we have a text field for the project_id. This is not exactly the best thing we should be doing. We shouldn't be able to type in the project_id like in a text field. Rather, we should be given the collection of projects already existing in a database and allow the user to select from that. So let's see what the effect is now. Before we do anything, let's go ahead and type in rake db:migrate. This will make sure that the tasks table is created. There you go. And now we can go to our browser. And instead of going to /projects, we can go to /tasks. The same table is provided but with the respective attributes. Let's click on New Task. We can totally create a new task just from typing this in, but we don't want to do this. Instead, we want to have a select box with a list of all projects. The fact we have a text field in the page is because we invoked that method. But what instead we want to do is call the collection_select. The collection_select method is a very unique method in the Rails API. I'm going to post a link in the show notes so you can see its documentation and what it's supposed to do. Basically, we want to refer to the project's ID, and then we want to refer to all of the projects. The way we do that is by calling project.all. Then we need to specify the value of the key, and then the text that goes right into it. So we want to refer to the project ID, this is the value that's going to be updated, and then all of the possibilities. In this case it's going to be models. Then we want to match the property that matches the value of the ID and then the descriptive text that's going to appear in the select box. If you reload the page, you will see that My First Project is the name of the only option that we have in the select box. And if you inspect the element using Firebug or something like, the option that you see there is the ID. So this is working like a charm. Let's think of creating a title for this task. For example, My First Task. Let's create it. And there you go. But as you can see here, this is really something that's really not pleasant to see. We would much rather have something like a link to the project and instead of Done: false, maybe we should provide a checkbox right next to the title. Let's do that. Let's introduce some change to our application so that we stretch our boundaries and manipulate the application to our very needs. So let's go to show.html.erb. The fact that I'm using the show template reflects the show action in the controller, which in turn is reflected in the route. Remember, if you type in rake routes, you will see that the route that we're trying to accomplish is this one with the GET method which is a default/tasks/:id, and the controller and respective action is right on the right side. Okay, with that explained, let's move on to what we need to do. I'm going to change this paragraph tag to be, for example, an H1, and I'm just going to print out the title, like so. Then instead of printing out something like this, I'm going to create a new chat box tag. So I'm going to create a new erb tag, as you can see there. And I'm creating check_box_tag, and the name really doesn't matter. I can just say something like task_done, and then I can type in task.done. Basically, done is a boolean, and this will return either true or false. If it's true, it's going to be checked. Otherwise it's not going to. Oh, I forgot about one thing. The value for this check box should be the second argument. Basically, we can just pass in an empty string because it really doesn't matter. We just want to know whether the task is done or not, and we use a boolean for that. Okay, let's save this and just give a quick check to this show page. You can see that the check box is empty, so let's edit this in order to make it done. I'm going to update the task, and there you go. The check box is now checked. The last thing we need to do is to focus on this particular string of text. Let's go to this paragraph, and instead of typing in the project object, we're gonna go and type in its name, for example. Let's save this, reload, and there you go. There's our project reference. Now if only we could have a link that points out to that project. What can we do to solve that challenge? Well, it's pretty easy. Using the link_to helper, we can point out to that project. We'll still use the name of the project as the text that goes into the link, and then we refer to the task's project. And that's it. Let's reload the page and you can see our link to that specific project. Notice how /projects/1 is at the bottom left side of the screen. Clicking on that, we will go to that project page. Let's do one more challenge. What if I want to show all of the tasks that belong to that specific project? I think that's a pretty good use case, don't you think? Let's fix that. We know we need to go to the projects folder because we want to change the way the particular project works. So under projects/show.html.erb, let's go ahead and change this also back to the header. There you go. We do know that the project ends at that specific date, which is pretty good. And now I want to list all of the tasks. So I'm typing in a header so Tasks for this project. I'm going to do something like this. I'm going to loop through all of the project's tasks. After all, we have a reference from the tasks to the project. Tasks belong to a project, so we should be able to do something like this. project.tasks.each will loop through each of the tasks and basically we're just going to print out a list item, for example. Let's do that. For each task, a list item. So task.title. We can also use a check box the same way as we did before. So let's just do this. Put in a new line here and then that check_box tag. Okay, I had that in auto completion, so I think this is good enough. In fact, I'm just going to change this to a string to which I can interpolate the task's ID, so let's do that. I'm going to inject the task.id, like so. So basically this will make each different check box different. Okay, let's save this and see whether this works or not. Let me just go ahead and type in an order list and shove that in there. Let's save and reload the changes. Uh-oh, you can see that we have our first error. It's actually a pretty good thing, because this way I can show you how an error looks like in Beos. So you have the big error message here. This is the name of the exception that gets thrown. It also says which controller and action it is being effected to. Next you see the file that's being affected and the line where it's raising the exception. You have the exception being thrown here. So this is a text explanation, the message from that exception. And then the source code in which the line was raised. You also get a web console right here. So I can type in something like add project, and you can see that it actually is nil. That's pretty odd. Well, I'm actually not even used to using this. It's a pretty recent feature. But oh, yes, so the context of this web console is actually the project. So focus on that when using this web console. The scope of the console is the object in which the exception was raised. So typing self gives me the project. So if I type in self.tasks, I get the same exception. So we don't have the tasks method defined. This is actually a great opportunity for me to explain to you a little bit about associations. We wanted to expose the task as belonging to a project. We do have that indeed, because if you go to the task model, you see this instruction. It says belongs_to, and then project. This instruction allows us to do something like, I don't know, maybe if we have a task we can access the project it belongs to. However, if we want to access a project's tasks, so something like we already had, project.tasks, this won't work. We need to establish the association on the project side. So if you go to the project here, you see that it's empty. We're gonna have to introduce a new instruction into this class. It is called has_many and then tasks, and that's it. Has_many: tasks. Now if I reload the page, this will not work because we have a different error now. It says that the id is being called on a nil. Well, that's basically because I made a mistake. We shouldn't be using that instance variable at all. So let's go back to show.html.erb. And when trying to retrieve the task, it shouldn't be with the @ sign. The variable at hand is this one, not @task. So I'm just going to remove that and then reload the page. Okay, well, it seems we have a little error here. Let me just fix that really quick. Okay, so remove that and reload. There you go. So we have our first project which has our first task. Wonderful job. In fact, we can go straight to the editor again, just one more time, and create a link to that task, and that's it. Wonderful. Now we can go ahead and reload this and go to that specific task. Just click on it and you'll be going straight to that task. So in just a couple of instructions we managed to have two different resources with their specific relationships. We have two models connected to each other with just a couple of small tweaks. We didn't do anything really big. We just took advantage of the framework. Because these sorts of features are so common in today's industry, Rails just took the opportunity and made them so streamlined that it really takes nothing more than a couple of instructions to make our application suit our needs.